fix(gate): the security scan's own diff was the seventh unvalidated git ref #5497
1 changed file+24−2
Modifiedsrc/lib/gate.ts+24−2View fileUnifiedSplit
@@ -561,9 +561,31 @@ export async function runAllGateChecks(
561561 let diffText = opts.diffText;
562562 if (runSecurityScan && !diffText) {
563563 try {
564 const { getRepoPath } = await import("../git/repository");
564 const { getRepoPath, refRange } = await import("../git/repository");
565 // Build the range through the shared guard rather than a template.
566 //
567 // There is no shell here — this is an argv array — but git treats a
568 // leading-dash argument as an OPTION wherever it appears, so a branch
569 // named `--output=<path>` turns `git diff` into an arbitrary file write.
570 // That exact vector was live and verified against real git via
571 // `GET /:owner/:repo/compare/:spec` (unauthenticated, public repos), and
572 // `refRange`'s own comment records six call sites that shipped this same
573 // bug. This was the seventh: selfcheck has been reporting it as HIGH
574 // `unvalidated-git-ref` here, while the suite that covers the class
575 // (git-ref-injection.test.ts) sits in ci-test-excludes.txt — so the rule
576 // was the only thing still watching, and nothing failed when it spoke.
577 //
578 // `refRange` returns null rather than throwing, so an unsafe ref lands
579 // in the same "no diff" state the scan already degrades to.
580 const range = refRange(baseBranch, headBranch);
581 if (!range) {
582 console.warn(
583 `[gate] refusing to diff ${owner}/${repo}: unsafe ref name(s) — base=${JSON.stringify(baseBranch)} head=${JSON.stringify(headBranch)}`
584 );
585 throw new Error("unsafe ref");
586 }
565587 const proc = Bun.spawn(
566 ["git", "diff", `${baseBranch}...${headBranch}`],
588 ["git", "diff", range],
567589 { cwd: getRepoPath(owner, repo), stdout: "pipe", stderr: "pipe" }
568590 );
569591 const killer = setTimeout(() => proc.kill(), 20_000);
570592
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts