问题
开启文件沙盒(sandbox: true / BOTMUX_SANDBOX=1 / 主机 device enroll 强制凭据隔离)后,在 macOS 上 agent 在沙盒内跑 lark-cli(以及类似把认证/配置写到用户目录下的 CLI)时,认证与配置信息读不到:
lark-cli doctor → config_file: operation not permitted
lark-cli whoami → not configured
lark-cli auth status → not configured
node 直接 stat/读取 lark-cli 配置文件 → EPERM
关键现象:在系统设置里给 node、codex「Full Disk Access / 全磁盘访问权限」也不起作用。
根因
macOS 有两套互相独立的强制访问控制层:
- TCC / Full Disk Access(系统设置 → 隐私与安全性)——管隐私弹窗层。
- Seatbelt(
sandbox-exec / sandbox_init)——内核级 MAC,botmux 用 sandbox-exec -f <profile.sb> 包住 CLI 进程强制施加。
botmux 在 src/worker.ts:7740-7751 把 spawn 改成 sandbox-exec -f <profile> <cliBin> ...,profile 由 src/adapters/cli/fs-policy.ts:500 的 compileToSeatbelt() 生成,起手 (deny default)(line 503)后逐路径 emit (deny file-read* (subpath "..."))。这些 deny 在 vnode 层由内核拦,和 TCC 完全无关——所以给 node/codex 全磁盘访问对 seatbelt 的 deny 规则没有任何作用,读被拒路径照样返回 EPERM。这正解释了「给全磁盘访问也不行」。
涉及的 deny 规则
src/adapters/cli/fs-policy.ts baseline 里直接 deny 的 lark-cli 路径:
| 行 |
规则 |
路径 |
| 300 |
deny |
~/.lark-cli |
| 330 |
deny |
~/Library/Application Support/lark-cli |
| 326 |
deny |
~/Library/Keychains |
macOS 上 lark-cli 的用户登录态 / whoami / auth token / config_file 就落在这两个被 deny 的目录下。沙盒只开了两个极窄的只读 carve-out(fs-policy.ts:434-437):
~/Library/Application Support/lark-cli/master.key.file
~/Library/Application Support/lark-cli/appsecret_<appId>.enc
外加可写目录 ~/.lark-cli-bots/<appId>(line 383,per-bot 身份配置)。
为什么 doctor / whoami / auth status 失败
这条 carve-out 的注释(fs-policy.ts:423-433)写得很清楚:它只验证过 lark-cli auth scopes(botmux 自己的 send 链路用),设计意图是「bot 自己的 send 走 BOT_HOME 里的 send-cred.json,不读这个 key store」。即 carve-out 是按 botmux send 链路的最小需求设计的,没有覆盖 agent 直接跑 lark-cli doctor / whoami / auth status 的需求:
whoami → 要读用户身份 token(在 deny 的 ~/.lark-cli / ~/Library/Application Support/lark-cli)→ EPERM → "not configured"
auth status → 要读登录/授权状态(同上被 deny)→ "not configured"
doctor config_file → 要读 lark-cli 配置文件(在 deny 区)→ "operation not permitted"
对「botmux send 链路」符合设计;对「agent 在沙盒内裸跑 lark-cli 自省/操作身份」是覆盖缺口。
影响面
- 平台:仅 macOS(Linux 走 bwrap,lark-cli 密钥放 per-bot
~/.lark-cli-bots/<self>,已 readWrite,不受影响——见 fs-policy.ts:432 注释)
- 会话类型:所有开启沙盒的 bot 会话(
sandbox: true、readIsolation、device enroll 强制隔离)
- CLI:lark-cli 及任何把认证/配置写到
~/Library/Application Support/<app> 或 ~/.<app> 下、且未被 carve-out 显式放行的 CLI
建议的解法方向(待讨论)
- 关沙盒:受影响 bot 的
bots.json 设 "sandbox": false,并确保无 BOTMUX_SANDBOX=1 全局强制、主机未 device enroll——最直接,但失去跨 bot 隔离。
sandboxPaths 开洞:在 bot 配置里把所需路径加进 sandboxPaths.readOnly(user 源 rank=3 > baseline rank=0,能覆盖 line 300/330 的 deny)。但整目录开 ~/Library/Application Support/lark-cli 会把所有 bot 的 appsecret 密文 + master key 暴露给当前 bot 的 agent,等于回到重构前的跨 bot 泄漏面(line 327-330 注释专门提到的那条),是安全回退。
- 扩 carve-out(推荐):先查清 lark-cli 在 macOS 上
whoami / auth status / doctor 实际读哪几个文件,把本 bot 自己需要的文件以本 bot 限定方式补成只读 carve-out(同 appsecret_<appId>.enc 做法),不破坏跨 bot 隔离。
环境
- 平台:macOS
- 沙盒:开启
- 相关代码:
src/adapters/cli/fs-policy.ts:300,326,330(deny)
src/adapters/cli/fs-policy.ts:434-437(lark-cli carve-out)
src/adapters/cli/fs-policy.ts:500-560(compileToSeatbelt)
src/worker.ts:7740-7751(sandbox-exec -f 包 spawn)
src/adapters/backend/sandbox.ts:232(sandboxEnabled)
问题
开启文件沙盒(
sandbox: true/BOTMUX_SANDBOX=1/ 主机 device enroll 强制凭据隔离)后,在 macOS 上 agent 在沙盒内跑lark-cli(以及类似把认证/配置写到用户目录下的 CLI)时,认证与配置信息读不到:关键现象:在系统设置里给 node、codex「Full Disk Access / 全磁盘访问权限」也不起作用。
根因
macOS 有两套互相独立的强制访问控制层:
sandbox-exec/sandbox_init)——内核级 MAC,botmux 用sandbox-exec -f <profile.sb>包住 CLI 进程强制施加。botmux 在
src/worker.ts:7740-7751把 spawn 改成sandbox-exec -f <profile> <cliBin> ...,profile 由src/adapters/cli/fs-policy.ts:500的compileToSeatbelt()生成,起手(deny default)(line 503)后逐路径 emit(deny file-read* (subpath "..."))。这些 deny 在 vnode 层由内核拦,和 TCC 完全无关——所以给 node/codex 全磁盘访问对 seatbelt 的 deny 规则没有任何作用,读被拒路径照样返回 EPERM。这正解释了「给全磁盘访问也不行」。涉及的 deny 规则
src/adapters/cli/fs-policy.tsbaseline 里直接 deny 的 lark-cli 路径:deny~/.lark-clideny~/Library/Application Support/lark-clideny~/Library/KeychainsmacOS 上 lark-cli 的用户登录态 / whoami / auth token / config_file 就落在这两个被 deny 的目录下。沙盒只开了两个极窄的只读 carve-out(
fs-policy.ts:434-437):外加可写目录
~/.lark-cli-bots/<appId>(line 383,per-bot 身份配置)。为什么 doctor / whoami / auth status 失败
这条 carve-out 的注释(
fs-policy.ts:423-433)写得很清楚:它只验证过lark-cli auth scopes(botmux 自己的 send 链路用),设计意图是「bot 自己的 send 走 BOT_HOME 里的 send-cred.json,不读这个 key store」。即 carve-out 是按 botmux send 链路的最小需求设计的,没有覆盖 agent 直接跑lark-cli doctor / whoami / auth status的需求:whoami→ 要读用户身份 token(在 deny 的~/.lark-cli/~/Library/Application Support/lark-cli)→ EPERM → "not configured"auth status→ 要读登录/授权状态(同上被 deny)→ "not configured"doctorconfig_file → 要读 lark-cli 配置文件(在 deny 区)→ "operation not permitted"对「botmux send 链路」符合设计;对「agent 在沙盒内裸跑 lark-cli 自省/操作身份」是覆盖缺口。
影响面
~/.lark-cli-bots/<self>,已 readWrite,不受影响——见fs-policy.ts:432注释)sandbox: true、readIsolation、device enroll 强制隔离)~/Library/Application Support/<app>或~/.<app>下、且未被 carve-out 显式放行的 CLI建议的解法方向(待讨论)
bots.json设"sandbox": false,并确保无BOTMUX_SANDBOX=1全局强制、主机未 device enroll——最直接,但失去跨 bot 隔离。sandboxPaths开洞:在 bot 配置里把所需路径加进sandboxPaths.readOnly(user 源 rank=3 > baseline rank=0,能覆盖 line 300/330 的 deny)。但整目录开~/Library/Application Support/lark-cli会把所有 bot 的 appsecret 密文 + master key 暴露给当前 bot 的 agent,等于回到重构前的跨 bot 泄漏面(line 327-330 注释专门提到的那条),是安全回退。whoami / auth status / doctor实际读哪几个文件,把本 bot 自己需要的文件以本 bot 限定方式补成只读 carve-out(同appsecret_<appId>.enc做法),不破坏跨 bot 隔离。环境
src/adapters/cli/fs-policy.ts:300,326,330(deny)src/adapters/cli/fs-policy.ts:434-437(lark-cli carve-out)src/adapters/cli/fs-policy.ts:500-560(compileToSeatbelt)src/worker.ts:7740-7751(sandbox-exec -f包 spawn)src/adapters/backend/sandbox.ts:232(sandboxEnabled)