Skip to content

fix: execute hook-mode rewrites through the shell seam (Windows/ENOENT + sandbox bypass) - #4

Open
xelr233 wants to merge 16 commits into
sleepinginsummer:mainfrom
xelr233:pr/shell-seam
Open

xelr233 wants to merge 16 commits into
sleepinginsummer:mainfrom
xelr233:pr/shell-seam

Conversation

@xelr233

@xelr233 xelr233 commented Sep 12, 2026

Copy link
Copy Markdown

摘要

hook 模式此前直接 spawn(['/bin/bash', '-c', rewritten])Windows 上没有 /bin/bash,替换执行每次都 ENOENT,catch 又静默 return next()——于是命令照常按原样跑、rtk 根本没见过它、也没有任何报错。实测:插件自己的压缩统计定格在 3 次调用不动,rtk gainTotal commands 一条都不涨。

同时,直接 spawn 在任何平台都绕过了部署的 shell 装配与沙箱:shell 启动方式(如 Git Bash 的 --login / CHERE_INVOKING)、权限边界、输出预算全都丢了。

改为走挂载的 ctx.shell 缝执行。

变更

  • tools/execute 短路改为 shell.resolve(...) + shell.run(...);删除自建的 spawn / Promise.race 超时 / 手动 readFrom 读取逻辑(executor 本就拥有这些机制)。
  • 每次调用解析并传 sandboxPolicy(与 tool-bash 相同做法:sandboxPolicy.resolve({ session }))。这是必须的——会 confine 的 executor 拒绝没有策略的 spec(实测 Git Bash executor 会因此每条命令 exit 256 崩溃)。
  • 把 executor 的 sandbox 判定(mode / denied / enforcement / runnerFailed)按 bash schema 回传,被拒的命令不再看起来像普通失败。
  • inject 增加 'shell''sandboxPolicy':服务缺失时插件驻留等待,而不是静默降级。
  • 头部注释与 README 的 hook 模式说明同步;删掉已被本次修复消灭的“绕过 DSH 沙箱”警告。

为什么这样更好(而不是适配某个 shell)

ctx.shell 本身就是 seam:Linux/macOS 上是 bash-sandbox/bash-local,Windows 上是 pwsh-* 或 git-bash 插件的 GitBashExecutor。走 seam 意味着

  • 不改一行 git-bash 专属代码,就自动拿到它的 bash.exe --login + CHERE_INVOKING=1 启动方式;
  • read-only / workspace-write / danger-full-access 语义与 exec 器预算自动跟随;
  • 将来换成别的 shell executor 也无需再改插件。

实测(本机 DSH web + rtk 0.34.3 + dsh-plugin-git-bash 0.3.2)

命令 结果
ls -la rtk gain 计数 +1,输出为 rtk 紧凑格式
ls -la | wc -l 计数 +1,管道正常
echo hi && ls | head -2 计数 +1,复合命令正常
grep -n ... | head -3 计数 +1
find . -maxdepth 1 -name '*.md' 计数 +1,输出为 rtk 的树状格式

/rtk show 的压缩统计开始正常累计(修复前定格不动):stats: 5 call(s), 3 compacted, 2537 char(s) saved

测试

fake ctx 增加 shell 缝记录器;原先断言 spawn 形式的三个 execute 用例改为断言到达 executor 的请求,并新增:策略转发、session cwd、sandbox 判定透传三个用例。

ℹ tests 49
ℹ pass 49
ℹ fail 0

评审提示:diff 范围

本 PR 基于 main,而 main 上还没有 #1 / #2 / #3 的内容,所以 GitHub 显示的 diff(8 文件 / +1768)包含这三个 PR 的全部改动,属于预期。真正要评审的只有最后一个提交 02f19d4src/host.jstest/hook.test.jsREADME.md)。

#1#3 合入后,本 PR 的 diff 会自动收窄到该提交;也可以直接以本 PR 取代 #2 中的短路执行部分。

xelr233 added 16 commits August 15, 2026 21:01
verify/show only read the cached probe state, which stays undefined
until the first bash call, so an installed rtk was always reported as
"not probed yet" (the verify hint even told users to re-run a command
that never probes). Force a fresh probe (cache bypass) in both commands
and report the resolved path or NOT FOUND.

Also fix the npm test script for Node 24 (node --test test/ fails with
MODULE_NOT_FOUND) and add command tests covering verify/show probing.
…EADME, fix mode comment

F1: bound state.hookPlan / state.hookExecuted to HOOK_PLAN_MAX (64) entries,
evicting the oldest beyond the cap, plus defensive hookPlan cleanup in
tools/post-execute so a planned-but-never-executed call leaks nothing.
F2: propagate readStream spillPath into bash stdout/stderr objects.
F3: reconcile README blockquote with the hook-mode exception.
F4: update stale DEFAULT_CONFIG.mode comment to include 'hook'.
Hook mode spawned `['/bin/bash', '-c', rewritten]` directly. On Windows
there is no /bin/bash, so every replacement died with ENOENT and the
catch fell through to next() — the command silently ran natively, rtk
never saw it, and nothing surfaced the failure. On every platform the
direct spawn also bypassed the deployment's shell setup and its sandbox.

Execute through the mounted `ctx.shell` seam instead:

- the executor behind it owns the shell itself (Bash, Git Bash with its
  --login/CHERE_INVOKING handling, pwsh), the privilege boundary, and the
  output budgets, so the rewrite now works wherever the bash tool works;
- the sandbox policy is resolved per call exactly as the bash tool does
  (an executor that confines refuses a spec without one) and travels with
  the request, so read-only / workspace-write / danger-full-access hold;
- the executor's sandbox verdict is passed back in the result, so a denied
  command reports it instead of looking like a plain failure.

`shell` and `sandboxPolicy` join the inject list so the plugin parks until
both services exist rather than silently degrading.

Tests: the fake ctx gains a shell seam recorder; the three execute tests
that asserted the spawn form now assert the request that reached the
executor, plus new cases for policy forwarding, session cwd, and the
pass-through sandbox verdict. 49/49 pass.
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.

1 participant