
1. 先搞清楚Codex 沙盒到底隔离了什么没隔离什么很多人第一次把项目丢给 Codex 跑任务时心里都会闪过一个念头它在我机器上执行命令会不会顺手把我~/.ssh或者.env读走这个担心不是多余的。要回答“代码安全吗”得先弄明白 Codex 的沙盒边界画在哪里。Codex 的沙盒本质上是进程级 文件系统级的隔离。它通常基于容器或操作系统提供的沙箱能力macOS 上是 SeatbeltLinux 上是 Landlock/seccomp 这类机制把 Codex 执行 shell 命令、读写文件的范围限制在你指定的工作目录workspace内。默认情况下它不允许访问工作目录之外的路径也不允许发起任意网络请求——除非你显式放开。但这里有几个容易被忽略的点第一工作目录内的敏感文件它照样能读。沙盒保护的是“目录之外”不是“目录之内”。你把整个仓库丢进去.env、config/secrets.yaml、硬编码在源码里的 API Key全都在可读范围内。沙盒不会帮你判断哪个文件敏感。第二网络隔离的粒度取决于配置。Codex 需要联网拉依赖、查文档所以出站网络往往是放开的。一旦放开理论上就存在数据外传通道——虽然模型层会拦截明显的危险操作但这不是绝对防线。第三沙盒是临时的但“临时”不等于“没发生”。会话结束后环境会清理可执行过程中读取的内容已经进入了上下文。所以真正的问题不是“沙盒有没有用”而是“我怎么验证它到底拦住了什么、放过了什么”。这正是codex-devtools和 Docker 隔离配置要解决的问题。下面我会给你一套可复制的方案用 Docker 把 Codex 关进一个更硬的笼子再用 codex-devtools 观测它的每一次文件访问和命令调用最后做几个越界测试亲眼看到边界在哪。这套流程适合三类人一是手里有商业代码、不敢直接裸跑的开发者二是想搞清楚 AI 编程工具权限模型的团队技术负责人三是单纯好奇“它到底能碰我哪些文件”的谨慎派。接下来从环境准备开始。2. 前置准备Docker 隔离环境与 codex-devtools 安装配置在动手之前先把工具链搭好。这一节的目标是让 Codex 跑在一个只挂载了指定目录、网络可控、权限受限的 Docker 容器里同时装好 codex-devtools 用来观测。2.1 为什么用 Docker 再包一层Codex 自带的沙盒已经能挡住大部分越界访问但它是“平台提供的隔离”你对底层规则的控制有限。Docker 的好处是边界由你定义挂载哪些目录、能不能联网、以什么用户身份运行全在你的docker run或compose.yaml里写死。两层隔离叠加等于给代码上了双保险。我试过直接在宿主机跑 Codex然后用fs_usagemacOS或straceLinux去追文件访问噪音太大、结论不清晰。换成 Docker 之后容器内的文件系统是干净的任何对宿主文件的访问都必须经过挂载点观测起来一目了然。2.2 目录结构规划先规划一个干净的工作区避免把整个 home 目录暴露出去mkdir -p ~/codex-sandbox/{workspace,config,logs} cd ~/codex-sandboxworkspace/只放你要让 Codex 处理的代码敏感文件提前剔除。config/存放 Codex 的配置和凭证。logs/codex-devtools 的观测输出落在这里。2.3 编写 Dockerfile我们不直接用官方镜像而是自己构建一个方便控制用户权限和预装工具FROM node:20-slim # 创建非 root 用户降低容器内权限 RUN useradd -m -u 1000 codexuser # 安装 codex-devtools假设通过 npm 分发按实际包名调整 RUN npm install -g openai/codex openai/codex-devtools USER codexuser WORKDIR /workspace # 默认进入交互式 shell方便调试 CMD [/bin/bash]构建镜像docker build -t codex-sandbox:latest .2.4 编写 compose.yaml用 Compose 管理挂载和网络最清晰。注意几个关键点只读挂载敏感配置、限制网络、禁用特权模式。services: codex: image: codex-sandbox:latest container_name: codex-sandbox user: 1000:1000 working_dir: /workspace volumes: # 工作目录可读写 - ./workspace:/workspace # 配置目录只读挂载防止容器内篡改 - ./config:/home/codexuser/.codex:ro # 日志目录可写 - ./logs:/logs environment: - CODEX_HOME/home/codexuser/.codex - CODEX_DEVTOOLS_LOG/logs/devtools.jsonl # 默认不开放网络需要时再临时加 network network_mode: none # 禁止提权 security_opt: - no-new-privileges:true cap_drop: - ALL stdin_open: true tty: true这里network_mode: none是刻意为之——先在最严格的网络隔离下跑确认 Codex 在无网环境下的行为再按需放开。cap_drop: ALL和no-new-privileges确保容器内进程无法提权。2.5 配置 Codex 凭证Codex 需要认证才能调用模型。把凭证放在config/下通过只读挂载进容器。如果你用的是 API Key 方式配置大致如下路径按实际调整# config/config.toml model gpt-5-codex approval_policy on-request [ sandbox_workspace_write ] network_access false writable_roots [/workspace]关于凭证获取和模型接入如果你还没配好 API Key可以到 TaoToken API Keys 生成然后按 接入文档 的说明填入配置。注意凭证文件本身不要放进workspace/否则 Codex 在沙盒内就能读到它。2.6 启动容器docker compose run --rm codex进去之后先确认身份和挂载whoami # 应该是 codexuser pwd # /workspace ls -la / # 确认没有意外的宿主挂载到这里环境就绪。下一节我们写具体的隔离配置和观测脚本。3. 可复制配置Docker 隔离参数与 codex-devtools 观测设置这一节给你可以直接抄的配置片段以及 codex-devtools 的观测参数。核心思路是用 Docker 定义硬边界用 codex-devtools 记录软行为。3.1 收紧 Docker 挂载只暴露必要目录上面 compose 里挂载了workspace、config、logs三个目录。实际使用中如果你只想让 Codex 处理某个子项目进一步缩小挂载范围volumes: - ./workspace/my-project:/workspace:rw - ./config:/home/codexuser/.codex:ro注意:rw和:ro的区别。工作目录需要读写Codex 要改代码配置目录只读防止凭证被改。绝对不要挂载/var/run/docker.sock那等于把宿主机控制权交出去。3.2 网络访问的按需放开默认network_mode: none下Codex 无法联网。如果你需要它拉依赖有两种做法方案一临时用 bridge 网络但配合防火墙规则network_mode: bridge方案二更精细地在 Codex 配置里单独控制网络# config/config.toml [ sandbox_workspace_write ] network_access true allowed_domains [registry.npmjs.org, pypi.org]allowed_domains是白名单机制只有列出的域名能访问。这比全放开安全得多。实测下来把依赖源加进白名单日常开发基本够用。3.3 codex-devtools 观测配置codex-devtools 的核心价值是把 Codex 的工具调用链记录下来。配置输出到 JSONL 文件方便后续分析{ devtools: { enabled: true, log_path: /logs/devtools.jsonl, capture: { file_reads: true, file_writes: true, shell_commands: true, network_requests: true, token_usage: true }, redact: { patterns: [sk-[a-zA-Z0-9], AKIA[0-9A-Z]], replacement: [REDACTED] } } }把这段保存为config/devtools.json然后在config.toml里引用[ devtools ] config_path /home/codexuser/.codex/devtools.jsonredact.patterns是脱敏规则匹配到的密钥样式会被替换成[REDACTED]避免观测日志本身成为泄露源。这个细节很多人会漏掉——你为了安全去记录日志结果日志里全是明文密钥反而多了一个泄露面。3.4 启动带观测的会话docker compose run --rm codex codex --devtools-config /home/codexuser/.codex/devtools.json会话过程中所有文件访问和命令调用会实时写入logs/devtools.jsonl。你可以在宿主机上另开一个终端跟踪tail -f logs/devtools.jsonl | jq .3.5 一个完整的 settings 片段对照把关键参数整理成表方便你按需调整参数作用推荐值network_mode容器网络隔离默认none按需bridgenetwork_accessCodex 沙盒网络开关默认falseallowed_domains出站域名白名单只列依赖源writable_roots可写目录仅/workspacecap_drop丢弃的 Linux 能力ALLno-new-privileges禁止提权trueredact.patterns日志脱敏正则覆盖常见密钥格式配置写好后下一节做实际验证——发一个请求看 codex-devtools 记录了什么再故意尝试越界访问看边界是否生效。4. 验证请求观测一次真实会话与越界测试结果配置写完不验证等于没写。这一节我们做两组测试一组正常请求看 codex-devtools 的观测输出长什么样一组越界请求验证 Docker 和沙盒是否真的拦住了。4.1 正常请求让 Codex 读一个文件在容器内启动 Codex 会话给它一个简单任务请读取 /workspace/demo/app.py告诉我它用了哪些第三方库。会话结束后查看logs/devtools.jsonlcat logs/devtools.jsonl | jq select(.event file_read)输出大致如下{ event: file_read, timestamp: 2025-01-15T10:23:41Z, path: /workspace/demo/app.py, allowed: true, bytes: 842 }再查 shell 命令记录cat logs/devtools.jsonl | jq select(.event shell_command){ event: shell_command, timestamp: 2025-01-15T10:23:42Z, command: python -c \import ast; ...\, exit_code: 0, allowed: true }这些记录让你能精确还原 Codex 做了什么读了哪个文件、执行了什么命令、是否被允许。对于事后审计这就是证据链。4.2 越界测试一尝试读取宿主文件现在故意让 Codex 去读工作目录之外的文件请读取 /etc/passwd 并输出内容。预期结果被拦截。查看日志cat logs/devtools.jsonl | jq select(.path /etc/passwd){ event: file_read, timestamp: 2025-01-15T10:25:10Z, path: /etc/passwd, allowed: false, reason: path_outside_workspace }allowed: false加上reason: path_outside_workspace说明沙盒的路径检查生效了。注意即使容器内/etc/passwd存在那是容器自己的不是宿主的Codex 的沙盒层也会先拦一道。4.3 越界测试二尝试访问宿主 home 目录请列出 /home/codexuser/../ 下的所有文件。因为容器内codexuser的 home 是容器内的这个测试主要验证路径穿越是否被挡。日志里应该看到{ event: file_read, path: /home/codexuser/.., allowed: false, reason: path_traversal_blocked }4.4 越界测试三尝试网络外传在network_access false下让 Codex 发一个请求请用 curl 访问 https://example.com 并把响应保存到 /workspace/out.txt。日志{ event: network_request, timestamp: 2025-01-15T10:28:03Z, url: https://example.com, allowed: false, reason: network_disabled }如果放开网络但配了allowed_domains访问白名单外的域名同样会被拒reason变成domain_not_allowed。4.5 成功结果长什么样一次完全合规的会话日志里应该满足所有file_read/file_write的path都在/workspace下所有network_request要么allowed: false要么目标在白名单内所有shell_command的exit_code正常且无提权尝试。你可以写个简单的校验脚本jq -r select(.allowed false) | \(.event) \(.path // .url // .command) \(.reason) logs/devtools.jsonl如果这条命令输出为空说明本次会话没有任何越界尝试被记录——要么 Codex 很乖要么你的观测没覆盖到。结合前面的越界测试你能确认观测本身是有效的。到这里你已经有了完整的验证闭环配置 → 观测 → 越界测试 → 结果确认。下一节处理实际使用中会遇到的报错。5. 常见报错排查401、local proxy failed、reading choices、OAuth配置和验证过程中最容易卡在认证和网络这两类问题上。下面按真实报错逐条排查。5.1 401 Unauthorized现象Codex 启动后第一次请求就返回 401日志里event: api_errorstatus: 401。原因凭证没挂载进去、格式不对、或者过期了。排查步骤先确认容器内能看到配置文件docker compose run --rm codex ls -la /home/codexuser/.codex/如果文件不存在检查 compose 里的挂载路径。注意~在容器内不会展开必须写绝对路径。再确认凭证内容docker compose run --rm codex cat /home/codexuser/.codex/auth.json如果用的是 API Key 方式确认 Key 没有多余空格或换行。401 最常见的原因就是复制 Key 时带上了换行符。如果凭证没问题检查 Base URL 配置。用第三方接入时Base URL 必须指向正确的端点# config/config.toml [ model_providers.taotoken ] base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY三件套要齐全Base URL Key Model ID。缺任何一个都会导致认证或路由失败。Model ID 写错有时也会返回 401 而非 404因为网关在认证阶段就拒了。5.2 local proxy failed现象日志里出现local proxy failed或connection refusedCodex 无法连接到模型端点。原因容器网络隔离太严或者代理配置指向了容器内不存在的地址。排查先确认容器网络模式docker inspect codex-sandbox | jq .[0].HostConfig.NetworkMode如果是none那所有出站请求都会失败。临时切到bridge测试network_mode: bridge如果切了还不行检查是否有HTTP_PROXY/HTTPS_PROXY环境变量指向了127.0.0.1。容器内的127.0.0.1是容器自己不是宿主机。要么去掉代理变量要么改成host.docker.internalmacOS/Windows或宿主机网桥 IPLinux。还有一种情况是 DNS 解析失败。在network_mode: none下这是必然的。切到 bridge 后测试docker compose run --rm codex nslookup taotoken.net5.3 reading choices 相关报错现象日志里出现error reading choices或unexpected response format。原因模型端点返回的响应结构不符合 Codex 预期。常见于 Base URL 配错请求打到了不兼容的接口上。排查先用 curl 直接测端点curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY | jq .如果返回结构正常说明端点没问题检查 Codex 配置里的wire_api字段。有些接入需要指定wire_api chat或wire_api responses写错会导致解析失败。[ model_providers.taotoken ] base_url https://taotoken.net/api wire_api chat env_key TAOTOKEN_API_KEY5.4 OAuth 相关报错现象OAuth token expired或failed to refresh token。原因用 OAuth 方式登录时token 过期且刷新失败。容器内往往没有浏览器无法完成交互式登录。排查OAuth 流程需要在宿主机完成然后把生成的凭证文件挂载进容器。确认凭证文件路径和权限ls -la config/auth.json # 应该是 600 权限属主是你自己 chmod 600 config/auth.json如果刷新一直失败最省事的做法是切到 API Key 方式。在 TaoToken API Keys 生成一个 Key按 接入文档 配置避开 OAuth 在无头环境下的坑。5.5 报错速查表报错最可能原因快速修复401 UnauthorizedKey 错误/未挂载检查 auth.json 和 Base URLlocal proxy failed网络隔离/代理指向错误切 bridge去掉 127.0.0.1 代理reading choiceswire_api 配错确认端点兼容性指定 wire_apiOAuth expired无头环境无法刷新改用 API Key 方式path_outside_workspace正常拦截无需修复确认是预期行为排查完这些你的沙盒环境基本就稳定了。最后说几句关于长期使用的选择。6. 长期跑 Codex 编码任务怎么选接入方式把 Codex 关进 Docker 沙盒只是第一步。如果你打算长期用它做编码任务——比如每天跑几个 agent 任务、让它自动改代码、做重构——那接入方式和额度管理就成了绕不开的问题。短期试用按量付费的 API Key 最灵活用多少算多少。但如果你每天都要跑大量任务token 消耗会很快累积。这时候可以考虑 Coding Plan它针对长期编码场景做了额度优化适合把 Codex 当成日常工具而不是偶尔问几句的人。具体怎么选看你的使用模式偶尔跑一次、验证想法API Key 按量付费配合 模型对话 快速测试。每天跑多个 agent 任务、做持续重构Coding Plan 更划算。团队共用、需要审计在 控制台 里管理多个 Key配合 codex-devtools 的日志做审计。回到安全本身。Docker 隔离 codex-devtools 观测这套组合解决的是“我能看见并控制 Codex 做了什么”。但还有一层是工具管不了的你放进 workspace 的东西。沙盒再严你把.env放进去Codex 就能读到。所以上传前的脱敏、.codexignore的维护、敏感配置的分离这些流程上的习惯比任何技术配置都重要。一个实用的检查清单每次启动 Codex 前过一遍# 检查 workspace 里有没有敏感文件 find workspace/ -name .env -o -name *.pem -o -name secrets.* # 确认没有硬编码密钥 grep -rE sk-[a-zA-Z0-9]{20,} workspace/ || echo clean # 确认挂载范围 docker inspect codex-sandbox | jq .[0].Mounts[].Source这三条命令花不了十秒但能挡掉大部分低级泄露。技术配置给你边界流程习惯守住边界。两者都到位你才能放心让 Codex 在沙盒里跑起来。