本文记录如何在 不创建新用户 的前提下,用
sandbox-exec(Seatbelt)对 OpenClaw(AI Agent)进行本地沙盒化运行。
一、背景与目标
运行 OpenClaw(AI Agent)存在潜在安全风险:
- 可能被 Prompt 注入执行危险命令
- 可能访问 SSH Key、Keychain 等私密数据
- 可能误删/篡改系统文件
- 可能污染 Homebrew 工具链
目标是在 不创建新用户 的前提下,把 OpenClaw 运行在一个 受限沙盒环境 中,实现:
- ✅ 限制文件访问范围
- ✅ 阻止系统级破坏
- ✅ 防止密钥泄露
- ✅ 保留正常运行能力
二、总体架构
launchd
↓
run_sandbox.sh (wrapper)
↓
sandbox-exec (Seatbelt)
↓
node → openclaw gateway关键点:
- 由
launchd启动,避免“手动运行绕过沙盒” - 强制通过 wrapper 进入
sandbox-exec - 使用 macOS Seatbelt(SBPL policy)进行隔离
三、目录结构规划
/opt/openclaw/
├── work/ # 工作目录(可选)
├── tmp/ # 临时文件
├── logs/ # 日志(可选;也可以继续使用 ~/.openclaw 内置日志目录)
├── bin/ # 启动脚本
└── policy/ # 沙盒规则权限:
chmod -R 700 /opt/openclaw仅当前用户可访问。
一键创建目录:
sudo mkdir -p /opt/openclaw/{work,logs,tmp,bin,policy}
sudo chown -R "$(id -un)":"$(id -gn)" /opt/openclaw
sudo chmod -R 700 /opt/openclaw四、沙盒策略
路径:
/opt/openclaw/policy/openclaw.sb核心思想:
- 先用 默认允许 保证跑得起来(黑名单模式)
- 再对高风险路径做明确
deny
实战建议:先把策略跑通,再逐步收紧;一上来就
deny default往往会导致大量“正常依赖”被误伤。
示例结构:
ME="$(id -un)"
tee /opt/openclaw/policy/openclaw.sb > /dev/null <<SB
(version 1)
(allow default)
(allow network*)
;; allow homebrew read-only (可选;按需开启)
(allow file-read* (subpath "/opt/homebrew"))
(deny file-write* (subpath "/opt/homebrew"))
;; allow openclaw workspace read/write
(allow file-read* (subpath "/opt/openclaw"))
(allow file-write* (subpath "/opt/openclaw"))
;; OpenClaw 自身目录(如果它默认写到 ~/.openclaw,可放行写入)
(allow file-write* (subpath "/Users/$ME/.openclaw"))
;; deny system writes
(deny file-write* (subpath "/System"))
(deny file-write* (subpath "/usr"))
(deny file-write* (subpath "/bin"))
(deny file-write* (subpath "/sbin"))
(deny file-write* (subpath "/Applications"))
(deny file-write* (subpath "/Library"))
(deny file-write* (subpath "/private"))
;; deny secrets
(deny file-read* (subpath "/Users/$ME/.ssh"))
(deny file-read* (subpath "/Users/$ME/.aws"))
(deny file-read* (subpath "/Users/$ME/.gnupg"))
(deny file-read* (subpath "/Users/$ME/.config"))
(deny file-read* (subpath "/Users/$ME/Library/Keychains"))
(deny file-read* (subpath "/Users/$ME/Documents"))
(deny file-read* (subpath "/Users/$ME/Desktop"))
(deny file-read* (subpath "/Users/$ME/Downloads"))
SB注意点:
- 直接
deny (subpath "~/.config")很“狠”,可能影响一些依赖读取配置(例如 git/ssh/各类 CLI)。如果发现 OpenClaw/插件读配置失败,可以先临时注释掉这一条,再逐项收紧。 - Keychain 相关路径在不同系统/用户环境可能略有差异;如果你发现仍能触达敏感资源,建议用对照实验验证并调整 deny 范围。
五、启动 Wrapper 脚本
路径:
/opt/openclaw/bin/run_sandbox.sh作用:
- 清理敏感环境变量(避免进程继承 Token/Agent)
- 固定
PATH(减少被注入替换二进制的概率) - 统一入口:所有执行都从
sandbox-exec进入
示例:
#!/bin/zsh
set -euo pipefail
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
unset OPENAI_API_KEY GITHUB_TOKEN
unset SSH_AUTH_SOCK
unset GOOGLE_API_KEY
unset ANTHROPIC_AUTH_TOKEN
export TMPDIR=/opt/openclaw/tmp
export PATH=/usr/bin:/bin:/opt/homebrew/bin
POLICY=/opt/openclaw/policy/openclaw.sb
exec /usr/bin/sandbox-exec -f "$POLICY" "$@"可选增强:
- 对
PATH更保守:如果不需要 Homebrew,把/opt/homebrew/bin去掉。 - 如果 OpenClaw 依赖
HOME下的某些缓存/配置,务必结合策略放行最小必要路径,而不是整体放开。
六、LaunchAgent 集成
修改原始 plist:
~/Library/LaunchAgents/ai.openclaw.gateway.plist关键改动:
ProgramArguments
原:

/opt/homebrew/bin/node ...改为:

/opt/openclaw/bin/run_sandbox.sh
/opt/homebrew/bin/node
...保留
- 原有 OPENCLAW_* 变量
- HOME=/Users/xxx(避免配置丢失)
建议
<key>WorkingDirectory</key>
<string>/opt/openclaw/work</string>七、重载服务
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl kickstart -k gui/$(id -u)/ai.openclaw.gateway八、安全验证:A/B 对照实验
目的:
确认安全来自 sandbox,而不是程序自身。
A 组:无沙盒
使用无 sandbox wrapper 启动。
测试:
ls ~/.ssh/结果:
✅ 成功读取
B 组:启用沙盒
恢复 sandbox wrapper。
测试:
ls ~/.ssh结果:
❌ abort / deny

对照结论
| 场景 | 结果 |
|---|---|
| 无沙盒 | 可访问私钥 |
| 有沙盒 | 被阻断 |
结论:
OpenClaw 安全隔离来自 sandbox,验证成功。
九、后续演进方向
Phase B:容器隔离(高风险任务)
- Apple Container / Lima
- Docker/OCI
- 高风险构建/下载任务分流
Phase C:密钥迁移
- Token → Keychain / secrets 文件
- 移出 plist 明文
Phase D:严格策略
- deny default
- 精细白名单
- 按需授权
十、经验总结
- 不要一开始用 deny default
- 先 Smoke → 再 Harden
- 一定做对照实验
- HOME 改动要谨慎
- launchd 环境 ≠ shell 环境