适用场景:sandbox-exec -f xxx.sb
一、Policy 基本结构
SBPL 基于 Scheme 语法(Lisp 变体),采用 S-Expression(S 表达式)。
1. 最小结构
一个最基础的策略文件通常包含版本声明和默认行为:
(version 1)
(allow default) ; 默认允许所有操作
(deny file-read* (subpath "/Users/lihu/.ssh")) ; 显式拒绝读取 .ssh 目录2. 语法组成
单条规则的基本格式如下:
(action permission (condition))图解示例:
(deny file-read* (subpath "/Users/lihu/.ssh"))
↑ ↑ ↑
动作 权限类型 匹配条件- 动作 (Action):
allow(允许) 或deny(拒绝) - 权限 (Permission): 如
file-read*,network*,process-exec等 - 条件 (Condition): 如路径匹配、网络地址匹配等
二、核心文件权限分类
文件权限控制是沙盒策略中最常用的部分,主要分为以下三类:
1. file* (完全控制)
file*定义:拥有对文件的所有权限。
等价于:file-read* + file-write* + 元数据修改 + 打开 + stat + rename + unlink。
👉 特点:读写删改全封死。
适用场景:
- 私钥 (
~/.ssh) - Keychain 数据
- Secrets / 凭证文件
- 核心敏感资产
示例:
(deny file* (subpath "~/.ssh"))2. file-read* (读取相关)
file-read*覆盖操作:
| 操作 category | 具体 syscall/command | 是否包含 |
|---|---|---|
| 读取内容 | read, cat, grep |
✅ |
| 元数据 | ls, stat, lstat |
✅ |
| 打开文件 | open(O_RDONLY) |
✅ |
| 内存映射 | mmap (read) |
✅ |
不包含:没有任何写入、删除或创建文件的权限。
👉 特点:只能防泄密,不能防破坏。
3. file-write* (写入相关)
file-write*覆盖操作:
| 操作 category | 具体 syscall/command | 是否包含 |
|---|---|---|
| 创建/修改 | touch, > 重定向, truncate |
✅ |
| 删除/移动 | rm, mv, unlink |
✅ |
| 目录操作 | mkdir, rmdir |
✅ |
| 属性修改 | chmod |
✅ |
👉 特点:控制破坏能力(防篡改)。
三、细分文件权限
除了带 * 的聚合权限,SBPL 还提供了更细粒度的控制:
file-read-data/file-read-metadata:file-read*的子集。file-read-data: 读取文件内容。file-read-metadata: 读取文件属性 (stat,ls)。
file-write-data: 仅允许修改文件内容,不允许删除或重命名。file-write-create: 仅允许新建文件,不允许覆盖现有文件。file-rename: 控制mv操作。file-unlink: 控制rm操作。file-metadata: 控制属性修改(chmod,chown,utime)。
⚠️ 实战建议:90% 的场景下,直接使用
file*,file-read*,file-write*即可满足需求,无需过度细分。
四、路径匹配规则
在定义文件路径时,常用的匹配器如下:
1. subpath (子路径匹配)
(subpath "/Users/lihu/.ssh")含义:匹配该目录路径及其下属的所有子目录和文件。 ✅ 推荐:最常用的匹配方式。
2. literal (精确匹配)
(literal "/Users/lihu/.ssh/id_ed25519")含义:仅匹配指定路径及其对应的单个文件,不包含子内容(除非是文件本身)。
3. regex (正则匹配)
(regex "^/Users/lihu/.*\\.key$")含义:使用正则表达式匹配路径。 ⚠️ 注意:开销较大且容易出错,慎用。
4. home-relative-path (家目录相对路径)
(home-relative-path ".ssh")含义:等价于 ~/.ssh。
✅ 优点:可移植性好,脚本在不同用户下通用。
五、Allow / Deny 优先级规则
这是编写 Seatbelt 策略最容易混淆的地方,由于其基于 Pattern Matching(模式匹配),遵循 “Specific overrides General” (具体覆盖宽泛) 的原则,通常表现为 Allow/Deny 互相 “打洞” (Hole punching)。
规则 1:Deny 优先于同级或更宽泛的 Allow
当一个操作同时被 Allow 和 Deny 覆盖时,如果范围相同或 Deny 更具体的针对该区域,通常 Deny 生效。
;; 1. 宽泛允许
(allow file-read* (subpath "/Users/lihu"))
;; 2. 局部拒绝 (在允许范围内挖一个洞)
(deny file-read* (subpath "/Users/lihu/.ssh"))结果:
/Users/lihu/file.txt✅ 可读 (匹配 Allow)/Users/lihu/.ssh/id❌ 拒绝 (匹配更具体的 Deny)
👉 结论:Deny 可以覆盖(Override)宽泛的 Allow。
规则 2:更具体的路径优先 (例外/打洞)
反之亦然,可以在一个大的 Deny 范围内,Allow 一个小子目录。
;; 1. 宽泛拒绝
(deny file* (subpath "/Users/lihu"))
;; 2. 局部允许 (在拒绝范围内挖一个洞)
(allow file-read* (subpath "/Users/lihu/test"))结果:
/Users/lihu/private❌ 拒绝/Users/lihu/test/log✅ 可读
👉 结论:更具体的 Allow 可以覆盖宽泛的 Deny。
规则 3:全局默认与局部调整
这是最推荐的策略编写模式:先定义基准(Default),再定义例外。
(allow default) ;; 基准:默认全开 (黑名单模式)
;; ... 添加具体的 deny 规则 ...
(deny file* (subpath "/Users/lihu/.ssh"))六、default 的真实含义
(allow default)
黑名单模式 (Blacklist Mode)
- 含义:未明确禁止的操作,默认全部允许。
- 适用:Smoke Test(冒烟测试)、调试初期、兼容性要求高的场景。
(deny default)
白名单模式 (Whitelist Mode)
- 含义:未明确允许的操作,默认全部禁止 (Abort)。
- 适用:Hardened(加固)系统、生产环境、最终发布的策略。
七、典型安全模板
模板 1:特定敏感目录保护
适用于一般程序,防止读取 SSH 密钥。
(deny file* (home-relative-path ".ssh"))模板 2:工作区模式
仅允许程序在一个特定目录下折腾。
(allow file-read* (subpath "/opt/project_build"))
(allow file-write* (subpath "/opt/project_build"))模板 3:Homebrew 只读保护
防止脚本篡改系统工具。
(allow file-read* (subpath "/opt/homebrew"))
(deny file-write* (subpath "/opt/homebrew"))模板 4:终极严格模式 (示意)
(version 1)
(deny default) ; 白名单模式,拒绝所有
;; 允许系统基本库读取 (否则程序起不来)
(allow file-read* (subpath "/usr/lib"))
(allow file-read* (subpath "/System/Library"))
;; 业务逻辑
(allow file-read* (subpath "/opt/openclaw"))
(allow file-write* (subpath "/opt/openclaw"))
(allow network-outbound) ; 允许联网