Codex 工程实践(三):工具生命周期、权限控制与沙箱
让模型调用工具,原型里可能只需要几行代码:解析工具名,找到函数,执行,再把结果送回去。到了实际开发环境,问题很快会变成:谁允许这次执行,进程能接触什么资源,用户中断后怎么办,以及结果究竟对应哪一次调用。
《Codex 源码分析》第 9、12、13 章把这些问题连接了起来。工具系统负责组织一次调用的过程,权限策略决定能否执行,沙箱把限制落实到操作系统。理解这三者的分工,才能解释“模型能看到工具,为什么还是执行失败”。
本系列日期按博客时间轴编排,资料统一于 2026-11-05 核对。指定参考章节固定到
f31e90b79f594c606746626a56bdf1e49497b275;另核对 Codex 上游86ac0916597244a2e67a197822a5483924660ff1的工具注册表与 Linux 沙箱文档。Windows 细节按参考章节描述讨论,不推断所有安装版本都具有相同部署状态。
一、工具可见和工具可执行是两个条件
模型需要工具名称、描述与参数结构,才能产生调用请求。但把一份 schema 放进请求,并不意味着运行时已经准备好对应的执行逻辑。
第 9 章区分了面向模型的工具声明与运行时注册表。前者决定模型知道哪些接口,后者决定调用到来时由哪个 handler 处理。二者还要结合本轮配置和能力开关保持一致。
可以用四个问题检查一次工具接入:
| 层次 | 需要回答的问题 |
|---|---|
| 声明 | 模型能否知道用途与参数? |
| 路由 | 该调用能否被解析并找到处理器? |
| 授权 | 当前任务是否允许这次操作? |
| 执行环境 | 进程及其依赖能否在限制内完成操作? |
例如模型成功发起 shell 调用,只能说明它选择了接口。目标目录不可写、网络不通或程序不存在,仍然会让执行失败。排障需要区分这些位置,不能都归为“工具没接好”。
二、一次调用的完整生命周期
下面是理解工具系统的概念流程,具体步骤顺序和事件细节依运行时实现而定:
1 | 本轮构造工具集合 |
在本次核对的上游工具注册表中,可以看到执行前 Hook、执行后 Hook 和生命周期通知的协作。它们不是在每种结果上无条件调用的固定流水线;例如,前置检查可能阻止调用,后置处理也会受结果状态影响。
对自己的 Harness,我会特别关注三个状态:尚未执行、执行中、已经产生副作用但结果尚未确认。这三个状态决定了失败后能不能重试。
读取文件失败,重试通常比较容易;写文件、发布消息或提交订单超时,则可能已经在外部成功。没有结果回包,不代表没有发生副作用。调用 ID、执行状态和外部产物核对,应该共同支撑恢复,而不是一律再调用一次。
三、审批策略与沙箱各自控制什么
官方安全文档把沙箱模式与审批作为不同控制项:一个限制执行环境能访问的资源,一个决定何时需要进一步授权。
这也解释了一个容易误会的配置:不请求审批,并不自动代表进程拥有完全访问权限。当前策略可能要求代理在既有边界内自主执行,越界操作直接失败。同样,一次用户批准也需要被转换成具体权限和执行路径,不能只在提示词里补一句“已允许”。
我会把相关控制分为三部分:
- 项目指令说明应该怎样做,帮助模型选择行为。
- 权限和审批策略判定本次操作是否可以继续,授权适用于什么范围。
- 执行后端限制进程实际可以访问的文件、网络与系统能力。
自然语言规则可以被误读,所以关键限制需要执行层支撑。执行层也不负责判断业务目标是否合理,因此仍然需要任务范围与验收约束。
四、macOS:将文件与网络策略编译为 Seatbelt 规则
参考第 12 章展示的 macOS 路径采用 Seatbelt,通过规则限制子进程的文件和网络访问。规则由基础策略、文件范围和网络配置等信息组合而成。
这里的重点是“组合”。可写工作区并不意味着其中每个位置都允许写,元数据或敏感目录可能继续受保护;网络也不只是开、关两种情况,还可能要求经过指定代理。
因此,排查一次失败至少要看请求路径、实际工作目录、符号链接解析后的目标,以及当前网络策略。给整个进程扩大权限,可能掩盖真正的路径配置问题。
五、Linux:文件系统视图与系统调用过滤协作
这次核对的Linux 沙箱 README明确说明:bubblewrap 负责文件系统与命名空间隔离,seccomp 负责系统调用过滤,并设置 PR_SET_NO_NEW_PRIVS。
可以把文件访问的构造过程理解为:先建立受限的文件系统视图,再开放允许写入的根目录,随后对其中受保护的子路径继续施加限制。默认只读的底层视图和局部可写的覆盖,共同表达最终权限。
这也解释了为什么“项目可写”与“项目中的 .git 可写”不能画等号。在核对的版本里,受保护子路径会重新设为只读,细分策略还需要保留更具体路径的覆盖关系。
网络限制则结合命名空间、过滤器和代理桥接等机制。仅清空代理环境变量,并不能建立同等隔离。
版本差异在这里很明显。旧资料常以 Landlock 介绍 Linux 沙箱,但当前核对的 README 将 bubblewrap 作为默认文件系统后端,并说明部分受限文件系统策略会拒绝 legacy Landlock 选项。不能假设 bubblewrap 失败后一定自动退回另一套安全语义。
部署环境还会影响沙箱能否构造:容器里的用户命名空间限制、旧版系统 bubblewrap、WSL1 与 WSL2 都需要分别判断。程序启动成功,不足以证明目标限制已经生效。
六、Windows:身份、ACL 与网络规则共同建立边界
参考第 13 章描述的 Windows 实现涉及独立执行身份、受限令牌、文件 ACL,以及 Windows Firewall 和 WFP 规则。它并非简单复用 Unix 的实现方式。
受限令牌与文件权限主要控制进程身份及文件访问;网络限制还需要额外机制。知道一次命令使用了受限身份,不能直接得出它无法联网的结论。
该章还记录了一个值得注意的失败路径:某些 WFP 安装错误会被记录,setup 仍继续。这是参考版本的可观察行为,但并不意味着所有网络限制同时消失,也不能证明原定隔离完整生效。实际判断需要检查剩余规则、错误记录及运行环境。
对企业部署来说,安装状态和权限探测应当成为运行记录的一部分。只有“setup 成功”一个布尔值,难以表达部分组件失败后的真实边界。
七、长进程、中断与权限缓存
命令运行超过单次等待时间时,工具可能返回进程会话标识,供后续轮询或写入。此时一次工具返回,并不一定意味着进程已经退出。生命周期记录需要区分工具调用、后台进程和业务任务的完成状态。
用户取消任务时,应停止后续调度,并按工具契约处理进程与等待者。取消也不能撤销已经发生的外部操作,因此结束说明里需要保留已完成和未知状态。
如果缓存审批结果,缓存范围还应当足够窄。仅按命令名缓存“允许 shell”会把不同目录、参数和权限需求混在一起。更合理的设计需要结合工具类型、资源范围、工作目录与策略版本,并明确有效期。这是实现建议,具体产品的缓存行为应查对应版本。
八、如何验证一套工具权限设计
我会用一组小范围操作检查它的正反两面:允许目录里的写入能够完成;受保护目录的写入被拒绝;受限网络连接符合配置;工具取消后不再继续调度;执行异常能回写到正确调用。
这些检查应在可丢弃环境中使用无敏感内容的文件与测试端点,记录预期和实际结果。它们验证的是权限行为,不能据此宣称沙箱不存在绕过路径。
工具系统真正需要维持的,是每次调用都能被定位、被约束,并以可解释的状态结束。工具数量增加以后,这份执行契约比单个 handler 的几行实现更重要。
参考阅读
上一篇:上下文如何压缩与拼接。下一篇:会话持久化与长期记忆。