Codex Sandbox 安全模型:让 Agent 放手做事但不失控

0阅读11分钟

一个真实的失控场景

上个月我在 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、单元测试这些只读操作会自动放行,但 rmgit reset --hardnpm 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 可以将工作区内容发送到任意外部地址

网络边界有几个值得注意的技术细节:

  1. 本地地址默认阻止。即使你开放了网络,Agent 也访问不了 localhost 和私有网段。这是为了防止 DNS rebinding 攻击——如果某个被 allow 的域名在 DNS 层面被解析到内网地址,Agent 不应该能触达内网服务。
  2. DNS 层面的防护。Codex 会做 best-effort 的 DNS/IP 分类检查,防止通过 DNS 重绑定绕过域名白名单。
  3. 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 在收到一个操作请求时的决策路径:

流程图画布 · 115%
Mermaid 流程图加载中...

这个流程图反映的是 Codex 默认配置(workspace-write + on-request)下的行为。如果使用 danger-full-access 模式,所有这些检查都会被跳过——这也是官方把它标记为「Elevated Risk」的原因。

平台差异:三套沙箱实现

Codex 需要在三个操作系统上提供一致的安全体验,但底层机制完全不同:

平台沙箱技术实现方式注意事项
macOSSeatbelt系统内置 sandbox-exec,开箱即用策略模板参数化可写根目录
Linux/WSLBubblewrap + 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/clipackages/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限定 registryon-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-requestuntrusted,不使用 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_rootsworkspace_roots语法变了,语义相同
sandbox_workspace_write.network_accessnetwork.enabled移到 network 子表

企业用户可以通过 managed requirements.toml 限制用户可选的 profile 范围。一旦设置了 allowed_permission_profiles,列表之外的 profile(包括内置 profile)都会被拒绝。

写在最后

Codex 的沙箱安全模型本质上是在回答一个问题:你怎么让一个拥有系统调用能力的 AI 程序替你干活,同时确保它不会做你没让它做的事?

答案是多层防御:OS 级隔离做硬边界,权限 profile 做细粒度控制,审批策略做人工介入点,auto-review 做自动化二次检查。没有哪一层是完美的,但它们叠加在一起,把 Agent 的行为空间收窄到一个可接受的范围内。

对我来说最重要的一条经验是:默认配置通常是合理的,但团队需要显式地为不同任务选择不同的配置。不要给所有事情都开 full-access,也不要在所有场景下都拦截审批。根据任务风险匹配权限级别,才是可持续的工作方式。

参考资料

评论 0

0 / 1000