fix(sandbox): sandbox=true 下 skill manifest 不可读导致 skill show 报 not found - #1036
fix(sandbox): sandbox=true 下 skill manifest 不可读导致 skill show 报 not found#1036xiaoxueSunn wants to merge 1 commit into
Conversation
… works under sandbox=true sandbox=true + skillInjection=prompt made `botmux skill show <name>` return "skill manifest not found" for every user skill: the file sandbox never exposed `<dataDir>/skill-manifests/<sessionId>.json`, so the in-sandbox read EACCES'd and readSessionSkillManifest — which only distinguished existsSync — collapsed the permission error to null, surfaced as not-found. User-skill bodies were unreadable inside the sandbox; the manifest was fine outside it. Fix (matches the reporter's requested approach, minimal scope): - worker.ts: add ONLY this session's own manifest file to mandatoryReadOnlyPaths, canonicalized so a symlinked HOME (/home/u -> /data00/home/u) still matches. Scoped to <sessionId>.json (not the whole dir) so a task can't read other sessions' manifests, and built from config.session.dataDir (the exact path the store writes) rather than the possibly-unset SESSION_DATA_DIR. - manifest-store.ts: readSessionSkillManifest now returns null ONLY on ENOENT; a permission/read error throws SkillManifestReadError and corrupt JSON throws SkillManifestParseError, so a sandbox misconfig no longer masquerades as not-found. - cli-session-command.ts: map those errors to distinct, diagnosable messages (exit 1) while a genuinely-absent manifest stays not-found (exit 2). Adds regression tests: absent -> null, corrupt -> throws, unreadable -> throws; and CLI-level corrupt vs absent messaging. Co-Authored-By: Claude <noreply@anthropic.com>
|
感谢这个 PR,问题定位和最小化改动的思路都很清楚,安全边界(只放行本 session 单个 1)🔴 主要问题:manifest 放行了,但 skill 正文(body)仍未放行 →
|
问题
sandbox=true+skillInjection=prompt时,任务内执行botmux skill show <name>对所有用户 Skill 统一返回:但 manifest 文件在沙箱外用同一 session id 能读到,且执行时该文件确实已存在(reporter 提供:session
66a95046-...,manifest 于16:02:58已生成,16:07:30任务内连续 5 次skill show全部 not found)。因此这里的 "not found" 实际是沙箱不可读,而非文件不存在。根因
两处叠加:
manifest-store.ts写到<config.session.dataDir>/skill-manifests/<sessionId>.json,但沙箱从未 re-expose 它 → 沙箱内读取 EACCES。readSessionSkillManifest()只用existsSync判断,读失败一律catch → 返回 null,把权限错误吞成了"不存在",最终显示为 not found,掩盖了沙箱配置缺陷。改动(对应 reporter 的方案,最小范围)
worker.ts:把仅当前 session 自己的 manifest 文件加入mandatoryReadOnlyPaths,只读;用canonical()规范化,覆盖/home/u → /data00/home/u这类 symlink HOME(验收 fix(adopt): macOS 上 /adopt 也能扫到 CLI 会话 #6)。只放行<sessionId>.json单文件、不放整个目录 → 任务读不到别的 session 的 manifest(验收 Codex 适配问题 #2/fix(cli): 修复 claude-code 在 root 账户下启动失败 #3)。路径由config.session.dataDir构造(store 实际写入路径),不依赖可能未设的SESSION_DATA_DIR。manifest-store.ts:readSessionSkillManifest()仅 ENOENT 返回 null;权限/读错误抛SkillManifestReadError,JSON 损坏抛SkillManifestParseError(验收 fix(cli): CoCo writeInput 改用 paste-buffer + bracketed paste #4/fix(tmux): exit copy-mode before forwarding input #5,错误类型可区分)。cli-session-command.ts:上述两类错误映射成可诊断信息 + exit 1;真正不存在仍是 not found + exit 2。不扩大对 Botmux 配置 / 凭证 / 其他任务数据的访问面(验收 #8)。
改动文件:
src/worker.ts、src/core/skills/manifest-store.ts、src/core/skills/cli-session-command.ts+ 两个测试,共 +117 / -9。验证
SkillManifestParseError;present-but-unreadable(chmod 000)→ 抛SkillManifestReadError(root 下自动跳过);CLI 层损坏 vs 缺失的消息与退出码区分。skill-manifest-store/skill-cli-commands/session-skill-runtime/session-skill-injection/session-skill-manifest-resolution/skill-resource-reader/plugin-cli-generation/fs-policy(70)。tsc --noEmit全量无报错。仍未验证
sandbox=true真机端到端:skill show读到完整 Skill 正文;~/.dsh/manifest 只读可读、其他 session manifest 被拒fs-policy-bwrap.e2e/sandbox-mask-manifest的 bwrap 用例对现有任务
沙箱策略在任务创建时冻结,本修复只对新任务生效;已创建任务需新建任务或重新生成沙箱策略。修复上线后,reporter 临时热更新的
sandboxPaths.readOnly: .../skill-manifests(会放大到全目录元数据)应撤销。🤖 Generated with Claude Code