Skip to content

[fix] Use execFile instead of a shell string in commandExists() - #99

Merged
kmturley merged 1 commit into
mainfrom
fix/command-exists-no-shell
Jul 31, 2026
Merged

[fix] Use execFile instead of a shell string in commandExists()#99
kmturley merged 1 commit into
mainfrom
fix/command-exists-no-shell

Conversation

@kmturley

Copy link
Copy Markdown
Member

Summary

  • commandExists() was the one function in the local-execution surface that built a shell command string (exec(\command -v ${cmd}`)) instead of using execFile/execFileSync with an argv array, unlike every other command execution in this codebase — which has extensive commentary explaining why shell strings are avoided wherever a value could trace back to registry/package metadata. It's only ever called with literal values today ('dpkg'/'rpm'inManagerLocal.install()`'s Linux branch), so there's no live injection vector, but it was a landmine for the next contributor who calls it with something dynamic. Flagged in an internal spec-compliance audit.
  • Switched to execFile('which', [cmd], ...)which is a real executable (unlike command, a shell builtin with no standalone binary), so this avoids a shell entirely rather than just working around it.
  • Also adds commandExists()'s first test coverage.

Test plan

  • npm run check passes (format, lint, build, tests — 203/203)
  • New test: commandExists('node') → true, commandExists('this-command-should-not-exist-xyz123') → false (skipped on Windows, where commandExists isn't actually used and which isn't a standalone command)

🤖 Generated with Claude Code

commandExists() was the one function in the local-execution surface
that built a shell command string (exec(`command -v ${cmd}`)) instead
of using execFile/execFileSync with an argv array, unlike every other
command execution in this codebase - which has extensive commentary
explaining why shell strings are avoided wherever a value could trace
back to registry/package metadata. It's only ever called with literal
values today ('dpkg'/'rpm' in ManagerLocal.install()'s Linux branch),
so there's no live injection vector, but it was a landmine for the
next contributor who calls it with something dynamic. Flagged in an
internal spec-compliance audit.

Switched to execFile('which', [cmd], ...) - `which` is a real
executable (unlike `command`, a shell builtin with no standalone
binary), so this avoids a shell entirely rather than just working
around it. Also adds commandExists()'s first test coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kmturley
kmturley merged commit 02f7acd into main Jul 31, 2026
6 checks passed
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.

1 participant