Merge pull request #27 from openqj/host-profile-fixes
Fix codex-host profile (capability + netlink for bwrap)
This commit is contained in:
3 files changed
+50
-26
No files matched your search
+10
-4
@@ -514,16 +514,22 @@ interrupted 且终态任务 cancel 返回 400、心跳后 sweep 不扫刚心跳
|
|||||||
`host-config-host.json`(sandbox workspace-write,state 独立
|
`host-config-host.json`(sandbox workspace-write,state 独立
|
||||||
为 work/host-state-host.json),以 `aa-exec -p codex-host`
|
为 work/host-state-host.json),以 `aa-exec -p codex-host`
|
||||||
运行(见 `deploy/apparmor/codex-host`:docker-codex 基础 +
|
运行(见 `deploy/apparmor/codex-host`:docker-codex 基础 +
|
||||||
userns/mount/pivot_root,仍禁 raw/ptrace/危险 capability),
|
userns/mount/pivot_root,仍禁 raw 网络与 ptrace;capability
|
||||||
|
必须放行——codexsvc 本身无宿主 capability,放行的只是 bwrap
|
||||||
|
的 userns 内假 root capability,实测 deny sys_admin 会让
|
||||||
|
bwrap 在 userns 创建处失败),
|
||||||
并带 `HTTP(S)_PROXY=http://127.0.0.1:8888` 让 relay/git 出口
|
并带 `HTTP(S)_PROXY=http://127.0.0.1:8888` 让 relay/git 出口
|
||||||
同样走 Tinyproxy 白名单。路由靠任务的 executor 字段
|
同样走 Tinyproxy 白名单。路由靠任务的 executor 字段
|
||||||
(POST /tasks 已支持 local/docker/cloud):宿主 worker 必须
|
(POST /tasks 已支持 local/docker/cloud):宿主 worker 必须
|
||||||
开 `--claim-executor-only`,只认领 executor='local' 的任务,
|
开 `--claim-executor-only`,只认领 executor='local' 的任务,
|
||||||
绝不抢 executor 为 NULL 的任务(否则会把本该进 Docker 的
|
绝不抢 executor 为 NULL 的任务(否则会把本该进 Docker 的
|
||||||
任务拉到宿主上跑,丢了 Docker 外层)。宿主任务放弃 Docker
|
任务拉到宿主上跑,丢了 Docker 外层)。宿主任务放弃 Docker
|
||||||
外层(无容器、无租户挂载/网络隔离),换来的是 bwrap 内层
|
外层(无容器、无租户挂载/网络隔离),换来的是 bwrap 内层。
|
||||||
(agent 只能写自己 worktree,/tmp 是隔离的 tmpfs,写不到
|
实测过的沙箱边界(诚实版):agent 只能写自己 worktree,
|
||||||
宿主 /tmp;其它任务 worktree 与宿主文件同样不可见)。
|
写宿主其它目录与其它任务 worktree 均被挡(报文件系统只读);
|
||||||
|
但 /tmp 不隔离——agent 写 /tmp 会落到宿主 /tmp 上(codex
|
||||||
|
的 workspace-write 不把 /tmp 做成隔离 tmpfs),所以 /tmp
|
||||||
|
是任务间的共享区,不能当作隔离边界。
|
||||||
- 网络白名单的诚实边界:docker run 默认 bridge 网络,本执行
|
- 网络白名单的诚实边界:docker run 默认 bridge 网络,本执行
|
||||||
器不做 egress 白名单——真正的“只放行模型 API”需要宿主防火
|
器不做 egress 白名单——真正的“只放行模型 API”需要宿主防火
|
||||||
墙或代理规则,那是部署项,不是本轮代码假装做到的,不要宣称
|
墙或代理规则,那是部署项,不是本轮代码假装做到的,不要宣称
|
||||||
|
|||||||
@@ -93,6 +93,22 @@ class HostConfigProfileTestCase(unittest.TestCase):
|
|||||||
self.assertIn("pivot_root,", text)
|
self.assertIn("pivot_root,", text)
|
||||||
self.assertNotIn("deny mount,", text)
|
self.assertNotIn("deny mount,", text)
|
||||||
self.assertNotIn("deny umount,", text)
|
self.assertNotIn("deny umount,", text)
|
||||||
|
# bwrap 还需要 userns 内的 capability(实测:deny
|
||||||
|
# capability sys_admin 会让 bwrap 在 userns 创建处失败,
|
||||||
|
# 而 capability, 允许则成功;codexsvc 本身无宿主
|
||||||
|
# capability,所以这里允许的只是 userns 内的假 root
|
||||||
|
# capability,不授予宿主特权)。
|
||||||
|
self.assertIn("capability,", text)
|
||||||
|
self.assertNotIn("deny capability sys_admin,", text)
|
||||||
|
# bwrap 还需要 netlink(在新网络命名空间里拉起 loopback;
|
||||||
|
# 缺了会报 Failed to bind NETLINK_ROUTE socket,且任何
|
||||||
|
# 工具都跑不起来)。注意 AppArmor 里 deny 优先于 allow,
|
||||||
|
# 所以 raw 的 deny 必须写明 inet/inet6,不能写裸的
|
||||||
|
# deny network raw(那样会连 netlink raw 一起挡掉,
|
||||||
|
# 已用隔离 netlink bind 实测验证)。
|
||||||
|
self.assertIn("network netlink raw,", text)
|
||||||
|
self.assertIn("deny network inet raw,", text)
|
||||||
|
self.assertNotIn("deny network raw,", text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+24
-22
@@ -38,29 +38,31 @@ profile codex-host flags=(attach_disconnected,mediate_deleted) {
|
|||||||
network inet dgram,
|
network inet dgram,
|
||||||
network inet6 stream,
|
network inet6 stream,
|
||||||
network inet6 dgram,
|
network inet6 dgram,
|
||||||
deny network raw,
|
# bwrap brings up loopback in its new network namespace via a
|
||||||
|
# NETLINK_ROUTE socket (verified 2026-10-26: without this,
|
||||||
|
# codex workspace-write fails with "bwrap: loopback: Failed
|
||||||
|
# to bind NETLINK_ROUTE socket: Permission denied" and no tool
|
||||||
|
# runs at all). CAREFUL: a bare "deny network raw" would
|
||||||
|
# override this allow (deny wins over allow in AppArmor) and
|
||||||
|
# break netlink too -- verified by isolated netlink bind test.
|
||||||
|
# So the raw deny must name inet/inet6 explicitly, leaving
|
||||||
|
# netlink raw allowed.
|
||||||
|
network netlink raw,
|
||||||
|
deny network inet raw,
|
||||||
|
deny network inet6 raw,
|
||||||
|
|
||||||
capability chown,
|
# Capabilities: bwrap NEEDS userns-scoped capabilities (verified
|
||||||
capability dac_override,
|
# 2026-10-26: codex-host with deny capability sys_admin etc.
|
||||||
capability fsetid,
|
# made bwrap fail at userns creation, while the same profile
|
||||||
capability fowner,
|
# with capability, allowed it). This is safe for an unprivileged
|
||||||
capability mknod,
|
# user: codexsvc holds NO host capabilities, so the only caps
|
||||||
capability net_bind_service,
|
# this allows are fake-root caps inside bwrap's own user
|
||||||
capability setfcap,
|
# namespace (scoped to that namespace, harmless to the host).
|
||||||
capability setgid,
|
# The profile's other denies still hold independently: raw
|
||||||
capability setpcap,
|
# network is gated by network rules, ptrace by ptrace rules,
|
||||||
capability setuid,
|
# /proc/sys writes by file rules -- allowing caps does not
|
||||||
capability sys_chroot,
|
# void any of them.
|
||||||
deny capability sys_admin,
|
capability,
|
||||||
deny capability sys_ptrace,
|
|
||||||
deny capability net_admin,
|
|
||||||
deny capability net_raw,
|
|
||||||
deny capability sys_module,
|
|
||||||
deny capability sys_rawio,
|
|
||||||
deny capability sys_boot,
|
|
||||||
deny capability sys_time,
|
|
||||||
deny capability mac_admin,
|
|
||||||
deny capability mac_override,
|
|
||||||
|
|
||||||
# bwrap needs these (docker-codex denies them).
|
# bwrap needs these (docker-codex denies them).
|
||||||
userns,
|
userns,
|
||||||
|
|||||||
Reference in new issue
Block a user