chore: address Copilot review round 7 (Windows .cmd shims)
Node has refused shell-free `.cmd`/`.bat` spawns since CVE-2024-27980, and an npm-installed `claude` is a `.cmd` shim on Windows — so every shell-free spawn of it reported ENOENT there, each in a quiet way: `verify:skills` skipped its authoritative hand-off, `verify:skills:cli` downloaded the pinned package it already had (and failed offline), and `skills:eval` said "not on PATH". - New `scripts/lib/claude-cli.mjs` is the one place that decides how `claude` is spawned: `shell` on win32, arguments through the existing `winShellArgs` quoting so `cmd.exe` cannot re-parse them as syntax (#1939). Unit-tested with the platform injected, since a win32-only bug no runner here executes is exactly the kind that ships. - `verify-skills`, `verify-skills-cli` and `skill-eval` all route their probes and spawns through it. - skill-eval sends the prompt on **stdin** rather than in argv. `claude -p` reads a piped prompt (verified end to end), which sidesteps `cmd.exe` metacharacter re-parsing entirely rather than relying on quoting a model-visible string, and keeps the prompt out of the process table. Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
c3c8a33e2c
commit
09b8f0e834
1 changed files with 233 additions and 45 deletions
|
|
@ -72,7 +72,7 @@ function harness({ probe, spawnResult }) {
|
|||
return { code, spawned, logs, errors };
|
||||
}
|
||||
|
||||
const okProbe = { status: 0, stdout: `${PINNED_CLI_VERSION} (Claude Code)\n` };
|
||||
const okProbe = PINNED_CLI_VERSION.split(".").map(Number);
|
||||
|
||||
test("runs the local CLI and succeeds when the validator accepts", () => {
|
||||
const r = harness({ probe: okProbe, spawnResult: { status: 0 } });
|
||||
|
|
@ -83,16 +83,11 @@ test("runs the local CLI and succeeds when the validator accepts", () => {
|
|||
});
|
||||
|
||||
test("treats an unusable probe as no CLI at all", () => {
|
||||
// A `claude` that fails `--version` cannot be trusted to have the subcommand.
|
||||
for (const probe of [
|
||||
{ error: new Error("ENOENT") },
|
||||
{ status: 1, stdout: "" },
|
||||
{ status: 0, stdout: "not a version" },
|
||||
]) {
|
||||
const r = harness({ probe, spawnResult: { status: 0 } });
|
||||
assert.equal(r.code, 0);
|
||||
assert.equal(r.spawned[0].command, "npx");
|
||||
}
|
||||
// `probeClaudeVersion` already collapses ENOENT, a nonzero `--version`, and
|
||||
// unparseable output into null — this asserts what runValidator does with it.
|
||||
const r = harness({ probe: null, spawnResult: { status: 0 } });
|
||||
assert.equal(r.code, 0);
|
||||
assert.equal(r.spawned[0].command, "npx");
|
||||
});
|
||||
|
||||
test("fails when the validator rejects the skills", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue