feat: 沙箱规模——/tmp 顺序加严、注册表寻址与失联精确清理、超时边界抽取

- Docker /tmp tmpfs 移到全部 -v 之前(父挂载在前,seed 子挂载在后);
  宿主级 bwrap /tmp 仍宣告未隔离,口径见 tmp_isolation_status()
- workers 表补 host/pid/address(老库幂等补列),心跳透传与保留语义,
  _claim 写 claimed_by_worker;orphaned_containers 精确识别失联容器,
  Worker.cleanup_orphaned_containers 默认只杀 pid_verified 的精确容器名
- docker_run_timeout() 纯函数抽取(行为与原内联一致),README 补边界与
  未验证清单;新增 test_sandbox_scale.py 20 项
This commit is contained in:
host committed 2026-11-03 02:49:25 +00:00
1 parent 143ab30094
commit 06710cf47b
7 files changed
+897 -48

No files matched your search

+49 -12
View File
@@ -41,6 +41,33 @@ from cloud.marker_tools import extract_wait_threads as _extract_wait_threads
from cloud.peer_messages import extract_peer_messages as _extract_peer_messages
MAX_STDOUT_CHARS = 2000
def tmp_isolation_status():
"""各层 /tmp 隔离现状的唯一口径(README 与单测都以它为准)。
返回 {docker: bool, host_bwrap: bool, ...}:
- docker=True:DockerExecutor 给每个任务容器挂 --tmpfs /tmp
与 --tmpfs /var/tmp(容器随 --rm 消失,/tmp 也随之消失,
绝不落宿主 /tmp;见 build_run_command 的顺序注释)。
- host_bwrap=False:宿主级 codex workspace-write(bwrap)
的挂载由 codex 二进制内部决定,host.py 只传
sandboxPolicy 类型、没有任何挂载旋钮,所以本仓库改不
了它;2026-10-26 宿主实测 agent 写 /tmp 落到宿主 /tmp,
本轮未在宿主重测、也不宣称已修复。需要 /tmp 隔离的
任务请走 docker 执行器,不要走宿主级沙箱 worker。
本函数是状态宣告,不是修复开关:调用方绝不能拿它的
docker=True 去给宿主级任务背书。
"""
return {
"docker": True,
"host_bwrap": False,
"docker_detail": "per-container tmpfs /tmp + /var/tmp, removed with --rm",
"host_bwrap_detail": "bwrap mounts are owned by the codex binary;"
" host.py passes only a sandboxPolicy type"
" and has no mount knob; /tmp is shared"
" with the host (2026-10-26 host test)",
}
# 任务结果文本上限:2000 字截断的是 host.py 打印的整段 JSON,
# text 超过 2000 字时 JSON 本身被截断,worker 再解析 stdout
# 取 text 必然失败,存库的就变成截断的 JSON(2026-10-30
@@ -1111,23 +1138,33 @@ class DockerExecutor(Executor):
cmd += ["--runtime", self.runtime]
if network:
cmd += ["--network", network]
cmd += ["-v", "%s:/work" % self._worktree_for(task),
"-v", "%s:/app:ro" % self._app_dir(),
"-w", "/work"]
# 额外挂载放在基础挂载之后:/app/work 这类子路径挂载会
# 覆盖 /app:ro 的对应子路径,让容器内 host.py 能写
# work/ 和 host-state.json,同时代码主体仍只读。
mounts = self.extra_mounts if extra_mounts is None \
else self._parse_extra_mounts(extra_mounts)
for host_p, cont_p, mode in mounts:
cmd += ["-v", "%s:%s:%s" % (host_p, cont_p, mode)]
# /tmp 固定为 tmpfs:容器内 /tmp 绝不落在宿主 /tmp,也不受
# 额外挂载影响(/tmp 目标的额外挂载已在解析时拒绝)。
# /tmp 固定为 tmpfs,且必须放在所有 -v 挂载之前:父挂载
# 在前、子挂载在后是 Linux 挂载顺序的基本要求——附件
# seed 以 /tmp/codex-seed:ro 挂进容器(见 dispatch_task),
# 它是 /tmp 之下的子挂载,只有 tmpfs 先挂、seed 后挂,
# seed 才不会被后挂的 tmpfs 遮掉。旧顺序把 --tmpfs 放在
# 全部 -v 之后,本 VM 无 docker、未在真实 docker 上证实
# 旧顺序确实遮挡(docker 可能按父子关系重排),所以这里
# 写为顺序加严而非“已证实 bug 修复”;单测只断言顺序,
# 不宣称旧顺序一定失效。
# 容器内 /tmp 绝不落在宿主 /tmp,也不受额外挂载影响
# (/tmp 目标的额外挂载已在解析时拒绝)。
# 诚实边界:这是 Docker 外层能做的;内层 codex 的 bwrap
# workspace-write 仍由 codex 二进制控制,本仓库改不了它
# 的 --proc 挂载,宿主级 bwrap 的 /tmp 不隔离问题也不在
# 本仓库的修复范围内——不要把本改动说成“内层沙箱已修”。
cmd += ["--tmpfs", "/tmp", "--tmpfs", "/var/tmp"]
cmd += ["-v", "%s:/work" % self._worktree_for(task),
"-v", "%s:/app:ro" % self._app_dir(),
"-w", "/work"]
# 额外挂载放在基础挂载之后:/app/work 这类子路径挂载会
# 覆盖 /app:ro 的对应子路径,让容器内 host.py 能写
# work/ 和 host-state.json,同时代码主体仍只读;seed 的
# /tmp/codex-seed:ro 同样在此后挂载,压在 tmpfs 之上。
mounts = self.extra_mounts if extra_mounts is None \
else self._parse_extra_mounts(extra_mounts)
for host_p, cont_p, mode in mounts:
cmd += ["-v", "%s:%s:%s" % (host_p, cont_p, mode)]
if self.env_key:
cmd += ["-e", self.env_key]
merged_env = dict(self.extra_env)