Skip to content

fix(security): eliminate shell injection in pr_merge GraphQL fallback#403

Merged
bakeb7j0 merged 1 commit into
kahuna/390-bug-drainfrom
fix/security-pr-merge-graphql-shell-injection
May 6, 2026
Merged

fix(security): eliminate shell injection in pr_merge GraphQL fallback#403
bakeb7j0 merged 1 commit into
kahuna/390-bug-drainfrom
fix/security-pr-merge-graphql-shell-injection

Conversation

@bakeb7j0
Copy link
Copy Markdown
Contributor

@bakeb7j0 bakeb7j0 commented May 6, 2026

Summary

Trust-score gate hotfix for kahuna/390-bug-drain. Eliminates a shell-injection surface in enqueuePullRequestViaGraphQL (introduced in PR #394 / issue #284).

The function previously interpolated repo and prId values into shell command strings via execSync. While prId (from the GitHub API) is opaque base64 in practice, no validation enforced this — and repo flowed from caller args without adapter-level shell-safety guarantees.

Changes

  • lib/adapters/pr-merge-github.ts — replaced string-template interpolation with runArgv (argv-array form, shell-escapes each element). This matches the established pattern in lib/adapters/ (label-create-github.ts, pr-diff-gitlab.ts, etc.).
  • lib/adapters/pr-merge-github.test.ts — added regression test injecting a malicious node_id containing "; echo PWNED; # and verifying the dangerous value stays contained within a single shell-quoted argv token.

Tests

  • ✅ 2238 pass / 0 fail / 3 skip (full suite)
  • ✅ 16 pass in pr-merge-github.test.ts (existing 14 + 2 new — the GraphQL fallback test was updated to use the test's unquote helper for argv-form matching)
  • ./scripts/ci/validate.sh green (76/76 tools)

Trust-score gate context

This is the only critical finding from the kahuna→main code-reviewer pass. The 3 important findings (work_item_update inline schema, plan_load_dod missing schema, wave_init Step 4 atomicity gap) are tracked as separate follow-up issues per the plan-A path.

Closes #390 (gate hotfix)

…te shell injection

The enqueuePullRequestViaGraphQL helper interpolated 'repo' and 'prId'
values into shell command strings via execSync. While 'prId' from the
GitHub API is opaque base64 in practice, no validation enforced this
contract — and 'repo' flowed through from caller args without
adapter-level shell-safety guarantees on this code path.

Replace string-template interpolation with runArgv (argv-array form,
shell-escapes each element). This is the established pattern in
lib/adapters/ subprocess code (label-create-github.ts, pr-diff-gitlab.ts,
etc.) and removes the shell-injection surface entirely.

Also adds a regression test that injects a node_id containing
'"; echo PWNED; #' and verifies the dangerous value remains contained
within a single shell-quoted argv token (never escapes to bare shell).
@bakeb7j0 bakeb7j0 merged commit 135f38a into kahuna/390-bug-drain May 6, 2026
1 check failed
@bakeb7j0 bakeb7j0 deleted the fix/security-pr-merge-graphql-shell-injection branch May 6, 2026 01:09
bakeb7j0 added a commit that referenced this pull request May 6, 2026
Sibling fix to PR #403. The original critical finding noted both
repoFlag in enqueuePullRequestViaGraphQL AND the repo arg in
buildGithubMergeCommand. PR #403 fixed only the former (via runArgv);
this PR closes the buildGithubMergeCommand path with shellEscape() —
matching the existing pattern for squashMessage and tempFile two
lines above.

Adds a regression test that injects 'sec/repo'\''; echo PWNED; #' as
the repo arg and verifies the dangerous chars only appear inside
single-quoted argv tokens (or inside the '\\'' escape sequence).

Updates two existing tests that asserted the unquoted form to use
unquote() / .replace(/'/g, '').

Co-authored-by: Baker B <bakerb@users.noreply.github.com>
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.

2 participants