Codex Sandbox 安全模型:让 Agent 放手做事但不失控
一个真实的失控场景
上个月我在 monorepo 里跑 Codex,让它帮忙修一个前端组件的类型错误。任务很小——改一个 props 接口定义,顺手跑一下 typecheck 确认没问题。它确实修好了,但当我翻 git diff 的时候,发现它还「顺手」改了三个毫不相关的包:一个内部 CLI 工具的参数解析、一个 shared-ui 组件的导出方式、以及一个测试 fixture 的数据结构。
不是幻觉。Codex 在执行 typecheck 的过程中发现了那些文件的类型不匹配,然后自作主张地一并修了。在一个没有沙箱边界约束的环境下,Agent 的行为逻辑是「我看到了问题,我就修」。这个逻辑对人类开发者来说都过于激进,放在 AI 身上就更危险了。
这件事之后我花了一些时间系统梳理 Codex 的安全模型。这篇文章是我对这套模型的理解,包括它的隔离原理、权限边界、审批策略,以及在实践中怎么配置才不会被 Agent 的「好意」坑到。
沙箱的理论基础:为什么是 OS 级隔离
AI 编程代理和传统 IDE 插件有一个根本区别:它不只是建议你改什么,它会自己去改。读文件、写文件、装依赖、跑命令、启服务——每一个动作都是真实的系统调用。传统的编辑器插件安全模型建立在「只生成文本」的假设上,这个假设对 Agent 不成立。
OpenAI 为 Codex 选择的方案是 OS 级沙箱隔离——直接在操作系统内核层面限制 Agent 能做什么,而不是在应用层靠提示词约束(OpenAI, Codex Sandbox Documentation, 2026)。这个选择背后的逻辑很直接:提示词可以被绕过,系统调用不行。
在隔离技术的光谱上,业界有几种主流方案(Northflank, How to Sandbox AI Agents, 2026):
| 隔离技术 | 隔离层级 | 启动开销 | 安全强度 | 适用场景 |
|---|---|---|---|---|
| Docker 容器 | 进程级(共享内核) | 毫秒 | 中 | 可信内部工作负载 |
| gVisor | 系统调用拦截 | 毫秒 | 高 | 多租户 SaaS、CI/CD |
| Firecracker MicroVM | 硬件级(独立内核) | ~125ms | 极高 | 不可信代码、Serverless |
| OS 原生沙箱 | 内核策略 | 近乎零 | 高 | 本地开发代理 |
Codex 选择的是第四种——利用各操作系统的原生沙箱机制。macOS 用 Seatbelt(sandbox-exec),Linux 用 Bubblewrap + seccomp,Windows 用基于 SID 和 write-restricted token 的自研沙箱(Humphrey, OpenAI Codex Windows Sandbox Engineering Deep Dive, 2026)。好处是几乎没有启动延迟,安全约束由内核保证,不依赖额外的虚拟化基础设施。
核心原则是 deny-by-default:Agent 默认不能做任何事情,只能在你明确允许的范围内行动。这跟传统软件的权限模型正好相反——传统软件默认什么都能做,然后靠白名单限制特定行为。
Codex 的四层安全边界
Codex 的安全模型可以拆成四层:文件边界、命令边界、网络边界和审批边界。每一层独立配置,但协同工作。
文件边界:Agent 能看哪里、改哪里
文件系统的权限控制是最基础的一层。Codex 默认使用 workspace-write 模式:Agent 可以自由读写当前工作区(也就是你提供的项目目录),但工作区之外的文件系统对它完全不可见。
这个设计针对的就是我在开头提到的问题——Agent 不应该因为「看到了」就去改不该碰的文件。
# ✅ 正确做法:限定工作区写入范围
default_permissions = "project-edit"
[permissions.project-edit]
extends = ":workspace"
[permissions.project-edit.filesystem.":workspace_roots"]
"." = "write"
"docs" = "read"
"generated" = "deny"
"**/*.env" = "deny"# ❌ 错误做法:直接给 full-access
sandbox_mode = "danger-full-access"
approval_policy = "never"
# 后果:Agent 可以读写机器上任何文件,包括 ~/.ssh、~/.aws、系统配置差异的关键在于:正确的配置用 extends = ":workspace" 继承了内置的安全基线,然后用精细规则进一步收窄;错误做法直接拆掉了所有围栏。
还有一个容易被忽略的细节:即使在可写目录内,.git、.agents、.codex 这些路径也被强制设为只读。这是有意为之——防止 Agent 在修 bug 的过程中不小心改了 Git 历史或者自己的配置。
命令边界:哪些命令可以跑、哪些要审批
Agent 的核心能力是执行命令。测试、构建、搜索这些操作风险低;删除、重置、部署、修改系统配置这些操作风险高。Codex 通过审批策略来区分这两类命令。
# ✅ 正确做法:对未知命令要求审批
approval_policy = "untrusted"
sandbox_mode = "workspace-write"
# 效果:已知安全的只读命令自动放行,状态变更类命令需要人工确认# ❌ 错误做法:所有命令自动通过
approval_policy = "never"
# 后果:Agent 可以自由执行 rm -rf、git push --force、数据库迁移等破坏性命令Codex 的审批策略有三个档位:
| 审批策略 | 行为 | 适用场景 |
|---|---|---|
on-request | 超出沙箱边界时询问用户 | 日常开发(默认) |
untrusted | 已知安全的只读命令自动放行,其余需审批 | 半自动 CI 任务 |
never | 永不询问 | 只读分析 / 受控 CI |
untrusted 是一个值得关注的档位——它不是无脑拦截所有命令,而是维护了一个「已知安全」的命令集合。typecheck、lint、单元测试这些只读操作会自动放行,但 rm、git reset --hard、npm publish 这类会改变状态的命令需要人工确认。这种设计在安全性和效率之间找到了一个合理的平衡点。
网络边界:最容易被忽视的高风险开关
默认情况下,Codex 的网络访问是关闭的。这个默认值非常重要——它意味着 Agent 无法把你的代码、日志、密钥发送到任何外部地址。
# ✅ 正确做法:按需开放网络并限定域名
[permissions.project-edit.network]
enabled = true
[permissions.project-edit.network.domains]
"api.openai.com" = "allow"
"registry.npmjs.org" = "allow"
"*.github.com" = "allow"
"tracking.example.com" = "deny"
# deny 永远优先于 allow,即使其他规则允许# ❌ 错误做法:全局开放网络
[permissions.project-edit.network]
enabled = true
[permissions.project-edit.network.domains]
"*" = "allow"
# 后果:Agent 可以将工作区内容发送到任意外部地址网络边界有几个值得注意的技术细节:
- 本地地址默认阻止。即使你开放了网络,Agent 也访问不了
localhost和私有网段。这是为了防止 DNS rebinding 攻击——如果某个被 allow 的域名在 DNS 层面被解析到内网地址,Agent 不应该能触达内网服务。 - DNS 层面的防护。Codex 会做 best-effort 的 DNS/IP 分类检查,防止通过 DNS 重绑定绕过域名白名单。
- Unix Socket 单独管控。Docker socket 这类本地通信走 Unix Socket 路径,需要单独配置 allowlist。
审批边界:关键决策还给人
审批不是拖慢 Agent,而是在 Agent 即将做出超出预期的动作时,给人类一个介入的机会。
Codex 还提供了一个有意思的功能:auto-review 模式。设置 approvals_reviewer = "auto_review" 后,审批请求会先经过一个 reviewer agent 审查,由它判断风险等级再决定是否放行。
# ✅ Auto-review 模式
approval_policy = "on-request"
approvals_reviewer = "auto_review"
# reviewer agent 会检查:数据泄露、凭据探测、持久性安全削弱、破坏性操作
# 风险等级分为 Low / Medium / High / Critical
# Critical 级别永远拒绝,High 级别需要足够的用户授权# ❌ 完全跳过审批
# --dangerously-bypass-approvals-and-sandbox(别名 --yolo)
# 后果:没有沙箱、没有审批,Agent 拥有与当前用户相同的系统权限auto-review 的风险评估逻辑是开源的,默认策略可以在 codex-rs/core/src/guardian/policy.md 看到。企业可以通过 guardian_policy_config 覆盖默认策略。
沙箱决策流程
用一个流程图来总结 Codex 在收到一个操作请求时的决策路径:
这个流程图反映的是 Codex 默认配置(workspace-write + on-request)下的行为。如果使用 danger-full-access 模式,所有这些检查都会被跳过——这也是官方把它标记为「Elevated Risk」的原因。
平台差异:三套沙箱实现
Codex 需要在三个操作系统上提供一致的安全体验,但底层机制完全不同:
| 平台 | 沙箱技术 | 实现方式 | 注意事项 |
|---|---|---|---|
| macOS | Seatbelt | 系统内置 sandbox-exec,开箱即用 | 策略模板参数化可写根目录 |
| Linux/WSL | Bubblewrap + seccomp | 需要安装 bwrap,支持 Landlock 降级 | Ubuntu 24.04 需额外 AppArmor 配置 |
| Windows (原生) | SID + write-restricted token | 专用低权限沙箱用户 + 文件系统权限边界 + 防火墙规则 | 最强模式需要管理员权限创建沙箱用户 |
Windows 的实现最值得展开。OpenAI 工程团队评估了三种方案后全部否决(Humphrey, 2026):
- AppContainer:设计用于 UWP 应用商店分发,文件系统模型粒度不够
- Windows Sandbox:完整的 VM 环境,启动延迟不可接受
- Mandatory Integrity Control:提供权限分层但不是真正的隔离,低完整性进程仍然能读用户可读的文件
最终选择的方案是为每次 Agent 执行创建一个专用的低权限 SID(Security Identifier),配合 write-restricted token 把文件写入限制在项目目录内。这个方案在 Windows 内核原语层面实现了隔离,不需要虚拟化,启动速度接近即时。
三个实践案例
案例一:Monorepo 里的越界修改
场景:一个包含 12 个包的 pnpm monorepo,让 Codex 修改 packages/ui 里一个按钮组件的样式。
翻车:Codex 在 typecheck 过程中发现 packages/cli 和 packages/core 也有类型错误,一并修了。git diff 显示改了 7 个文件,涉及 3 个不同的包。
修复:配置权限 profile,把可写范围限定在当前任务相关的包。
# ✅ 限定工作区根目录到具体包
default_permissions = "ui-fix"
[permissions.ui-fix.workspace_roots]
"~/code/monorepo/packages/ui" = true
[permissions.ui-fix.filesystem.":workspace_roots"]
"." = "write"
# 其他包只能读,不能写
[permissions.ui-fix.filesystem]
"~/code/monorepo/packages" = "read"# ❌ 把整个 monorepo 设为可写根
[permissions.ui-fix.workspace_roots]
"~/code/monorepo" = true
# 后果:Agent 可以修改 monorepo 里任何一个包案例二:依赖安装引入的供应链风险
场景:让 Codex 修一个构建错误,它判断需要升级 @types/node。
翻车:Codex 执行了 pnpm add @types/node@latest,不仅升级了类型包,还触发了 lockfile 变动和 postinstall 脚本。新版本的 postinstall 脚本访问了一个内部 registry 地址。
修复:禁止自动安装依赖,网络访问限定到必要域名。
# ✅ 网络白名单 + 禁止自动安装
default_permissions = "fix-build"
[permissions.fix-build]
extends = ":workspace"
[permissions.fix-build.network]
enabled = true
[permissions.fix-build.network.domains]
"registry.npmjs.org" = "allow"
# 不 allow 内部域名,阻止对内部 registry 的意外访问
[permissions.fix-build.filesystem.":workspace_roots"]
"." = "write"
"**/package.json" = "deny"
"**/pnpm-lock.yaml" = "deny"
# 禁止修改依赖声明文件,安装操作需要人工审批# ❌ 开放网络且不禁写 package.json
[permissions.fix-build.network]
enabled = true
[permissions.fix-build.network.domains]
"*" = "allow"
# 后果:Agent 可以自由安装任意包,访问任意外部地址案例三:CI 环境中 Agent 的权限升级
场景:在 GitHub Actions 里用 Codex 做自动代码审查,任务只是读取 PR diff 并输出审查意见。
翻车:CI 环境中 Codex 默认获得了 runner 的全部权限。一个精心构造的 PR 内容触发了 Agent 执行一段 shell 命令,该命令读取了 CI 环境中的 GITHUB_TOKEN 并尝试向 API 发起请求。
修复:CI 环境使用只读沙箱 + 永不审批模式。
# ✅ CI 环境只读配置
# ~/.codex/readonly_quiet.config.toml
approval_policy = "never"
sandbox_mode = "read-only"
# 使用 profile 调用:
# codex --profile readonly_quiet# ❌ CI 中用默认配置或 full-access
codex --dangerously-bypass-approvals-and-sandbox
# 后果:Agent 拥有 CI runner 的全部权限,包括 CI secrets任务风险矩阵
不同任务应该使用不同的沙箱配置。这张表可以作为团队的默认策略参考:
| 任务类型 | 沙箱模式 | 网络 | 审批策略 | 典型配置 |
|---|---|---|---|---|
| 代码解释/阅读 | read-only | 关闭 | never | 只读分析,无交互 |
| 文档修改 | workspace-write | 关闭 | on-request | 限定 docs/content 目录 |
| 前端 bug 修复 | workspace-write | 按需 | on-request | 限定相关包目录 |
| 依赖升级 | workspace-write | 限定 registry | on-request | 禁止直接写 package.json |
| 数据迁移 | 人工执行 | 受控 | on-request | 不在 Agent 自动范围内 |
| CI 代码审查 | read-only | 关闭 | never | 最小权限,无交互 |
一个常见错误是给所有任务使用同一个「全开放」配置。一个修文档的任务不需要网络访问,一个只读分析的任务不需要写入权限。权限越窄,Agent 犯错时的影响范围越小。
审批提示应该包含什么
好的审批提示需要让人快速判断风险。Codex 的审批信息结构:
# ✅ 有足够上下文让人决策
即将执行:pnpm --filter @acme/web check-types
原因:验证 Header 搜索相关 TypeScript 变更
影响范围:只读编译检查,不修改文件
风险:低# ❌ 信息不足,人无法判断
是否允许执行命令?
[Y/n]
# 人只能猜这个命令是安全的如果你发现审批提示的信息量不足以做判断,通常意味着沙箱配置太宽了——窄到不需要审批的配置比更好的审批提示更有效。
沙箱之外的风险
沙箱控制的是 Agent 的工具行为,但有三类风险在沙箱边界之外:
上下文泄露。Agent 的对话上下文可能包含内部代码片段、日志或密钥。即使网络被沙箱阻止,如果 Agent 的 LLM 调用经过外部 API,上下文本身就离开了你的环境。本地模型或 API 的数据保留策略是这一层需要考虑的问题。
依赖污染。即使 Agent 不能直接访问网络,已安装的 node_modules 中的 postinstall 脚本、构建插件、lint 规则的自定义 parser 都可能在 Agent 运行命令时执行。这些代码不在沙箱的控制范围内——它们是项目的一部分,Agent 有理由去运行它们。
逻辑越权。Agent 生成的代码可以通过 typecheck 和 lint,但改变了权限模型或数据可见性。比如在一个 RBAC 系统里新增了一个角色但没有正确配置权限继承——代码是「正确的」,但业务逻辑是有漏洞的。
这些风险需要配合依赖审查工具(如 Socket、Snyk)、敏感信息扫描(如 gitleaks)和业务层面的 code review 来处理。
交付前安全检查清单
涉及 Agent 自动修改代码时,按阶段执行以下检查:
配置阶段
- 确认沙箱模式为
workspace-write或更窄,不使用danger-full-access - 确认网络访问默认关闭,需要时仅白名单开放必要域名
- 确认审批策略为
on-request或untrusted,不使用never(除只读场景) - 确认
.env、*.pem、*.key等敏感文件路径在deny规则中 - 确认
.git、.agents、.codex目录受只读保护
执行阶段
- 检查 Agent 是否访问了工作区以外的文件
- 检查 Agent 是否安装或升级了依赖
- 检查 Agent 是否修改了
package.json或 lockfile - 检查 Agent 是否运行了超出预期的命令(特别是涉及网络、删除、部署的命令)
交付阶段
- 审查
git diff确认所有改动都在预期范围内 - 检查是否涉及认证、权限、支付、数据迁移相关文件
- 确认已运行与改动风险匹配的验证命令(typecheck、test、build)
- 确认不存在未验证的外部副作用
从旧配置迁移
Codex 的权限系统正在从旧版的 sandbox_mode + sandbox_workspace_write 过渡到新的 default_permissions + [permissions] profile 系统。两套系统不能混用。
| 旧配置 | 新配置 | 说明 |
|---|---|---|
sandbox_mode = "read-only" | extends = ":read-only" | 内置 profile 直接继承 |
sandbox_mode = "workspace-write" | extends = ":workspace" | 内置 profile 直接继承 |
sandbox_mode = "danger-full-access" | extends = ":danger-full-access" | 仍然不推荐 |
sandbox_workspace_write.writable_roots | workspace_roots 表 | 语法变了,语义相同 |
sandbox_workspace_write.network_access | network.enabled | 移到 network 子表 |
企业用户可以通过 managed requirements.toml 限制用户可选的 profile 范围。一旦设置了 allowed_permission_profiles,列表之外的 profile(包括内置 profile)都会被拒绝。
写在最后
Codex 的沙箱安全模型本质上是在回答一个问题:你怎么让一个拥有系统调用能力的 AI 程序替你干活,同时确保它不会做你没让它做的事?
答案是多层防御:OS 级隔离做硬边界,权限 profile 做细粒度控制,审批策略做人工介入点,auto-review 做自动化二次检查。没有哪一层是完美的,但它们叠加在一起,把 Agent 的行为空间收窄到一个可接受的范围内。
对我来说最重要的一条经验是:默认配置通常是合理的,但团队需要显式地为不同任务选择不同的配置。不要给所有事情都开 full-access,也不要在所有场景下都拦截审批。根据任务风险匹配权限级别,才是可持续的工作方式。
参考资料
- OpenAI Codex Sandbox Documentation - Codex 沙箱概念官方文档
- OpenAI Codex Permissions Reference - Codex 权限配置完整规格
- OpenAI Codex Agent Approvals & Security - 审批策略与安全边界官方文档
- OpenAI Built Codex's Windows Sandbox the Hard Way - Windows 沙箱工程深度解析
- How to Sandbox AI Agents in 2026: MicroVMs, gVisor & Isolation - AI Agent 隔离技术方案对比
- Codex CLI Sandbox Source (GitHub) - Codex 沙箱实现源码文档
- AI Agent Sandbox & Code Execution Isolation - Agent 沙箱执行隔离研究报告
- Sandbox Management for AI Coding Agents - 编程 Agent 沙箱管理方法论