Skip to content

fix(shells): declare meta tool names and refuse a host-binary argv - #565

Closed
tietoumiao wants to merge 1 commit into
T-Auto:mainfrom
tietoumiao:fix/484-shell-surface-argv
Closed

tietoumiao wants to merge 1 commit into
T-Auto:mainfrom
tietoumiao:fix/484-shell-surface-argv

Conversation

@tietoumiao

@tietoumiao tietoumiao commented Nov 1, 2026 •

Copy link
Copy Markdown

Closes #484

What was wrong

Two defects in the declared shell surface:

  1. dsh-plugin.json:140-142 declared three tool names (batch, load_sdk, discover_tools) that
    no x-tool-namespaces entry at :68-104 covered, so the manifest advertised a surface the
    validator's namespace rule could not reconcile.
  2. src/shells/session-execution.ts:41 passed confinement.argv through unchanged. When the host
    hands the Electron binary as the argv head, the plugin spawned a second GUI instance that
    exits 0 without running a shell — a silent no-op the model cannot tell from success.

The change

  • dsh-plugin.json — the three meta tools get their own x-tool-namespaces entry, so the declared
    names and the declared namespaces agree.
  • src/shells/host-runner.ts (new, 50 lines) + src/shells/session-execution.ts — a confined argv
    whose head is the host Electron binary is refused with EXECUTOR_UNAVAILABLE and an actionable
    message instead of being spawned.
  • scripts/validate-manifest.mjs — the namespace rule plus a components ⊆ namespaces check (the
    one file in the commit not literally named by the issue; without it npm run verify rejects the
    new namespace entry).
  • test/manifest.test.mjs, test/shells-host.test.mjs, docs/CHANGELOG.md.

Deliberately not done: no trampoline rewrite of the host's windows-acl-runner.js (host-internal),
and the bashRefusalValue rendering half fixed by #520 is untouched.

Validation

$ node test/run.mjs manifest
  ok   the manifest advertises ops_batch but no x-tool-namespaces entry declares it
dsh-ops manifest: all 14 checks passed

$ node test/run.mjs shells-host
  ok   the session executor refuses the host binary instead of spawning a second GUI instance (#484)
dsh-ops shells-host: all 25 checks passed      (pristine HEAD: 23)

$ node scripts/validate-manifest.mjs
validate-manifest: all 108 checks passed

# revert-red (a) dsh-plugin.json only
dsh-ops manifest: 1 of 14 check(s) failed
# revert-red (b) session-execution.ts only
FAIL the session executor refuses the host binary instead of spawning a second GUI instance (#484)
  • 撤掉本次实现后变红的检查:上面两条
  • docs/CHANGELOG.md ## [Unreleased]:已加(EN+ZH)
  • 面向模型的描述 / 参数:声明层补三个命名空间条目;未改任何工具的描述文本或参数,schema 字节不变
  • CI:待维护者批准后由 pull_request 事件触发,届时回填 CI required 的 run URL

Environment

  • OS / 架构:Windows 11 x64
  • Node:24.16.0
  • 基于上游 main 的 commit:065cd7f
  • 安装通道:源码 tarball + npm ci --ignore-scripts

未验证 / 风险披露

  • 没有 Electron 运行时:argv 守卫通过注入 HostRuntime + 模拟 process.versions.electron 证明;
    "宿主是否真的把 [process.execPath, runner] 交下来"取自 issue 的现场,未再次推导。
  • scripts/validate-manifest.mjs 的改动请重点看一眼:它把"声明必须自洽"变成可执行规则。
  • check:size 541→542 文件,均 ≤1000 行。

CI

$ gh run view 41338681274 --json url,event,conclusion --jq '"(.url) (.event) (.conclusion)"'
https://github.com/T-Auto/dsh-ops/actions/runs/41338681274 pull_request action_required

Declare the progressive-disclosure, PTC batch and PTC SDK loader tools in their own x-tool-namespaces entry so the manifest advertises the surface the plugin publishes, and assert that every x-components name reaches a namespace.

Refuse a confined argv whose head is the host Electron binary: without ELECTRON_RUN_AS_NODE it opens a second GUI instance that exits 0 without running the command, which was indistinguishable from success. Report EXECUTOR_UNAVAILABLE instead of a silent pass.

@Haoming9527 Haoming9527 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed 519ea68 against the already prepared #543 and #572. The missing manifest inventory and silent Electron startup are valid problems; there are two concrete gaps in this alternative guard.

  • [P2] Normalize the Windows executable path before comparing it (src/shells/host-runner.ts, argv[0] !== host.execPath). Case and slash variants identify the same executable on Windows. The guard returns undefined for an uppercase/forward-slash spelling of process.execPath; I also launched a harmless Node probe through that spelling and confirmed it resolves to the same image. An equivalent host runner path can therefore evade the refusal.
  • [P2] Do not accept an ambiguous Node-mode environment (nodeModeRequested). With { ELECTRON_RUN_AS_NODE: '0', electron_run_as_node: '1' }, .some() returns true, but an actual Windows child receives ELECTRON_RUN_AS_NODE=0. The guard therefore admits a runner whose effective environment does not request Node mode. Require one unambiguous effective setting and test case-colliding keys; POSIX matching should remain case-sensitive.

These controls were run against the exact helper from this PR. The comparison through #543's execution boundary rejected both inputs with zero spawns. No live Electron GUI was used, so this evidence establishes the guard behavior and Windows path/environment semantics, not Desktop runtime recovery.

Please coordinate with #543 for the runner guard and #572 for the manifest inventory. #572 validates exact coverage of the production capability catalogue and documents that inventory does not itself publish or authorize tools. Applying both versions would duplicate the namespace entries and overlap the runner logic. The integration path is #557; this PR remains open for author revision rather than being silently replaced. No CI approval or merge has been performed as part of this review.

@Nicholas023 Nicholas023 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

审查结论:REQUEST_CHANGES — 建议关闭(或只保留 components ⊆ namespaces 一条)

manifest 半边是对的,但 runner 守卫的两个谓词各有可复现缺陷。

1. Windows 路径比较未归一化,四类拼写全部绕过(阻断)

src/shells/host-runner.ts:48 是 argv[0] !== host.execPath 的字符串比较。用真实 process.execPath 逐变体注入:

text 变体 解析到同一映像 守卫拒绝 原样 是 是 全大写 是 否 正斜杠 是 否 8.3 短名 NICHOL~1 是 否 \\?\ 扩展前缀 是 否

fs.realpathSync.native 对这四类都判 sameImage=true,且 spawn 实测成功。注意 path.resolve + toLowerCase 只能修前两类,修不了 8.3 与 \\?\ —— 只有 realpath 比较能覆盖。

2. POSIX 上是真 fail-open(维护者这条的因果判断有误)

nodeModeRequested() 无条件 key.toUpperCase()。POSIX 环境名大小写敏感、Electron 的 getenv("ELECTRON_RUN_AS_NODE") 也大小写敏感,所以 {electron_run_as_node:"1"} 在 Linux/macOS 会被放行,而 Electron 根本看不到该变量 → 仍然开第二个 GUI 实例。

同时给出一个反证:Electron 源码 shell/common/process_util.cc 用 base::Environment::HasVar(kRunAsNode),electron_main_win.cc 用 getenv_s(...) != 0 —— 变量存在即生效,与值无关。所以 ELECTRON_RUN_AS_NODE=0 也会进 Node 模式,本 PR 的 value === '1' 是过严(fail-closed 误杀),不是放行。而 test/shells-host.test.mjs:370 恰好把 POSIX 上的错误行为钉死了。

3. manifest 部分与 #572 重复

两者都补 batch/load_sdk/discover_tools。朴素合并会产生 4 个 namespace、3 个工具各声明两次,而本 PR 自己新增的「declared by exactly one namespace」规则会把这种重复判红。

已核实为真的部分

  • manifest 14/14、shells-host 25/25、validate-manifest 108/108
  • 两条 revert-red 都真红
  • 0.5.0 基线确实缺三个 namespace 条目(x-tool-namespaces 仅 2 条目 / 17 工具)

处置建议

关闭。 唯一有价值的是 components ⊆ namespaces 校验规则,请单独 rebase 到 #572。

@T-Auto T-Auto closed this Nov 2, 2026
@tietoumiao

Copy link
Copy Markdown
Author

Closing — #572 landed the namespace inventory in main (#557), so the manifest half here is redundant. The review's two counter-examples are accepted: path identity needs realpath, not string comparison, and the POSIX electron_run_as_node casing made the guard fail open. The one piece that may still be worth a small follow-up is the components ⊆ namespaces validator rule; I will propose it separately against the post-#572 tree rather than carry this branch.

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.

受限档位下 ops_bash 静默失败:命令被拒却返回 exit 0、无输出

4 participants