Commit Graph

1 Commits

Author SHA1 Message Date
Reuven 73babfb06e fix(security): close shell injection in github-tools MCP (audit_1776853149979)
The run() helper used execSync with template-string interpolation, and
the inline `.replace(/"/g, '\\"')` was a porous escape — it only handled
double quotes, leaving backticks, $(...), \, and ; literal. Combined
with `prNumber as number` (a TS cast, not a runtime check), an MCP
client could send:

  prNumber: "1; touch /tmp/pwned; echo "
  title:    'foo`whoami`bar'
  labels:   ['bug; rm -rf $HOME']

…and run() would interpolate them straight into the shell.

Fixes:

  runArgv(file, args[], cwd?)
    New helper using execFileSync with shell:false. argv elements aren't
    interpreted by /bin/sh, so metas in user-supplied strings stay
    literal. Original run() preserved for the static-string callers
    (gh issue list, git rev-list — they need pipes).

  toPositiveInt(value)
    Coerces + validates prNumber/issueNumber/workflowId as a positive
    integer (≤ 2^31). "1; rm -rf /" → null. Number([1,2]) → null.

  sanitizeLabels(value)
    Each label must be string ∈ /^[A-Za-z0-9][A-Za-z0-9 _\-./]{0,63}$/.
    Backticks, $, ;, |, leading separator, length > 64, non-array, or
    non-string element → null.

Risky callers converted (10 sites):
  pr create  : title/body/branches via argv
  pr review  : prNumber via toPositiveInt + argv
  pr merge   : same
  pr close   : same
  issue create: title/body via argv, labels via sanitizeLabels
  issue update: issueNumber via toPositiveInt, title via argv,
                labels re-sanitized
  issue close: issueNumber via toPositiveInt + argv
  workflow status / trigger / cancel: workflowId/ref via argv
                (already validated by validateIdentifier — argv form
                kept for consistency / defense in depth)

Tests:
  __tests__/github-tools-injection.test.ts — 15 cases pinning the
  toPositiveInt + sanitizeLabels contracts. Includes payloads
  '1; rm -rf /', '1`whoami`', '1$(whoami)', 'bug`whoami`' so a future
  refactor that loosens the validators trips a CI failure.

Verified: tsc --noEmit clean; full audit-fix test surface 46/46 passing
(4 files: validate-input-agent-spawn, validate-input-env, fs-secure,
github-tools-injection).
2026-05-03 20:34:04 -04:00