2524 字
约 8 分钟
1
macOS:把安全策略拼成一个字符串
无标签

OpenAI Codex · 执行边界 macOS:把安全策略拼成一个字符串 Seatbelt 吃的是一段文本。文本由静态基线加现拼的读写网段组成。路径不进这段文本,走旁边的参数表。 一句话速览 Seatbelt 吃的是一段文本。文本由静态基线加现拼的读写网段组成。路径不进这段文本,走旁边的参数表。

课程目标 读完能说清三件事。macOS 上平台沙箱默认就在,没有 Windows 那种总开关。交给 sandbox-exec 的策略正文只留占位符,真实路径走 -D 。挖空一条路径时,要同时挡住这个节点、它的子孙,以及把这个节点搬走。

先玩一遍 · 开关片段,看菜单怎么长

策略拼装台:左边加片段,右边看正文、配料表和放行范围

播放 单步 重置

片段

基线 deny 写根 挖空 .codex 钉住 unlink

点片段开关,看策略正文和右侧探针怎么变。

网络

无网 全网 managed 无端口 managed 有端口

实验

路径带引号 只留 subpath

策略正文 · 只留代号

配料表与探针

逻辑轨迹 · 动画每一步对应源码里的哪一段

权限档案拆成读写网络 manager.rs L375 固定使用 /usr/bin/sandbox-exec seatbelt.rs L56 装入四份静态 sbpl seatbelt.rs L21 按根生成 allow 与 require-not seatbelt.rs L476 可写根钉上 file-write-unlink seatbelt.rs L508 排除子路径同时写 literal 与 subpath seatbelt.rs L551 拼网络段,managed 无端口走受限 seatbelt.rs L309 sections join 成 -p 文本 seatbelt.rs L1012 路径写成 -DKEY=value seatbelt.rs L1024 双短横线后接用户命令 seatbelt.rs L1029

点播放,看一份策略怎样由片段拼出来。也可自己开关片段。

正文和路径分开 菜单上只写 param 代号。真实目录名落在 -D 。带引号的路径污染不了 SBPL 语法。 挖空少一条就漏 只留 subpath , mkdir .codex 会成功。两条 require-not 加上最后的 unlink,才把节点、子孙和搬家一起钉住。 网络先收窄 要走代理却没有端口,就不要放行整网。网络基线还在,只是没有 blanket outbound。

教学示意:路径与探针为课程化设定,用来展示片段如何改变策略文本和放行范围。逻辑轨迹右侧行号对应 openai/codex 仓库 commit 4f39251a01。

思路一 · 先关大门,再按片段开门

它解决什么问题 同事把仓库设成可写根,让模型在里面改代码。模型执行 mkdir .codex ,想在仓库里落一份自己的配置。命令立刻失败,stderr 写着 Operation not permitted。可写根听起来像整棵树,实际不是。 再试一条更绕的路。模型先在仓库里写一个普通文件,再 mv 整个工作区,想把里面的 .codex 一起挪到沙箱外面。这条也失败。错误还是 Operation not permitted。

思路是什么 macOS 上 get_platform_sandbox 直接返回 MacosSeatbelt 。那个布尔开关只对 Windows 有意义。包装器不是 Codex 自己的 exe,是系统里固定路径的 /usr/bin/sandbox-exec 。PATH 上放一个同名二进制没用。 出处: codex-rs/sandboxing/src/manager.rs 第 62 至 76 行 · codex-rs/sandboxing/src/seatbelt.rs 第 52 至 56 行 策略文本分两层。四份 .sbpl 用 include_str! 编进二进制,当静态基线。动态段按这一次的可读可写根现拼。拼接顺序固定:基线、读、写、网络在前。全盘可读才加 preferences。受限读才加平台默认路径。祖先的 file-write-unlink 放最后,避免前面更宽的 allow 把 rename 用的 unlink 重新打开。 出处: codex-rs/sandboxing/src/seatbelt.rs 第 21 至 27 行 · codex-rs/sandboxing/src/seatbelt.rs 第 985 至 1012 行 基线第一句业务规则是 (deny default) 。没写明的事一律拒绝。子进程继承这份底稿, sandbox-exec 拉起的 bash 再 fork 出来的子孙,仍在同一套规则里。

权限档案 读根 · 写根 · 网络

四份静态 sbpl

动态读写规则

动态网络段

join -p 文本 -D 参数表

sandbox-exec 再 exec 用户命令

教学化结构图:一份权限档案拆成片段,拼成 argv 再交给系统里那个固定路径的包装器。

为什么长期成立 Seatbelt 的内核接口就是吃一份 SBPL 文本。能控的是文本怎么生成。默认拒绝,再按这一次的根往上开门,换语言重写也用得上。片段的顺序是合同:更窄的 deny 必须能压住前面更宽的 allow。

思路二 · 路径走配料表,不进菜单

它解决什么问题 有人把工程放在 ~/work/app (copy) 这种带括号和空格的目录里,或者路径里出现引号。SBPL 里括号、引号、分号都是语法。路径一旦插进策略正文,用户目录名就能变成语法。 sandbox-exec 解析失败,模型看到的是包装器错误,不是沙箱拒绝。

思路是什么 动态读写规则只往正文里写 (param "WRITABLE_ROOT_0") 这类键名。真实路径写成 -DKEY=value ,成为独立 argv。排除子路径同样走参数表。策略正文和 -D 必须同时出现,测试把这件事锁成合同。 出处: codex-rs/sandboxing/src/seatbelt.rs 第 1022 至 1028 行

路径写进正文

subpath "/tmp/app(copy)"

括号变成语法 包装器解析失败 路径走参数表

正文只写 param WRITABLE_ROOT_0

-D 单独一条 argv value 里可以有引号

语法不被污染

教学化对照:左边把目录名交给解析器,右边只把代号交给解析器。

为什么长期成立 安全策略如果最终是一段文本,用户可控的字符串不要进这段文本。模板只含占位符,真实路径、域名、端口走另一份参数表。和 Seatbelt 语法无关。Python 里同样是模板加 argv。

思路三 · 挖空要挡住节点、子孙和搬家

它解决什么问题 只写 subpath ,第一次创建这个目录会漏, mkdir .codex 会成功。元数据保护变成空壳。不加 file-write-unlink ,rename 能把只读子树挪出挖空区。下一次若仍按原路径授权,实际写的是链接对面。

思路是什么 排除子路径时, require-not 要写两条。 literal 管目录自己, subpath 管下面的内容。两条一起放进 require-all ,是并且关系。顺序不敏感,少写 literal 才是。 出处: codex-rs/sandboxing/src/seatbelt.rs 第 548 至 555 行 可写根默认保护三个顶层名字: .git 、 .agents 、 .codex 。可写根目录本身再钉一条 deny file-write-unlink 。可写根中间若有用户可控的符号链接,拼装直接报错,不会跟着链接走。 出处: codex-rs/protocol/src/permissions.rs 第 24 至 33 行 · codex-rs/sandboxing/src/seatbelt.rs 第 504 至 509 行 · codex-rs/sandboxing/src/seatbelt.rs 第 441 至 450 行 网络是另一段动态文本。要走代理却没有可用端口,返回受限网络段加网络基线,不给 (allow network-outbound) 这种整网放行。网络基线还在,只是没有 blanket outbound。 出处: codex-rs/sandboxing/src/seatbelt.rs 第 309 至 335 行

literal 挡住这个节点 mkdir .codex 目录自己还不存在时 subpath 不一定吃到

subpath 挡住它的子孙 建出来之后的文件 只留 literal 下面的写会漏

unlink 挡住把边界搬走 rm 再 ln -s 放在 sections 最后 避免被更宽的 allow 打开

教学化结构图:三条规则一起生成,少一条就会漏一种绕法。

路径走参数表。挖空要三条一起。

为什么长期成立 挖空一条路径,同时挡住节点本身和它的子孙。再挡住对这个节点的 rename。三条一起生成,用测试锁住形状。跨平台错误文案不同:macOS 常见 Operation not permitted,能被现有关键词表接住。不要假设另外两个平台也是这句话。 出处: codex-rs/sandboxing/src/denial.rs 第 50 至 58 行

横向对比 · 路径进不进这段文本

Claude Code:先放开读,路径 stringify 后进文本 Claude 从 (allow file-read*) 往下加 deny,再在 deny 里 re-allow。后写规则优先。路径用 JSON.stringify 包一层,嵌进策略文本。包装命令从 PATH 里找 sandbox-exec ,不写死 /usr/bin 。两边都要防 rename,Claude 同样生成 deny file-write-unlink 。 Codex 从 (deny default) 往上加 allow,路径留在 -D 。付的是生成器复杂度,换来用户路径不进入 SBPL 语法。Claude 付的是后写顺序必须按对,换来配置面更接近 allowAllExcept。 两侧均已核对源码 · 2026-08-22

DSH 与 Grok:路径进文本,靠转义或直接失败 DSH 默认 (allow default) ,再 (deny file-write*) ,只把写权收回来。路径经 sbplString 转义反斜杠和双引号后,直接嵌进 -p 。没有 -D 参数表。 sandbox-exec 从 PATH 取。本机 macOS 上做不到「可写根里挖掉 .codex 」这一粒度。 Grok 不包一层 sandbox-exec 。它把 SBPL 片段交给当前进程。路径照样进文本,控制字符直接失败,避免沙箱报 active、那条路径却没被挡住。内核 apply 失败时继续跑。Codex 在 Seatbelt 准备失败时返回错误,命令不会裸跑。 两侧均已核对源码 · 2026-08-22

课堂练习

01

少一条 require-not,漏在哪里 演示里先打开写根和挖空 .codex ,看 mkdir .codex 探针是红的。再打开「只留 subpath」,探针变绿。用自己的话解释: subpath 管的是目录下面,为什么挡不住第一次创建这个节点。对照 seatbelt.rs 第 548 至 555 行的注释即可,不要在真实仓库里执行破坏性命令。

Takeaway: macOS 上的沙箱是一段拼出来的 SBPL。路径走 -D ,正文只留占位。挖空要同时挡住节点、子孙和搬家。Seatbelt 挡得住策略里拒绝的操作,挡不住被写宽的 allow,也挡不住模型在可写根里改业务代码。

macOS:把安全策略拼成一个字符串
http://www.clxhxhhr.top/posts/4057/
作者
clxstart
发布于
2026-09-25
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。