Skip to content

fix: keep user argv out of cmd.exe on the Windows launcher path (#2196) - #2205

Merged
ranxianglei merged 2 commits into
masterfrom
2026-10-06_win-cmd-argv-fix
Oct 6, 2026
Merged

ranxianglei merged 2 commits into
masterfrom
2026-10-06_win-cmd-argv-fix

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Closes #2196

What was broken

The default npm Codex install is a codex.cmd shim, and its %* forwarding re-parses every user argument through cmd.exe's line parser. That parser has no escape mechanism: embedded double quotes split tokens (Please say "hello world" exactly arrived as four arguments), TOML -c values lost their quotes, %VAR% expanded, &|<>^() acted as command operators, and empty arguments vanished. Plain spaced prompts happened to survive, so the #679 space-truncation fix never exposed this. The issue's A/B against BILI_CLIENT_BIN=<codex.exe> confirmed the corruption lived in bili's shim launch path, not in Codex.

Fix strategy (remove the producer, not the symptom)

The producer of the corrupted bytes was re-feeding user argv into shell-syntax interpretation. Two complementary changes remove that mechanism:

  1. The codex path bypasses the shell entirely (win32 only): per PATH directory the native codex.exe wins over codex.cmd/.bat; a .cmd/.bat hit beside a trusted npm layout — <dir>/node_modules/@openai/codex named @openai/codex with a resolvable "codex" bin entry AND shim text referencing that package (a hand-written shim beside an unrelated tree cannot hijack) — is upgraded to node <official bin/codex.js>. This reproduces exactly what the shim does (vendor-binary lookup, env init, signal forwarding) while Node's own CreateProcess argv encoding carries every argument verbatim. Unrecognized layouts (yarn-classic .bin trees, pnpm store shims without the local link) keep the legacy cmd path under contract 2. The BILI_CLIENT_BIN override keeps its top precedence.
  2. Every remaining comspec wrap site fails loudly instead of mangling: planClientSpawn (launcher) and planDshSpawn (dsh channel — same mechanism family) now validate every token against a safe-set predicate (src/win-cmd.ts) before building the line; an unsafe token throws with position + class + actionable hint before any process starts. Direct-spawn .exe launches are untouched — Node encodes their argv losslessly itself. Refusal messages never echo token content (prompts routinely carry secrets; log-masking discipline).

Behavior changes (mandatory disclosure)

# Old New Why
1 Unsafe argv (embedded quotes, %VAR%, &|<>^(), empty arg, line breaks, whitespace-bearing odd trailing-backslash runs) was silently corrupted or interpreted on EVERY Windows .cmd/.bat/extensionless launch — all clients + dsh channel Launch aborts with an actionable error pointing at BILI_CLIENT_BIN / BILI_DSH_BIN; the client is never spawned Silent corruption is the bug; loud refusal is the contract. Safe argv produces byte-identical plans to before (regression-pinned as literals)
2 codex Windows resolution tried .cmd/.bat/.exe per directory (first match won) Per-directory order is now .exe/.cmd/.bat; a .cmd hit upgrades to node + official JS entry when the trusted layout is detected exe-first removes the shell layer when present; the upgrade preserves wrapper behavior losslessly
3 npm-shim codex launch: bili → cmd.exe → codex.cmd → node → codex.js (argv re-parsed at the cmd boundary) bili → node → codex.js (one hop) removes the re-parse point entirely

No wire-format, persistence, cache, retry, or session-binding changes. No kernel/ changes.

Config surface

None added. BILI_CLIENT_BIN (pre-existing override, highest precedence, unchanged semantics) is documented in CLIENTS.md as the power-user knob / temporary workaround. No new env vars, config fields, or CLI flags — nothing for CONFIGURATION.md to mirror.

Verification

  • Local (linux): typecheck clean; targeted suites green — new tests/win-cmd-argv.test.ts + fix-679 + dsh-refresh + dsh-native + fix-1292: 108 pass / 0 fail (7 win32-gated skipped locally); launcher.test.ts 269 pass / 0 fail. Full suite left to CI per local-test discipline.
  • Real Windows lane (windows-latest, NEW hard gate in ci-windows-codex.yml): full-corpus round-trip asserting the child's RECEIVED argv equals the caller array item-by-item through (a) detection + spawn of an official-wrapper-shaped entry and (b) the direct-spawn leg alone; detection against the real global @openai/codex install; the official wrapper's --help booting through plain node. Corpus covers the issue's acceptance list: empty arg, plain spaces, embedded double quotes, TOML -c values, JSON, Unicode, trailing backslashes, %COMSPEC%, !VAR!, &|<>^().
  • [bug] Windows: a space in the client or -e path truncates the launch (spawn shell:true, unquoted) — and silently kills compression when only the command is quoted #679 regression pins retained byte-identical for safe argv (launcher and dsh plans asserted as literal strings).

Review notes

  • The safe set deliberately allows ! (delayed expansion is opt-in; standard npm shims never enable it) and even-run trailing backslashes; the refused backslash case is specifically the MSVCRT closing-quote escape interaction for whitespace-bearing tokens.
  • quoteWinToken stays a pure formatter; its docstring now states callers must pre-validate. The old test titled "embedded double quotes stay bare (today's behavior preserved)" is reframed — that framing pinned the bug as intended behavior.
  • resolveClientCommand gained an optional platform parameter (default process.platform); both runLaunch call sites pass deps.platform ?? process.platform so tests can inject. Existing call sites/tests are untouched.

The default npm Codex install is a codex.cmd shim whose %* forwarding re-parses every user argument through cmd.exe's line parser — embedded double quotes split tokens, %VAR% expands, &|<>^() execute, empty args vanish. Plain spaced prompts survived, which is why the #679 space-truncation fix did not expose it.

- codex resolution (win32): per PATH dir the native codex.exe wins over the shim; a .cmd/.bat hit beside a trusted npm layout (node_modules/@openai/codex named @openai/codex with a resolvable codex bin entry, shim text referencing that package) upgrades to node <official bin/codex.js> — same vendor-lookup/env-init/signal-forwarding behavior, lossless CreateProcess argv encoding. BILI_CLIENT_BIN override precedence unchanged.
- every remaining comspec wrap site (launcher planClientSpawn + dsh-channel planDshSpawn) now refuses argv the line parser cannot carry verbatim with an actionable error before any spawn, instead of mangling or interpreting it. Direct-spawn .exe paths are untouched (Node encodes argv losslessly). Refusal messages name position + class, never token content (log-masking discipline).
- tests/win-cmd-argv.test.ts: predicate table, guard behavior incl. byte-identical safe-plan pins for #679, layout-detection cases, resolution-order cases, and win32-gated real round-trips asserting the child's RECEIVED argv equals the caller array item-by-item over the full corpus (empty arg, spaces, embedded quotes, TOML -c, JSON, Unicode, trailing backslashes, %COMSPEC%, !VAR!, metacharacters) — plus detection against the real global @openai/codex install and the wrapper's --help booting through plain node.
- CI: hard gate in ci-windows-codex.yml running the new suite on windows-latest.
- docs: CLIENTS.md (+zh) codex section on the new resolution order, pass-through contract, and workaround.
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-06_win-cmd-argv-fix (3b28155)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.2265-pr.2205.08a09d0

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr2205.tgz
npm install -g package

Install instructions are refreshed on each push.

… test (#2196)

The assertion message interpolated res.stderr while stderr was set to 'ignore' (null) — the interpolation threw unconditionally before the status check could report. Pipe stderr and assert.fail with a guarded message.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 PR #2205 review — Windows launcher argv 保真(修 #2196)

已拉取分支 2026-10-06_win-cmd-argv-fix(head 3b28155,base master@e5a0066)逐文件核对。8 文件 +689/−8;两个提交(fix: 主修 + test: 修 wrapper --help 探针的 eager-null-stderr 插值)。结论:技术上可合,建议人工合并(理由见末)。

① 是什么问题:Windows 默认 npm 装的 Codex 是 codex.cmd shim,其 %* 把用户 argv 重新送进 cmd.exe 的行解析器——该解析器无转义机制:嵌入双引号拆 token、%VAR% 展开、&|<>^() 当命令操作符、空参数消失。普通带空格提示词恰好能过,所以 #679 的空格截断修复没暴露它。

② 什么原因导致的:根因不是「引号没加对」,而是把用户 argv 重新喂回 shell 语法解释(producer 在 shim 启动路径)。quoteWinToken 只给「有空白且无引号」的 token 加引号,嵌入引号/无空白元字符/空串都无保护;.cmd/.bat/无扩展名拼成 comspec /d /s /c "<line>" + windowsVerbatimArguments:true。这是疏堵之辨里的「堵点」——在传输层再补一层引号/过滤只会变成新的 mask(#1039 教训)。

③ 怎么修复的(两层,均指向移除 producer):

  1. codex win32 绕开 shell:resolveCodexWin32 按 PATH 目录优先 .exe(原生直接 spawn);.cmd/.bat 命中时经 resolveCodexOfficialJsEntry 识别可信 npm 布局(<dir>/node_modules/@openai/codex 且 package.json name=@openai/codex + 可解析 "codex" bin + shim 文本引用该包,防手写 shim 劫持),升级为 node <官方 bin/codex.js>——Node 自身 CreateProcess argv 编码逐字携带参数。非可信布局(yarn-classic/pnpm store)回落旧 cmd 路径。BILI_CLIENT_BIN 仍最高优先。
  2. 所有剩余 comspec 包裹点响亮拒绝而非篡改:planClientSpawn(launcher)+ planDshSpawn(dsh channel,同机制族)在拼行前用 winCmdUnsafeToken(新模块 src/win-cmd.ts)校验每个 token,不安全则抛带位置+类别+可操作提示的错误,在任何进程启动前。直连 .exe 不受影响(Node 无损编码)。拒绝信息不回显 token 内容(日志脱敏纪律)。策略符合 §7.3「疏不堵」:移除重解析点,而非在传输层改字节。

④ 是否有回归问题——逐项过既有行为:

  • 安全 argv 字节不变:tests/win-cmd-argv.test.ts 把 [bug] Windows: a space in the client or -e path truncates the launch (spawn shell:true, unquoted) — and silently kills compression when only the command is quoted #679 计划串作为字面量钉死(safe argv 产出与旧版 byte-identical),launcher/dsh 两处都断言。✓
  • 行为变更(已在 PR 表披露):(1) 不安全 argv 从「静默损坏」→「响亮拒绝」,影响所有 Windows .cmd/.bat/无扩展名客户端 + dsh channel;(2) codex win32 解析顺序 .cmd/.bat/.exe→.exe/.cmd/.bat;(3) npm-shim codex 从 bili→cmd→codex.cmd→node→codex.js(bili→node→codex.js 一跳)。三者均为有意变更,披露完整。✓
  • 非回归确认:非 win32 完全不受守卫影响(有测);.exe 直连从不触发守卫(有测);BILI_CLIENT_BIN 仍压过 codex 分支(有测);非 codex 客户端不受 win32 分支影响(有测)。
  • 错误路径干净:planClientSpawn 在 runClient 内同步 throw,被 runLaunch 的 try/catch 捕获 → 单行可操作消息(bili: failed to launch codex: …),非堆栈崩溃;finally 正常清理 overlay/tmp。✓
  • 唯一需留意(非阻塞):codex 走「主动绕开」,其余客户端仅「被动守卫」——不对称是有意的(codex 是已知坏例,其余由守卫兜底),合理。

⑤ 是否破坏缓存:不涉及会话/压缩/重试/去重键。唯一缓存是进程内 codexNoDaemonCache(key=[command,...prefixArgs]),node+entry 形态下 key 变化属探针缓存,非用户数据缓存。✓

⑥ 是否改了设计方向:不涉及架构转向。新增聚焦模块 win-cmd.ts 承载安全契约,接入两个既有包裹点,codex 专属 win32 解析——边界清晰,未污染数据流。✓

⑦ 是否引入新配置:无新增 env/config/字段/flag。BILI_CLIENT_BIN 为既有覆盖项(CONFIGURATION.md:333 已存在),PR 仅在 CLIENTS.md 的 codex 段补了 workaround/power-user 说明,en+zh 同步。符合配置面纪律(未自造新形状)。✓

测试有效性(④质量关):有牙,能兜底——safe argv 字节级回归钉死;不安全输入断言「spawn 未被调用」;专门一条断言拒绝消息不含 token 内容(脱敏);issue 原始复现(runClient 层)被钉住;真实 windows-latest 往返断言子进程收到的 argv 与调用方数组逐项相等,并新增硬门禁(ci-windows-codex.yml,装真 @openai/codex 后跑,无 continue-on-error)。fix-679 里那句「embedded double quotes stay bare (today's behavior preserved)」已重构为「formatter-level,rejected upstream since #2196」——正确纠正了把 bug 钉成期望的旧桩。

CI / 基线:head 3b28155 全部 19 项 check success(含两条 windows-latest 24/22 lane);mergeable: true / clean;base 即当前 master(behind 0,非陈旧基线)。本地未重复跑全量(交 CI,符合本地测试纪律)。

判定:代码逻辑、边界、错误处理、测试、文档、披露均到位,无阻塞缺陷。建议人工合并——按 §7.4 自动合入门禁,本 PR 触及「跨所有 Windows cmd 路径客户端的行为变更 + 安全相关(消除 &|<>^() 命令注入面)+ 解析顺序默认值语义变化」,属 human-only 档,不宜 auto-merge。技术层面已就绪,可合。

@ranxianglei
ranxianglei merged commit 84b47ea into master Oct 6, 2026
19 checks passed
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.57-master.84b47ea (rolling master tag).

npm install -g billion-context@0.0.57-master.84b47ea  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

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.

[Bug][Windows launcher] 经 codex.cmd 启动破坏 argv:嵌入引号拆分、%变量% 展开和 & 被解释

1 participant