diff --git a/cloud/README.md b/cloud/README.md index ebd445b..289a8ed 100644 --- a/cloud/README.md +++ b/cloud/README.md @@ -514,16 +514,22 @@ interrupted 且终态任务 cancel 返回 400、心跳后 sweep 不扫刚心跳 `host-config-host.json`(sandbox workspace-write,state 独立 为 work/host-state-host.json),以 `aa-exec -p codex-host` 运行(见 `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 出口 同样走 Tinyproxy 白名单。路由靠任务的 executor 字段 (POST /tasks 已支持 local/docker/cloud):宿主 worker 必须 开 `--claim-executor-only`,只认领 executor='local' 的任务, 绝不抢 executor 为 NULL 的任务(否则会把本该进 Docker 的 任务拉到宿主上跑,丢了 Docker 外层)。宿主任务放弃 Docker - 外层(无容器、无租户挂载/网络隔离),换来的是 bwrap 内层 - (agent 只能写自己 worktree,/tmp 是隔离的 tmpfs,写不到 - 宿主 /tmp;其它任务 worktree 与宿主文件同样不可见)。 + 外层(无容器、无租户挂载/网络隔离),换来的是 bwrap 内层。 + 实测过的沙箱边界(诚实版):agent 只能写自己 worktree, + 写宿主其它目录与其它任务 worktree 均被挡(报文件系统只读); + 但 /tmp 不隔离——agent 写 /tmp 会落到宿主 /tmp 上(codex + 的 workspace-write 不把 /tmp 做成隔离 tmpfs),所以 /tmp + 是任务间的共享区,不能当作隔离边界。 - 网络白名单的诚实边界:docker run 默认 bridge 网络,本执行 器不做 egress 白名单——真正的“只放行模型 API”需要宿主防火 墙或代理规则,那是部署项,不是本轮代码假装做到的,不要宣称 diff --git a/cloud/tests/test_host_sandbox_routing.py b/cloud/tests/test_host_sandbox_routing.py index 5b913bb..ec31fd6 100644 --- a/cloud/tests/test_host_sandbox_routing.py +++ b/cloud/tests/test_host_sandbox_routing.py @@ -93,6 +93,22 @@ class HostConfigProfileTestCase(unittest.TestCase): self.assertIn("pivot_root,", text) self.assertNotIn("deny mount,", 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__": diff --git a/deploy/apparmor/codex-host b/deploy/apparmor/codex-host index cfc1c92..b7ce99b 100644 --- a/deploy/apparmor/codex-host +++ b/deploy/apparmor/codex-host @@ -38,29 +38,31 @@ profile codex-host flags=(attach_disconnected,mediate_deleted) { network inet dgram, network inet6 stream, 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, - capability dac_override, - capability fsetid, - capability fowner, - capability mknod, - capability net_bind_service, - capability setfcap, - capability setgid, - capability setpcap, - capability setuid, - capability sys_chroot, - deny capability sys_admin, - 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, + # Capabilities: bwrap NEEDS userns-scoped capabilities (verified + # 2026-10-26: codex-host with deny capability sys_admin etc. + # made bwrap fail at userns creation, while the same profile + # with capability, allowed it). This is safe for an unprivileged + # user: codexsvc holds NO host capabilities, so the only caps + # this allows are fake-root caps inside bwrap's own user + # namespace (scoped to that namespace, harmless to the host). + # The profile's other denies still hold independently: raw + # network is gated by network rules, ptrace by ptrace rules, + # /proc/sys writes by file rules -- allowing caps does not + # void any of them. + capability, # bwrap needs these (docker-codex denies them). userns,