Skip to content

docs(sandbox): 补齐「lark-cli 按 bot 拆配置」这条沙盒前置条件 - #701

Open
xu4wang wants to merge 1 commit into
deepcoldy:masterfrom
xu4wang:docs/lark-cli-per-bot
Open

docs(sandbox): 补齐「lark-cli 按 bot 拆配置」这条沙盒前置条件#701
xu4wang wants to merge 1 commit into
deepcoldy:masterfrom
xu4wang:docs/lark-cli-per-bot

Conversation

@xu4wang

@xu4wang xu4wang commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Refs #683纯文档,零代码改动。

问题

沙盒 / 读隔离的白名单本来就是按「每个 bot 用自己的 lark-cli 配置目录」设计的:

路径 档位
~/.lark-cli deny
~/Library/Application Support/lark-cli(整目录) deny
…/master.key.file…/appsecret_<自己appId>.enc readOnly carve-out
~/.lark-cli-bots/<自己appId> readWrite

这个前提从没写进任何文档。机器没做过「按 bot 拆 lark-cli 配置」时,沙盒内所有 lark-cli 命令都以 not configured / config_file: operation not permitted 失败,而报错完全不指向根因——#683 里就一路被误导到「给 node / codex 开完全磁盘访问权限」上去(TCC 与 Seatbelt 是两套互相独立的强制访问控制,TCC 授权对 Seatbelt 的 deny 规则毫无作用)。

fs-policy.ts 里其实已经有一行注释提到「botmux's lark-cli identity mapping lives there(~/.zshenv)」——契约存在于代码注释里,用户侧无从得知。

改动

新增 docs/lark-cli-per-bot.md

  • 为什么要拆(白名单怎么切的、每条 deny/carve-out 的理由)
  • 原理链路:BOTMUX_LARK_APP_IDLARKSUITE_CLI_CONFIG_DIR~/.lark-cli-bots/<appId>
  • 配置步骤:~/.zshenv 映射(为什么必须是 .zshenv 不是 .zshrc)、per-bot config init + default-as bot、开放平台开 scope
  • 验证:config show / auth status,以及用会话线上的 Seatbelt profile 做内核级读探测
  • 边界与排错

交叉链接docs/file-sandbox.md「启用」段、docs/isolated-bot-deploy.md 部署步骤 + 排错表。

边界部分记下的几条实测 / 读码结论

  • 沙盒下只有 bot 身份可用:用户登录态 <appId>_<openId>.enc 不在 carve-out 内,--as user 及一切依赖用户 token 的命令读不到。
  • 不要用 sandboxPaths.readOnly 整目录开洞:user 源 rank=3 > baseline rank=0 确实能覆盖 deny 让命令跑起来,但等于把所有 bot 的 appsecret 密文 + master key 交给当前 bot 的 agent,是安全回退。
  • no-transport turn(apiOnly bot / HTTP 虚拟会话)下 lark-cli 一律不可用:整个飞书授权面被冻结,per-bot 目录与 keystore carve-out 都不发放。这是有意的。
  • lark-cli 1.0.56 没有 whoami 子命令fix(sandbox): macOS 沙盒 deny lark-cli 认证/配置路径,agent 在沙盒内无法读 auth/whoami/doctor(Full Disk Access 无法覆盖 Seatbelt) #683 里提到的那个)。自查身份用 auth status / config show

没做的(可后续单独提)

这份 PR 只补文档。真正把隐式契约变成显式行为的两件事没做:

  1. worker spawn 时直接注入 LARKSUITE_CLI_CONFIG_DIR(该目录存在时),让它不再依赖用户手改 shell rc——现在的 .zshenv 方案在不经过登录 shell 的 spawn 路径上、以及非 zsh 机器上会静默失效;
  2. 沙盒开启但 ~/.lark-cli-bots/<appId>/config.json 缺失时 spawn 前 warn 一条,把静默失败变成指向文档的提示。

验证

文档里的命令输出、路径、行为都在 macOS + lark-cli 1.0.56 上对着当前 master 的 fs-policy.ts 逐条核过。

🤖 Generated with Claude Code

沙盒/读隔离的白名单本来就是按「每个 bot 用自己的 lark-cli 配置目录」
设计的:deny 共享的 `~/.lark-cli` 与 macOS keystore 整目录,只放行
`~/.lark-cli-bots/<自己appId>`(rw)+ 本 bot 的 `master.key.file` /
`appsecret_<自己appId>.enc`(ro)。但这个前提从没写进文档,机器没配过时
沙盒内所有 lark-cli 命令会以 `not configured` /
`config_file: operation not permitted` 失败,而报错完全不指向根因——
还会把人误导到「给 node/codex 开完全磁盘访问权限」上去(TCC 与 Seatbelt
是两套独立的强制访问控制,对 seatbelt 的 deny 毫无作用)。

新增 docs/lark-cli-per-bot.md:白名单切法与理由、
BOTMUX_LARK_APP_ID → LARKSUITE_CLI_CONFIG_DIR 的映射链路、配置步骤、
验证(含用线上 profile 做内核级读探测)、边界与排错。

边界部分记下几条实测/读码结论:沙盒下只有 bot 身份可用(用户 token
`<appId>_<openId>.enc` 不在 carve-out 内);不要用 sandboxPaths.readOnly
整目录开洞(user rank 3 > baseline 0 能覆盖 deny,但会把所有 bot 的
appsecret 密文 + master key 暴露给当前 agent);no-transport turn 会冻结
整个飞书授权面、per-bot 目录与 keystore carve-out 都不发放;lark-cli
1.0.56 没有 whoami 子命令,自查用 auth status / config show。

同时在 docs/file-sandbox.md「启用」与 docs/isolated-bot-deploy.md
部署步骤 + 排错表交叉链接。纯文档,无代码改动。

Refs deepcoldy#683

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审结论:需要修改。macOS 的 deny/carve-out 因果没有讲反,但 Linux 安全语义与 lark-cli 1.0.56 的实际落盘行为相反,当前文字会给出错误的跨 bot 隔离保证。

[P1] Linux 密钥并不在 per-bot 配置目录,当前策略会把所有 bot 的密文和 master key 暴露为 readOnly

docs/lark-cli-per-bot.md:22 写道「Linux(bwrap)把密钥直接放在 per-bot 目录里」,但我用 PR 明确声称实测的 lark-cli 1.0.56 在隔离 HOME 下复现:

.lark-cli-bots/cli_review_56/config.json
.local/share/lark-cli/appsecret_cli_review_56.enc
.local/share/lark-cli/master.key

LARKSUITE_CLI_CONFIG_DIR 只重定向 config.json,Linux fallback keystore 仍落在 ~/.local/share/lark-cli。当前 commonHomeBaseline() 又把整个 ~/.local/share 设为 readOnly,且 Linux 没有对 ~/.local/share/lark-cli 的 deeper deny/carve-out。直接调用策略真源得到:

~/.local/share/lark-cli/master.key                 readOnly
~/.local/share/lark-cli/appsecret_cli_other.enc    readOnly
~/.lark-cli-bots/cli_self/config.json              readWrite
~/.lark-cli-bots/cli_other/config.json             none

因此 Linux 沙盒内的 bot 能读到兄弟 bot 的 appsecret 密文和共享 master key;还可以改写自己可写的 config 指向兄弟 appId/key id,从而以兄弟 app 身份调用 lark-cli。这个问题也存在于当前安装的 1.0.76,不是只影响旧版本。

在合并这份“安全前置条件”文档前,至少需要二选一:

  1. 修正 Linux policy:deny ~/.local/share/lark-cli,仅 carve-out 本 bot 所需的 master key + appsecret_<self>.enc(并补策略/编译器测试);或
  2. 明确把该隔离保证限定为 macOS,并警告 Linux 文件沙盒下 lark-cli 的跨 bot secret 隔离尚未成立。

否则 file-sandbox.md 新增的跨平台前置条件会把一个实际未闭合的 secret 边界描述成已闭合。

[P2] 不要建议在隔离 bot 会话内首次 config init

docs/lark-cli-per-bot.md:72 暗示在 bot 会话内加 --force-init 即可。但首次配置时 per-bot 目录尚不存在,worker 的 existence filter 不会发放这条 allow;即便预建了目录,Linux 的 ~/.local/share/lark-cli、macOS 的 keystore carve-out 在沙盒内也只能读,config init 无法写入新 appsecret/master key。--force-init 只绕过 lark-cli 的 Agent-context 检查,不绕过 bwrap/Seatbelt。

建议把步骤 2 明确限定为沙盒外的宿主终端;若要保留 --force-init 说明,需限定为非隔离的 OPENCLAW/HERMES 类 Agent 会话。

其余我复核的 macOS 路径、source-rank/deepest-prefix、no-transport gate、用户 token 不 carve-out、Seatbelt profile 路径均与源码/测试一致。纯文档 diff 的 git diff --check 也通过。按群内要求,未执行合并。

@deepcoldy

Copy link
Copy Markdown
Owner

我让agent直接改下

@xu4wang

xu4wang commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

我让agent直接改下

感觉linux下基线需要增强下

deepcoldy added a commit that referenced this pull request Aug 15, 2026
沙盒白名单设计上按「每个 bot 只放行自己那份 lark-cli 密钥」,macOS 早已在 keystore 上闭合,
Linux 漏了这半边:lark-cli 在 Linux 把密钥落在共享 store(`appsecret_<appId>.enc` 每 app
一份 + 共享 `master.key`),而 Linux fs-policy 只有一条 baseline `ro(~/.local/share)`、没有
更深的 deny——任何开 sandbox/readIsolation 的 bot 在沙盒内都能只读到所有兄弟 bot 的 appsecret
密文 + master key,可改写自身 config 指向兄弟 appId 从而冒用其身份。daemon 跑在 Linux,任何
Linux bot 开沙盒即触发。

keystore 落点由 `LARKSUITE_CLI_DATA_DIR` 决定(`<clean(value)>/lark-cli`,仅当合法绝对路径;
否则回退 `$HOME/.local/share/lark-cli`),不读 `XDG_DATA_HOME`(strace 实测 v1.0.76 +
官方 keychain_other.go::StorageDir / SafeEnvDirPath 交叉核对)。

改动:
- fs-policy.ts:Linux transport 会话镜像 macOS carve-out——deny(store 根) + 深一层
  ro(master.key)(Linux 文件名,非 macOS 的 master.key.file)+ ro(appsecret_<self>.enc);
  兄弟密文 / 用户 token / 未来文件全 deny-by-default。
- 双候选 store 全锁:lark-cli 对一个 env 值只可能开 `<resolved>/lark-cli` 或默认
  `$HOME/.local/share/lark-cli`,其字符校验规则难以逐字符复刻,故两个 store 都锁(transport
  都 deny+都 carve 自身;no-transport 都进 authority roots 零 carve-out),无论它选哪个兄弟+
  master 都 deny、自身可读。常态去重成一个。
- BOT_HOME 嵌套 authority:insideAuthority 的 BOT_HOME 例外改为按 root 作用,只豁免作为
  BOT_HOME 祖先的 root;嵌套在 BOT_HOME 内更具体的 authority root(如 LARKSUITE_CLI_DATA_DIR
  =<BOT_HOME> 时的 keystore)仍限制,堵深层 hostile user RW 逃过 dropAuthority 把 master.key
  解析成 readWrite 的逃逸。
- no-transport(apiOnly/HTTP 虚拟):keystore 纳入 computeNoTransportAuthorityRoots 冻结、
  零 carve-out(连自身 key 也不给)。该 store 加入仅在 platform==='linux'——macOS 用
  Library/… store,在 darwin 冻结 Linux 路径会改变 darwin 行为(no-transport workingDir 落
  ~/.local/share/lark-cli 会新失败),故严格按 platform 门控,darwin 与旧行为逐字节一致。
- 路径规范化:新增纯函数 cleanPosixAbsPath(Go filepath.Clean 语义 + 控制字符拒绝,对齐
  SafeEnvDirPath)+ resolveLarkCliLinuxStoreDir。worker 从冻结宿主环境取
  process.env.LARKSUITE_CLI_DATA_DIR,Clean 后经 canonicalNearestAncestor(realpath 最深存在
  祖先 + 回接不存在尾段,解 symlink 前缀而不在缺失叶子上抛错)规范化,作为显式
  FsPolicyContext.larkCliLinuxStore 传入;policy 保持纯函数不读 env。larkCliLinuxStorePath 对
  不可规范化 override 返回 null(不发 carve-out、store 保持 deny-by-default),绝不静默回退默认。
- 沙盒 child-pin:prepareDirectSandbox 新增 larkCliDataDir,worker 传 larkCliChildDataRoot
  (canonStore)——仅当 canonical store basename 仍是 lark-cli 才 --setenv dirname(store) 作
  child 的 LARKSUITE_CLI_DATA_DIR,使沙盒内 lark-cli 与 policy 落同一 canonical 命名空间;若
  lark-cli leaf 是 symlink 指向别名目录则不钉(child 回退默认 store,默认也已锁)→ symlink 数据
  根不再命名空间分叉。
- per-bot env:LARKSUITE_CLI_DATA_DIR 纳入 per-bot-env.ts 保留键——用户在 bots.json.env 配它
  会被拒 + 可诊断,不再被沙盒 host --setenv/--unsetenv 静默覆盖。

影响面:仅 Linux 且开 sandbox/readIsolation 的会话行为变化(收紧)。darwin 路径完全不变(新逻辑
均在 platform==='linux' 分支,含 no-transport authority roots 的 platform gate)。transport 拿
自身 key carve-out、no-transport 整个 keystore 冻结。PtyBackend/TmuxBackend 共用同一编译产物无差异;
所有 CLI 开沙盒都受同一收紧规则保护。prepareDirectSandbox 新增可选入参,旧调用兼容。

验证:
- pnpm build 绿。
- fs-policy 单测:自身 RO / 兄弟+token+未来文件 deny / 无关工具链不误伤;自定义 store rehome;
  双候选 store 全锁(transport+no-transport)+ default-only 去重;BOT_HOME 嵌套 authority;
  cleanPosixAbsPath 与 resolveLarkCliLinuxStoreDir 的「绝对 Clean/相对忽略/控制字符拒绝/`..` 归一」
  真值表;larkCliChildDataRoot 同名/别名 symlink/root/不可用输入;no-transport authority roots
  按 platform 门控 + darwin 与旧行为一致回归。per-bot-env 单测覆盖新保留键。
- bwrap 内核级 e2e:真 bubblewrap 下兄弟 appsecret + master.key 读不到、ls 不列兄弟、自身可读、
  store 不可写;LARKSUITE_CLI_DATA_DIR=<BOT_HOME> + no-transport + 敌意 user RW 直指 master.key
  仍全拒。
- 变异测试:去 store deny / 破 no-transport 门 / 碰错 master.key 文件名 / 去默认 store 锁 / 去
  child-pin basename 判定 / 去 platform 门控,各让对应用例(含内核级)翻红,确认测试有牙。
- live(真 sandbox + 真 lark-cli):默认 store auth 正常 + 兄弟内核级拦;自定义/含 `..`/symlink
  数据根 store 同样 auth 正常 + 兄弟拦;no-transport 下自身 key 亦被冻结。

Refs #701(该文档 PR 保持 docs,待本代码修复合入后 rebase 修正 Linux 密钥落点表述 + 首次
init 只能在宿主沙盒外终端执行)。
deepcoldy added a commit that referenced this pull request Aug 15, 2026
沙盒白名单设计上按「每个 bot 只放行自己那份 lark-cli 密钥」,macOS 早已在 keystore 上闭合,
Linux 漏了这半边:lark-cli 在 Linux 把密钥落在共享 store(`appsecret_<appId>.enc` 每 app
一份 + 共享 `master.key`),而 Linux fs-policy 只有一条 baseline `ro(~/.local/share)`、没有
更深的 deny——任何开 sandbox/readIsolation 的 bot 在沙盒内都能只读到所有兄弟 bot 的 appsecret
密文 + master key,可改写自身 config 指向兄弟 appId 从而冒用其身份。daemon 跑在 Linux,任何
Linux bot 开沙盒即触发。

keystore 落点由 `LARKSUITE_CLI_DATA_DIR` 决定(`<clean(value)>/lark-cli`,仅当合法绝对路径;
否则回退 `$HOME/.local/share/lark-cli`),不读 `XDG_DATA_HOME`(strace 实测 v1.0.76 +
官方 keychain_other.go::StorageDir / SafeEnvDirPath 交叉核对)。

改动:
- fs-policy.ts:Linux transport 会话镜像 macOS carve-out——deny(store 根) + 深一层
  ro(master.key)(Linux 文件名,非 macOS 的 master.key.file)+ ro(appsecret_<self>.enc);
  兄弟密文 / 用户 token / 未来文件全 deny-by-default。
- 双候选 store 全锁:lark-cli 对一个 env 值只可能开 `<resolved>/lark-cli` 或默认
  `$HOME/.local/share/lark-cli`,其字符校验规则难以逐字符复刻,故两个 store 都锁(transport
  都 deny+都 carve 自身;no-transport 都进 authority roots 零 carve-out),无论它选哪个兄弟+
  master 都 deny、自身可读。常态去重成一个。
- BOT_HOME 嵌套 authority:insideAuthority 的 BOT_HOME 例外改为按 root 作用,只豁免作为
  BOT_HOME 祖先的 root;嵌套在 BOT_HOME 内更具体的 authority root(如 LARKSUITE_CLI_DATA_DIR
  =<BOT_HOME> 时的 keystore)仍限制,堵深层 hostile user RW 逃过 dropAuthority 把 master.key
  解析成 readWrite 的逃逸。
- no-transport(apiOnly/HTTP 虚拟):keystore 纳入 computeNoTransportAuthorityRoots 冻结、
  零 carve-out(连自身 key 也不给)。该 store 加入仅在 platform==='linux'——macOS 用
  Library/… store,在 darwin 冻结 Linux 路径会改变 darwin 行为(no-transport workingDir 落
  ~/.local/share/lark-cli 会新失败),故严格按 platform 门控,darwin 与旧行为逐字节一致。
- 路径规范化:新增纯函数 cleanPosixAbsPath(Go filepath.Clean 语义 + 控制字符拒绝,对齐
  SafeEnvDirPath)+ resolveLarkCliLinuxStoreDir。worker 从冻结宿主环境取
  process.env.LARKSUITE_CLI_DATA_DIR,Clean 后经 canonicalNearestAncestor(realpath 最深存在
  祖先 + 回接不存在尾段,解 symlink 前缀而不在缺失叶子上抛错)规范化,作为显式
  FsPolicyContext.larkCliLinuxStore 传入;policy 保持纯函数不读 env。larkCliLinuxStorePath 对
  不可规范化 override 返回 null(不发 carve-out、store 保持 deny-by-default),绝不静默回退默认。
- 沙盒 child-pin:prepareDirectSandbox 新增 larkCliDataDir,worker 传 larkCliChildDataRoot
  (canonStore)——仅当 canonical store basename 仍是 lark-cli 才 --setenv dirname(store) 作
  child 的 LARKSUITE_CLI_DATA_DIR,使沙盒内 lark-cli 与 policy 落同一 canonical 命名空间;若
  lark-cli leaf 是 symlink 指向别名目录则不钉(child 回退默认 store,默认也已锁)→ symlink 数据
  根不再命名空间分叉。
- per-bot env:LARKSUITE_CLI_DATA_DIR 纳入 per-bot-env.ts 保留键——用户在 bots.json.env 配它
  会被拒 + 可诊断,不再被沙盒 host --setenv/--unsetenv 静默覆盖。

影响面:仅 Linux 且开 sandbox/readIsolation 的会话行为变化(收紧)。darwin 路径完全不变(新逻辑
均在 platform==='linux' 分支,含 no-transport authority roots 的 platform gate)。transport 拿
自身 key carve-out、no-transport 整个 keystore 冻结。PtyBackend/TmuxBackend 共用同一编译产物无差异;
所有 CLI 开沙盒都受同一收紧规则保护。prepareDirectSandbox 新增可选入参,旧调用兼容。

验证:
- pnpm build 绿。
- fs-policy 单测:自身 RO / 兄弟+token+未来文件 deny / 无关工具链不误伤;自定义 store rehome;
  双候选 store 全锁(transport+no-transport)+ default-only 去重;BOT_HOME 嵌套 authority;
  cleanPosixAbsPath 与 resolveLarkCliLinuxStoreDir 的「绝对 Clean/相对忽略/控制字符拒绝/`..` 归一」
  真值表;larkCliChildDataRoot 同名/别名 symlink/root/不可用输入;no-transport authority roots
  按 platform 门控 + darwin 与旧行为一致回归。per-bot-env 单测覆盖新保留键。
- bwrap 内核级 e2e:真 bubblewrap 下兄弟 appsecret + master.key 读不到、ls 不列兄弟、自身可读、
  store 不可写;LARKSUITE_CLI_DATA_DIR=<BOT_HOME> + no-transport + 敌意 user RW 直指 master.key
  仍全拒。
- 变异测试:去 store deny / 破 no-transport 门 / 碰错 master.key 文件名 / 去默认 store 锁 / 去
  child-pin basename 判定 / 去 platform 门控,各让对应用例(含内核级)翻红,确认测试有牙。
- live(真 sandbox + 真 lark-cli):默认 store auth 正常 + 兄弟内核级拦;自定义/含 `..`/symlink
  数据根 store 同样 auth 正常 + 兄弟拦;no-transport 下自身 key 亦被冻结。

Refs #701(该文档 PR 保持 docs,待本代码修复合入后 rebase 修正 Linux 密钥落点表述 + 首次
init 只能在宿主沙盒外终端执行)。
deepcoldy added a commit that referenced this pull request Aug 15, 2026
沙盒白名单设计上按「每个 bot 只放行自己那份 lark-cli 密钥」,macOS 早已在 keystore 上闭合,
Linux 漏了这半边:lark-cli 在 Linux 把密钥落在共享 store(`appsecret_<appId>.enc` 每 app
一份 + 共享 `master.key`),而 Linux fs-policy 只有一条 baseline `ro(~/.local/share)`、没有
更深的 deny——任何开 sandbox/readIsolation 的 bot 在沙盒内都能只读到所有兄弟 bot 的 appsecret
密文 + master key,可改写自身 config 指向兄弟 appId 从而冒用其身份。daemon 跑在 Linux,任何
Linux bot 开沙盒即触发。

keystore 落点由 `LARKSUITE_CLI_DATA_DIR` 决定(`<clean(value)>/lark-cli`,仅当合法绝对路径;
否则回退 `$HOME/.local/share/lark-cli`),不读 `XDG_DATA_HOME`(strace 实测 v1.0.76 +
官方 keychain_other.go::StorageDir / SafeEnvDirPath 交叉核对)。

改动:
- fs-policy.ts:Linux transport 会话镜像 macOS carve-out——deny(store 根) + 深一层
  ro(master.key)(Linux 文件名,非 macOS 的 master.key.file)+ ro(appsecret_<self>.enc);
  兄弟密文 / 用户 token / 未来文件全 deny-by-default。
- 双候选 store 全锁:lark-cli 对一个 env 值只可能开 `<resolved>/lark-cli` 或默认
  `$HOME/.local/share/lark-cli`,其字符校验规则难以逐字符复刻,故两个 store 都锁(transport
  都 deny+都 carve 自身;no-transport 都进 authority roots 零 carve-out),无论它选哪个兄弟+
  master 都 deny、自身可读。常态去重成一个。
- BOT_HOME 嵌套 authority:insideAuthority 的 BOT_HOME 例外改为按 root 作用,只豁免作为
  BOT_HOME 祖先的 root;嵌套在 BOT_HOME 内更具体的 authority root(如 LARKSUITE_CLI_DATA_DIR
  =<BOT_HOME> 时的 keystore)仍限制,堵深层 hostile user RW 逃过 dropAuthority 把 master.key
  解析成 readWrite 的逃逸。
- no-transport(apiOnly/HTTP 虚拟):keystore 纳入 computeNoTransportAuthorityRoots 冻结、
  零 carve-out(连自身 key 也不给)。该 store 加入仅在 platform==='linux'——macOS 用
  Library/… store,在 darwin 冻结 Linux 路径会改变 darwin 行为(no-transport workingDir 落
  ~/.local/share/lark-cli 会新失败),故严格按 platform 门控,darwin 与旧行为逐字节一致。
- 路径规范化:新增纯函数 cleanPosixAbsPath(Go filepath.Clean 语义 + 控制字符拒绝,对齐
  SafeEnvDirPath)+ resolveLarkCliLinuxStoreDir。worker 从冻结宿主环境取
  process.env.LARKSUITE_CLI_DATA_DIR,Clean 后经 canonicalNearestAncestor(realpath 最深存在
  祖先 + 回接不存在尾段,解 symlink 前缀而不在缺失叶子上抛错)规范化,作为显式
  FsPolicyContext.larkCliLinuxStore 传入;policy 保持纯函数不读 env。larkCliLinuxStorePath 对
  不可规范化 override 返回 null(不发 carve-out、store 保持 deny-by-default),绝不静默回退默认。
- 沙盒 child-pin:prepareDirectSandbox 新增 larkCliDataDir,worker 传 larkCliChildDataRoot
  (canonStore)——仅当 canonical store basename 仍是 lark-cli 才 --setenv dirname(store) 作
  child 的 LARKSUITE_CLI_DATA_DIR,使沙盒内 lark-cli 与 policy 落同一 canonical 命名空间;若
  lark-cli leaf 是 symlink 指向别名目录则不钉(child 回退默认 store,默认也已锁)→ symlink 数据
  根不再命名空间分叉。
- per-bot env:LARKSUITE_CLI_DATA_DIR 纳入 per-bot-env.ts 保留键——用户在 bots.json.env 配它
  会被拒 + 可诊断,不再被沙盒 host --setenv/--unsetenv 静默覆盖。

影响面:仅 Linux 且开 sandbox/readIsolation 的会话行为变化(收紧)。darwin 路径完全不变(新逻辑
均在 platform==='linux' 分支,含 no-transport authority roots 的 platform gate)。transport 拿
自身 key carve-out、no-transport 整个 keystore 冻结。PtyBackend/TmuxBackend 共用同一编译产物无差异;
所有 CLI 开沙盒都受同一收紧规则保护。prepareDirectSandbox 新增可选入参,旧调用兼容。

验证:
- pnpm build 绿。
- fs-policy 单测:自身 RO / 兄弟+token+未来文件 deny / 无关工具链不误伤;自定义 store rehome;
  双候选 store 全锁(transport+no-transport)+ default-only 去重;BOT_HOME 嵌套 authority;
  cleanPosixAbsPath 与 resolveLarkCliLinuxStoreDir 的「绝对 Clean/相对忽略/控制字符拒绝/`..` 归一」
  真值表;larkCliChildDataRoot 同名/别名 symlink/root/不可用输入;no-transport authority roots
  按 platform 门控 + darwin 与旧行为一致回归。per-bot-env 单测覆盖新保留键。
- bwrap 内核级 e2e:真 bubblewrap 下兄弟 appsecret + master.key 读不到、ls 不列兄弟、自身可读、
  store 不可写;LARKSUITE_CLI_DATA_DIR=<BOT_HOME> + no-transport + 敌意 user RW 直指 master.key
  仍全拒。
- 变异测试:去 store deny / 破 no-transport 门 / 碰错 master.key 文件名 / 去默认 store 锁 / 去
  child-pin basename 判定 / 去 platform 门控,各让对应用例(含内核级)翻红,确认测试有牙。
- live(真 sandbox + 真 lark-cli):默认 store auth 正常 + 兄弟内核级拦;自定义/含 `..`/symlink
  数据根 store 同样 auth 正常 + 兄弟拦;no-transport 下自身 key 亦被冻结。

Refs #701(该文档 PR 保持 docs,待本代码修复合入后 rebase 修正 Linux 密钥落点表述 + 首次
init 只能在宿主沙盒外终端执行)。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants