gstack/.github/scripts/gate-secret-scan.mjs

47 lines
1.7 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from "node:child_process";
const child = spawn("bun", [
"bin/gstack-redact",
"--repo-visibility", "public",
"--json",
"--max-bytes", "16000000",
], { shell: false, windowsHide: true, stdio: ["pipe", "pipe", "inherit"] });
let diff = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => { diff += chunk; });
process.stdin.once("end", () => {
const additions = diff
.split(/\r?\n/)
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
.map((line) => line.slice(1))
.join("\n");
// The scanner may exit before consuming an oversize payload (it refuses
// stdin over --max-bytes and reports oversize:true). EPIPE here is that
// refusal in flight, not a failure — the report + exit code carry the verdict.
child.stdin.on("error", (error) => {
if (error.code !== "EPIPE") throw error;
});
child.stdin.end(additions);
});
let stdout = "";
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk) => { stdout += chunk; });
child.once("error", (error) => { throw error; });
child.once("close", (code) => {
let report;
try {
report = JSON.parse(stdout);
} catch {
// No parseable report: the oversize refusal prints only to stderr and
// exits 3, and a crashed scanner emits nothing. Both fail closed.
console.log(`credential scan: 1 high, 0 advisory (scanner emitted no report, exit ${code} — fail-closed)`);
process.exitCode = 1;
return;
}
const high = Number(report.counts?.HIGH ?? 0);
const medium = Number(report.counts?.MEDIUM ?? 0);
console.log(`credential scan: ${high} high, ${medium} advisory`);
process.exitCode = high > 0 || report.oversize || ![0, 2, 3].includes(code) ? 1 : 0;
});