-
Notifications
You must be signed in to change notification settings - Fork 113
fix(windows): route .cmd shims through cmd.exe in dev and perf runners #391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
f17b136
1808971
a1fe51c
b74d12d
e8f8d2f
7b22021
d30c054
c556368
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ const VERSION = require('../package.json').version; | |
| const PACKAGE_DIR = path.join(__dirname, '..'); | ||
| const discovery = require('../lib/discovery'); | ||
| const transforms = require('../lib/adapter-transforms'); | ||
| const { resolveExecutableForPlatform } = require('../lib/utils/command-parser'); | ||
| const { resolveExecutableForPlatform, planShimSpawn, shimSpawnOptions } = require('../lib/utils/command-parser'); | ||
|
|
||
| // Valid tool names | ||
| const VALID_TOOLS = ['claude', 'opencode', 'codex', 'cursor', 'kiro']; | ||
|
|
@@ -72,7 +72,9 @@ const WINDOWS_DIRECT_EXEC = /\.(exe|com)$/i; | |
| // to execFileSync fails with EINVAL rather than running it. | ||
| const WINDOWS_BATCH_SHIM = /\.(cmd|bat)$/i; | ||
| // Arguments safe to hand to cmd.exe: no whitespace, no shell metacharacters. | ||
| const CMD_SAFE_ARG = /^[A-Za-z0-9@._:\\/+-]+$/; | ||
| // Ends with (?![\s\S]) rather than $, which in JavaScript also matches before a | ||
| // trailing newline - so 'plugin\n' would otherwise pass as safe. | ||
| const CMD_SAFE_ARG = /^[A-Za-z0-9@._:\\/+-]+(?![\s\S])/; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an auto review done by revuto. This change is a no-op, and the comment justifying it states something that isn't true of JavaScript. In JS, /^a$/.test('a\n') // false <- JS
/^a$/m.test('a\n') // true <- only with the m flagre.match(r'^a$', 'a\n') # True <- Python doesSo Separately, No objection to keeping |
||
|
|
||
| /** | ||
| * Pick the Claude Code executable from a `where.exe claude` result. | ||
|
|
@@ -100,23 +102,21 @@ function pickClaudeExecutable(platform, whereOutput) { | |
| /** | ||
| * Build the file and argv for one Claude Code invocation. | ||
| * | ||
| * A batch shim cannot be handed to execFileSync at all, so it is launched | ||
| * through cmd.exe. cmd.exe re-parses its command line, so every argument is | ||
| * checked first: callers only ever pass literal subcommands and validated | ||
| * `<plugin>@<marketplace>` ids, so anything else is a bug rather than a string | ||
| * to escape. The quoting mirrors how Node wraps a shell command - /s strips the | ||
| * outer quote pair, leaving the quoted shim path as the first token. | ||
| * planShimSpawn does the cmd.exe routing a batch shim needs, on win32 only. | ||
| * Every argument is checked against a stricter rule than that quoting requires: | ||
| * callers only ever pass literal subcommands and validated | ||
| * `<plugin>@<marketplace>` ids, so anything carrying whitespace or a shell | ||
| * metacharacter is a bug rather than a string to escape. The platform parameter | ||
| * exists so the win32 path stays testable off Windows. | ||
| */ | ||
| function claudeSpawnPlan(executable, args, comspec) { | ||
| if (!WINDOWS_BATCH_SHIM.test(executable)) { | ||
| return { file: executable, args }; | ||
| } | ||
| const unsafe = args.find(arg => !CMD_SAFE_ARG.test(arg)); | ||
| if (unsafe !== undefined) { | ||
| throw new Error(`Refusing to pass ${JSON.stringify(unsafe)} to cmd.exe`); | ||
| function claudeSpawnPlan(executable, args, comspec, platform = process.platform) { | ||
| if (platform === 'win32' && WINDOWS_BATCH_SHIM.test(executable)) { | ||
| const unsafe = args.find(arg => !CMD_SAFE_ARG.test(arg)); | ||
| if (unsafe !== undefined) { | ||
| throw new Error(`Refusing to pass ${JSON.stringify(unsafe)} to cmd.exe`); | ||
| } | ||
| } | ||
| const command = [`"${executable}"`, ...args].join(' '); | ||
| return { file: comspec || 'cmd.exe', args: ['/d', '/s', '/c', `"${command}"`], verbatim: true }; | ||
| return planShimSpawn(executable, args, { comspec, platform }); | ||
| } | ||
|
|
||
| let claudeBinCache; | ||
|
|
@@ -144,7 +144,7 @@ function claudeExecutable() { | |
| */ | ||
| function claudeSpawn(args, options) { | ||
| const plan = claudeSpawnPlan(claudeExecutable(), args, process.env.comspec); | ||
| return execFileSync(plan.file, plan.args, plan.verbatim ? { ...options, windowsVerbatimArguments: true } : options); | ||
| return execFileSync(plan.file, plan.args, shimSpawnOptions(plan, options)); | ||
| } | ||
|
|
||
| function copyDirRecursive(src, dest) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an auto review done by revuto.
This
### Securityentry documents a vulnerability that did not exist, and states an incorrect fact about JavaScript regex semantics.Without the
mflag, JS$matches only at end of Input (ECMAScriptAssertion :: $).core@agentsys\nwas rejected by the base regex:A published
### Securitynote asserting a bypass that was never reachable is worth correcting before release — it pollutes downstream security triage, and readers will take the JS-$claim at face value.The second half of the sentence is fine and worth keeping:
planShimSpawngenuinely does need the CR/LF refusal, because unlikebin/cli.jsit has no allowlist and would otherwise let'bench\ncalc'through to the cmd.exe command line. Suggest demoting this to### Fixed, scoping it to theplanShimSpawnCR/LF guard plus thequoteForCmdReDoS fix (which is real — CodeQL alert 111), and dropping thebin/cli.jsbypass claim.