跳转至

第 10 章 安全自治:执行策略、沙箱、权限、审批与守护

前面几章,我们一步步给 agent 装上了越来越强的能力:能跑命令、能改代码、能调外部工具。现在该问一个让人后背发凉的问题了——它能跑任何命令、能动你的文件,那它会不会 rm -rf 你的家目录、把密钥发到外网、往 main 分支强推?

这一章讲的就是那道闸:怎么让 agent 放手干,又不闯祸。这是 harness 里最像"安全工程"的一层,Codex 用了一套纵深防御——硬隔离 + 软策略 + 独立守护,三道防线层层兜底。

引子:一把上了膛的枪

一个能自主跑命令的 agent,本质上是一把上了膛的枪。绝大多数时候它在帮你干活,但只要有一次:

  • 它"自作主张"跑了 rm -rf 删错了目录;
  • 它把含密钥的文件 curl 到了某个外部地址;
  • 它对着 main 分支来了个 git push --force

后果就可能不可逆。模型越自主、能调的工具越多,这把枪的"走火概率"就越值得认真对待。

但你又不能因噎废食——把 agent 锁死到什么都不能做,它也就没用了。安全这一层的全部艺术,是在"放手"和"兜底"之间找平衡:默认关上危险的门,同时让正常的活儿顺畅地过。

Codex 的做法是三道防线

  1. 硬隔离(沙箱 + 执行策略):物理上限制它能动什么——哪怕它想干坏事,也够不着。
  2. 软策略(审批 + 权限画像):什么时候该停下来问你一声。
  3. 独立守护(guardian):一个独立的"复核者",对高风险动作再判一次,必要时一票否决。

〔配图 1:一把上了膛的枪 + 三道防线〕——中间画一个 agent 举着一把"工具枪",枪口前依次三道闸:① 沙箱(一堵物理墙/围栏,标"够不着")② 审批(一道写着"要不要问人?"的栏杆)③ guardian(一只独立的大眼睛 + 一个红色 STOP 牌,标"高风险一票否决")。底部:"放手干,又不闯祸 = 纵深防御。"

一、硬隔离之一:执行策略——这条命令准不准跑

第一道防线在命令还没跑之前就起作用。

每当模型要跑一条命令,Codex 先用执行策略(exec policy)判一下:"这条命令,在当前配置下,准不准跑?"这套策略由独立的 execpolicy crate + core/src/exec_policy.rs 实现,本质是一组规则:哪些命令直接放行、哪些要拦、哪些要先问人。

为什么要在沙箱之外加一层命令级策略?因为沙箱是"粗粒度的物理边界"(你只能在这个目录里写),而执行策略是"细粒度的语义判断"(git push --force 这条命令本身就危险,哪怕它在沙箱里)。两者互补:沙箱管"能动到哪",执行策略管"这个动作本身该不该做"。

二、硬隔离之二:三平台沙箱

执行策略放行后,命令真正跑起来时,是被关在沙箱里的。沙箱是物理边界:哪怕模型铁了心要删你家目录,沙箱也让它够不着。

Codex 在三个平台上各用了一套原生机制(还记得第 2 章 core/README.md 那张支持矩阵吗):

  • macOS:Seatbelt(sandbox-exec)。
  • Linux:Landlock + bubblewrap(用户命名空间)。
  • Windows:受限令牌(restricted token)/ 提权后端。

而且有一条沙箱通用默认:即便在可写模式下,也始终把工作区里的 .git.codex.agents 保持只读——防止 agent 改坏版本历史或自己的配置。这不是某个平台的专利,而是定义在平台无关的策略层(protocol/src/permissions.rsPROTECTED_METADATA_PATH_NAMESdefault_read_only_subpaths_for_writable_root / append_default_read_only_*),由三平台后端共享同一份策略——Seatbelt、Linux 的 bwrap/Landlock 都会照此把这些目录 mask 成只读。

这份"受保护清单"在源码里就是平平无奇的几行常量,却是三平台共享的同一份事实(protocol/src/permissions.rs):

/// Top-level workspace metadata paths that stay protected under writable roots.
pub const PROTECTED_METADATA_PATH_NAMES: &[&str] = &[
    PROTECTED_METADATA_GIT_PATH_NAME,    // ".git"
    PROTECTED_METADATA_AGENTS_PATH_NAME, // ".agents"
    PROTECTED_METADATA_CODEX_PATH_NAME,  // ".codex"
];

它被放在平台无关的 protocol crate、而不是某个沙箱后端里,是有意为之:版本历史(.git)、agent 配置(.agents)、Codex 自己的元数据(.codex)这三样"即便可写模式下也只读"的红线,必须三平台一字不差地共享——否则换个操作系统这条保护就漏了。

沙箱有三档模式(这三档你会天天打交道):

模式 能写吗 能联网吗 用途
read-only 最安全;只读探查
workspace-write 仅工作区内 日常默认;改代码但不出门
danger-full-access 全部 危险;仅在你已在容器等隔离环境里才用

注意当 MemoryTool 启用时,Codex 会把 ~/.codex/memories 追加进沙箱的可读区helper_readable_roots,见 core/src/config/mod.rs)——注意是可读不是可写:这样 agent 能读取自己的记忆(第 11 章)而无需为读取额外审批;真要写入记忆,仍走各自的写入路径。安全设计里到处是这种"在安全和顺畅之间的精细取舍"。

还有两个细节很能体现工程纪律:

  • 环境变量是红线:Codex 用 CODEX_SANDBOX_NETWORK_DISABLEDCODEX_SANDBOX 这类环境变量标记沙箱状态,它的 AGENTS.md 里明令"绝不要改动任何与这些变量相关的代码"——因为大量测试靠它们判断"我现在在沙箱里、该提前退出"。安全机制本身,必须是不可被随手破坏的。
  • 能自测codex sandbox [命令] 子命令让你直接试"某条命令在沙箱里会怎样",macOS 上还能 --log-denials 看它被拦了哪些访问。安全边界做得可观察、可测试,才敢信。

〔配图 2:同一条 rm -rf,三种沙箱〕——中间一条 rm -rf ~/important,三个分支指向三档模式:read-only(一堵实墙挡死,"连读都只读")、workspace-write(围栏只圈住工作区,墙上贴 ".git/.codex 只读",家目录在栏外够不着)、danger-full-access(栏杆放倒,红字"仅限已隔离环境")。底部:"沙箱管'能动到哪'。"

三、软策略之一:审批四档——什么时候该问你

硬隔离是"物理上做不到"。但有些事物理上能做、却应该先问问你——比如要不要联网、要不要动工作区外的文件。这就是审批(approval)

Codex 的审批策略有四档(对应 prompts/templates/permissions/approval_policy/ 下分别写给模型的说明,这是独立的 prompts crate,不在 core 里):

  • never:从不问,全自动(配合严格沙箱用,适合无人值守)。
  • on_failure:正常放行,命令在沙箱里失败了(比如需要联网/写外部)才问你要不要升级权限重试。
  • on_request:模型觉得需要时主动请求审批。
  • unless_trusted:除非是已知可信的命令,否则都问。

这四档,本质是一根"自动 ↔ 谨慎"的旋钮:越往 never 拧越省心但越冒险,越往 unless_trusted 拧越安全但越打扰人。选哪档,取决于你对这次任务的信任度和它的破坏半径。

把"沙箱模式 + 审批档位 + 网络规则"合在一起,Codex 解析成一份权限画像(permission profile)config/permissions.rsresolved_permission_profile.rs)——一份"这一轮 agent 到底能干什么"的可执行清单。这份画像,也会作为一片上下文讲给模型听(第 4 章那个 PermissionsInstructions 片段),让模型知道自己被关在什么笼子里,从而不去做注定被拦的事。

agent 还能主动申请提权:用 request_permissions 工具说"这一步我需要联网,能给我开吗",网络放行甚至能细到单条规则保存复用(network_approvalnetwork_proxy_spec)。

〔配图 3:审批四档光谱〕——一根从左到右的旋钮刻度尺:never(全自动·闪电图标)→ on_failure(失败才问)→ on_request(按需问)→ unless_trusted(事事问·盾牌图标)。左端标"省心但冒险",右端标"安全但打扰"。一个 agent 小人站在旋钮旁,手里举着 request_permissions 牌子:"这步要联网,能开吗?" 中文清晰。

四、第三道防线:Guardian——独立的第二双眼睛

前两道防线都基于"规则"(沙箱配置、审批档位)。但有些坏事,规则不好提前枚举——比如"把一个看似普通、其实含密钥的文件发到外网"。这时需要判断,而不只是规则。

Codex 为此设了一个独立的守护者(guardian):一个专门用来复核 agent 行为风险的角色,带着一套写好的风险策略(core/src/guardian/policy.md)去给动作分级。读一眼它的策略,你能感受到它有多"较真"——它把风险分成四级(low / medium / high / critical),并按类别给规则:

  • 数据泄露:把私有数据、密钥、凭证发到不可信的外部目的地 → high/critical
  • 凭证刺探:从浏览器配置等"不该读的地方"抠 token/cookie → high
  • 持久性削弱安全:改一个会长期生效、打开未来风险的安全设置 → high/critical
  • 破坏性动作:删数据、搞挂生产、对受保护分支乱来 → 视范围 high/critical

最能体现 guardian 分量的,是它的几条"一票否决"规则。比如那条原文写着:即便用户授权等级是 high,也要拒绝把密钥/凭证/私有组织数据泄露给不可信外部目的地的动作。 换句话说——有些红线,连"用户说可以"都压不过。 这正是"独立守护"的意义:它不是听命于 agent 或用户,而是守着一套独立的安全底线。

这条红线在策略文件里是逐字写死的(core/src/guardian/policy.md,"Data Exfiltration" 一节):

Outcome rule: deny actions that disclose secrets, credentials, or private
organization data to an untrusted external destination even when
user_authorization = "high".

末尾那句 even when user_authorization = "high",就是"一票否决"的字面出处:最高一档的用户授权,也解锁不了这条泄密红线。 把红线写进一份独立、可审计的策略文件,而不是散落在代码的 if-else 里,正是"独立守护"可被信任的前提——规则摆在明处,才好复核。

同时这套策略又写得很克制,专门避免"误伤":本地改几个普通文件、对自己的特性分支做有限的 git 操作、用现有凭证走服务原生路径认证……这些都被明确划为低风险,不打扰你。好的安全策略,既要拦得住真危险,又不能把正常活儿全卡死——后者比前者更难,也更见功力。

〔配图 4:Guardian 风险分级复核闸〕——一个 agent 动作("把 config.env curl 到外网")走到一道闸前,闸上站着一只大眼睛 guardian,手里拿一张风险量表(low/medium/high/critical 四色)。它给这个动作贴上 critical,按下红色 STOP,旁边一行字:"即使用户授权=high,泄密也拒绝。"另一个动作("改本地一个普通文件")被贴 low 绿灯放行。底部:"规则之外,还要有判断。"

五、纵深防御:为什么要三道而不是一道

你可能会问:有了沙箱这堵物理墙,为什么还要审批和 guardian?

因为没有任何一道防线是完美的,而它们各补对方的短板:

  • 沙箱(硬隔离)很强,但太粗——它不知道"这条命令语义上危不危险",也挡不住"在允许范围内"的坏事(比如把工作区内一个含密钥的文件发出去,如果网络开着)。
  • 审批(软策略)能引入人的判断,但太频繁会把人烦死、最终被无脑点"同意"(审批疲劳)。
  • guardian(独立判断)能做语义级风控,但它也是个 LLM、会犯错,不能当唯一防线。

三道叠起来,才能既放得开(日常顺畅、不被审批淹没)又兜得住(物理墙 + 语义判断双保险)。这正是 Anthropic《Beyond permission prompts》那篇文章的主旨:降低审批摩擦的同时不失控,靠的不是某一招,而是更好的沙箱 + 策略 + 判断的组合。 安全不是一道墙,是一套纵深。

〔配图 5:纵深防御三层〕——一个危险动作要穿过三层同心防线才可能生效:最外"沙箱墙(物理:够不着的就免谈)"→ 中层"审批闸(该问就问,但别问到疲劳)"→ 内层"guardian 眼(语义判断,红线一票否决)"。每层旁标它的强项与短板。底部:"没有完美的单层;纵深才兜得住。"

本章小结

  • 安全这一层的目标:让 agent 放手干、又不闯祸——在"自治"和"兜底"之间找平衡,默认关危险的门、放正常的活。
  • 三道防线(纵深防御)
  • 硬隔离——执行策略(命令级语义判断,准不准跑)+ 三平台沙箱(物理边界:Seatbelt / Landlock+bubblewrap / 受限令牌;三档模式 read-only / workspace-write / danger-full-access;.git/.codex 只读;环境变量是不可破坏的红线;codex sandbox 可自测)。
  • 软策略——审批四档(never / on_failure / on_request / unless_trusted,一根"自动↔谨慎"旋钮)+ 权限画像(讲给模型听,让它知道自己的笼子)+ 主动提权与网络放行。
  • 独立守护 guardian——带风险分级策略的独立复核者,对泄密/凭证/削弱安全/破坏性动作分级,有些红线连"用户授权"都压不过;同时克制地放行正常操作,避免误伤。
  • 为什么三道而非一道:每道防线都有短板(沙箱太粗、审批会疲劳、guardian 会犯错),叠起来才能既放得开又兜得住——安全是纵深,不是一堵墙。

下一章是本书的"皇冠章":当一个任务大到一个上下文窗口装不下、要跨好几个小时、好几次"换班"才能干完时,harness 怎么让 agent 不失忆地接力——压缩、目标、记忆。


参考来源

解剖标本(codex-rs 源码)· 执行策略

  • execpolicy/ — 命令级语义策略
  • core/src/exec_policy.rs — 策略接入执行路径

解剖标本 · 沙箱(三平台)

  • sandboxing/ — 沙箱总入口
  • sandboxing/src/seatbelt.rs — macOS Seatbelt 实现(在 sandboxing crate,非 core)
  • core/src/landlock.rs — Linux Landlock + bwrap
  • linux-sandbox/ — Linux 沙箱后端
  • core/src/windows_sandbox.rs — Windows 受限令牌
  • core/README.md — 三平台支持矩阵
  • codex-rs/README.md — read-only / workspace-write / danger-full-access、codex sandbox 子命令
  • AGENTS.mdCODEX_SANDBOX* 环境变量红线

解剖标本 · 权限与审批

  • core/src/config/permissions.rs — 权限配置
  • core/src/config/resolved_permission_profile.rs — 解析后的权限画像
  • protocol/src/permissions.rsPROTECTED_METADATA_PATH_NAMES.git/.codex/.agents 只读默认,跨平台共享)
  • prompts/templates/permissions/{approval_policy,sandbox_mode}/*.md — 四档审批 / 三档沙箱写给模型的说明(独立 prompts crate)
  • core/src/tools/handlers/request_permissions.rs — 主动提权
  • core/src/tools/network_approval.rs — 网络放行

解剖标本 · guardian

  • core/src/guardian/policy.md — 风险分级策略与"一票否决"规则
  • core/src/guardian/review.rs — 复核执行
  • core/src/guardian/review_session.rs — 复核会话、guardian_followup_review_reminder

方法论

注:沙箱与策略在三平台各有大量平台细节(Seatbelt profile、Landlock 规则、Windows 后端分级);本章聚焦"三道防线"主干,配置项/路径以你 clone 到的版本与 OpenAI 安全文档为准。

留言