fix(security): harden Windows subprocess argument handling - #2252
Conversation
|
Claude Security Review: no high-confidence findings. (run) |
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Looks good
This is a clean security/correctness fix. On Windows, spawn previously defaulted to shell: true and the resolveCommand helper concatenated command + args with .join(' '), so any argument containing shell metacharacters (&, |, >, quotes, spaces, etc.) would be re-parsed by cmd.exe — a real command-injection surface when paths/user input flowed into e.g. git, uv, or npm invocations. Replacing all five wrappers with cross-spawn eliminates that path entirely while keeping .cmd/.bat shim resolution working on Windows.
Things I checked:
- Verified no remaining callers pass
shell: true(only removals were the redundantshell: falseoverrides insrc/cli/operations/init/files.ts, correctly cleaned up here). SubprocessOptions.shellis removed from the exported interface; no external consumers were relying on it (greped forSubprocessOptionsand.shellreferences).cross-spawnis amodule.exports = spawndefault with.syncattached;esModuleInterop: trueis set intsconfig.build.json, soimport crossSpawn from 'cross-spawn'andcrossSpawn.sync(...)both resolve correctly.encoding: 'utf-8'is passed through untouched tospawnSyncinside cross-spawn, sorunSubprocessCaptureSyncstill returns strings.- The new regression test exercises
runSubprocessCaptureend-to-end with a real subprocess (no mocking), which is exactly the right level.
Telemetry: N/A — this is a security refactor, not a new user-facing feature.
Nothing blocking. Ship it.
0e1ee91 to
2e71cf8
Compare
|
Claude Security Review: no high-confidence findings. (run) |
2e71cf8 to
7a2d8de
Compare
Package TarballHow to installgh release download pr-2252-tarball --repo aws/agentcore-cli --pattern "*.tgz" --dir /tmp/pr-tarball
npm install -g /tmp/pr-tarball/aws-agentcore-0.28.1.tgz |
|
Claude Security Review: no high-confidence findings. (run) |
Coverage Report
|
7a2d8de to
b0b568e
Compare
|
Claude Security Review: no high-confidence findings. (run) |
The subprocess helpers spawned with shell: true on Windows, which let the shell interpret argument contents. They now delegate spawning to cross-spawn, which resolves Windows .cmd/.bat wrappers and escapes arguments so their contents reach the child process intact rather than being interpreted. Nothing spawns through a shell, and the shell option is removed from SubprocessOptions. - Promote cross-spawn to a direct dependency; add @types/cross-spawn - runSubprocess, checkSubprocess, runSubprocessCapture, runSubprocessCaptureSync, checkSubprocessSync now call crossSpawn / crossSpawn.sync - Behavioral test: an argument containing shell metacharacters arrives at the child as a single argv element
b0b568e to
872eb92
Compare
|
Claude Security Review: no high-confidence findings. (run) |
Summary
On Windows, the subprocess helpers spawned with
shell: true, which let the shell interpret the contents of arguments passed to a child process. This hardens that path.The five subprocess helpers now delegate spawning to
cross-spawn, which resolves Windows.cmd/.batwrappers and escapes arguments so their contents reach the child process intact rather than being interpreted by a shell. Nothing spawns through a shell, and theshelloption is removed fromSubprocessOptions.cross-spawn was already present transitively (via eslint/vite); this promotes it to a direct dependency and adds
@types/cross-spawn. Delegating also removes the hand-rolled Windows quoting and PATHEXT resolution.Changes
cross-spawnto a direct dependency; add@types/cross-spawnrunSubprocess,checkSubprocess,runSubprocessCapture,runSubprocessCaptureSync,checkSubprocessSynccallcrossSpawn/crossSpawn.syncshellfromSubprocessOptionsand the now-redundantshell: falseininitGitRepoTesting
tsc --noEmitclean, eslint clean, 18 subprocess unit tests passOut of scope (follow-up)
Other callers (
codezip-dev-server.ts,container.ts,update/action.ts,import/phase2-import.ts, and others) still spawn directly and bypass these hardened helpers. Routing them through the helpers closes the same class of issue everywhere.