Skip to content

fix(cli): close shell injection via crafted staged filenames (security, #87) - #98

Merged
fusengine merged 2 commits into
mainfrom
fix/shell-injection-staged-content
Aug 3, 2026
Merged

fix(cli): close shell injection via crafted staged filenames (security, #87)#98
fusengine merged 2 commits into
mainfrom
fix/shell-injection-staged-content

Conversation

@fusengine

Copy link
Copy Markdown
Owner

Summary

  • Security fix: closes arbitrary command execution via a crafted staged filename in harness check, reported in Arbitrary command execution via crafted staged filenames in harness check #87 by @VikramNehreTR.
  • stagedContent()/stagedFiles() (src/cli/run.ts) now use execFileSync with an argv array instead of an interpolated shell string — a staged filename can no longer reach a shell, regardless of its content (backticks, $(...), etc.).
  • Non-regression test (test/staged-content-shell-injection.test.ts) proven discriminating: fails on the vulnerable execSync form, passes on the fix.
  • All 9 execution sites in src/ audited for the same shape; this was the only exploitable one.

Relates to #87 — issue will be closed manually once the npm publish for this version is confirmed, not automatically on merge.

Test plan

  • tsc --noEmit clean
  • bun test: 1095 pass / 1 skip / 0 fail (baseline 1094, +1 new test)
  • Independent sniper pass: clean, no correction needed

stagedContent() built a shell command string via execSync(`git show
":${path}"`) with path taken directly from stagedFiles() -- i.e. the
literal name of a staged file, fully controlled by whoever authored
the commit. git's core.quotepath escapes an embedded `"` or `\`, but
not backticks or $(...), so a filename like `` x`touch pwned.txt`.ts ``
ran as shell syntax. Anyone running `harness check` as a pre-commit
hook (the README's own documented usage) against an untrusted staged
file list -- e.g. right after `git add .` on a checked-out
contributor branch -- got arbitrary command execution.

Both stagedContent() and stagedFiles() now use execFileSync with an
argv array; the filename reaches `git show` as a single literal
argument and is never parsed by a shell, regardless of its content.
stagedFiles() had no interpolated input (its args were already
literal) but is switched for consistency and defense in depth.

test/staged-content-shell-injection.test.ts exercises the real
stagedContent() against an isolated git repo (mkdtempSync, cleaned up
in a finally block) with a backtick-laden staged filename -- proven
discriminating: it fails against the vulnerable execSync form (the
shell eats the backticks before git ever sees them) and passes against
the fix.

All 9 execution sites in src/ were audited for the same shape; this
was the only exploitable one.

Reported in #87 by @VikramNehreTR, with reproduction and root-cause
analysis (including why core.quotepath doesn't help) and the exact fix
adopted here.
@fusengine
fusengine merged commit b2a831e into main Aug 3, 2026
1 check 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