mirror of https://github.com/garrytan/gstack.git
Merge bfeb6462ee into 3bef43bc5a
This commit is contained in:
commit
a4ca5b04fa
|
|
@ -160,13 +160,27 @@ function readLines(path: string | undefined): string[] | undefined {
|
|||
|
||||
function buildOpts(): ScanOptions {
|
||||
const vis = (arg("--repo-visibility") as RepoVisibility) || "unknown";
|
||||
const maxBytes = arg("--max-bytes");
|
||||
const maxBytesRaw = arg("--max-bytes");
|
||||
let maxBytes: number | undefined;
|
||||
if (maxBytesRaw !== undefined) {
|
||||
// Reject a malformed cap loudly. Silently passing NaN/0/negative would
|
||||
// weaken the engine's fail-closed oversize guard (exit 1 = usage error,
|
||||
// distinct from the 0/2/3 finding-tier codes).
|
||||
const parsed = Number(maxBytesRaw);
|
||||
if (!Number.isInteger(parsed) || parsed <= 0) {
|
||||
process.stderr.write(
|
||||
`gstack-redact: --max-bytes must be a positive integer, got: ${maxBytesRaw}\n`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
maxBytes = parsed;
|
||||
}
|
||||
return {
|
||||
repoVisibility: ["public", "private", "unknown"].includes(vis) ? vis : "unknown",
|
||||
allowlist: readLines(arg("--allowlist")),
|
||||
selfEmail: arg("--self-email"),
|
||||
repoPublicEmails: readLines(arg("--repo-public-emails")),
|
||||
...(maxBytes ? { maxBytes: parseInt(maxBytes, 10) } : {}),
|
||||
...(maxBytes !== undefined ? { maxBytes } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -253,7 +253,15 @@ function emailAllowed(email: string, opts: ScanOptions): boolean {
|
|||
|
||||
export function scan(input: string, opts: ScanOptions = {}): ScanResult {
|
||||
const repoVisibility: RepoVisibility = opts.repoVisibility ?? "unknown";
|
||||
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
||||
// `??` only catches null/undefined, so a NaN (e.g. from a malformed
|
||||
// `--max-bytes` flag) or a non-positive value would slip through and make
|
||||
// `byteLen > maxBytes` always false — silently turning this fail-CLOSED guard
|
||||
// into fail-OPEN. Treat any invalid cap as "use the known-good default".
|
||||
const requestedMax = opts.maxBytes;
|
||||
const maxBytes =
|
||||
typeof requestedMax === "number" && Number.isFinite(requestedMax) && requestedMax > 0
|
||||
? requestedMax
|
||||
: DEFAULT_MAX_BYTES;
|
||||
|
||||
// Fail CLOSED on oversize input. Check byte length BEFORE heavy work.
|
||||
const byteLen = Buffer.byteLength(input, "utf8");
|
||||
|
|
|
|||
|
|
@ -94,4 +94,15 @@ describe("gstack-redact oversize fails closed", () => {
|
|||
expect(code).toBe(3);
|
||||
expect(stdout).toContain("too large");
|
||||
});
|
||||
|
||||
// Regression: a malformed --max-bytes must error loudly (usage exit 1), not
|
||||
// silently pass NaN into the engine where it would disable the fail-closed
|
||||
// guard. Exit 1 is distinct from the 0/2/3 finding-tier codes.
|
||||
for (const bad of ["notanumber", "-5", "0", "10.5"]) {
|
||||
test(`malformed --max-bytes (${bad}) exits 1 with a clear error`, () => {
|
||||
const { code, stderr } = run(["--max-bytes", bad], "a".repeat(500));
|
||||
expect(code).toBe(1);
|
||||
expect(stderr).toContain("--max-bytes must be a positive integer");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -239,6 +239,20 @@ describe("oversize fails CLOSED", () => {
|
|||
expect(r.findings[0].id).toBe("engine.input_too_large");
|
||||
expect(exitCodeFor(r)).toBe(3);
|
||||
});
|
||||
|
||||
// Regression: an invalid cap (NaN/0/negative) must NOT disable the guard.
|
||||
// `?? DEFAULT` did not catch NaN, so `byteLen > NaN` was always false and the
|
||||
// fail-CLOSED guard silently failed OPEN. Invalid caps fall back to the
|
||||
// 1 MiB default, so a >1 MiB input still blocks.
|
||||
for (const bad of [NaN, 0, -1]) {
|
||||
test(`invalid maxBytes (${bad}) falls back to the default cap and still blocks >1 MiB`, () => {
|
||||
const big = "a".repeat(1024 * 1024 + 1);
|
||||
const r = scan(big, { maxBytes: bad });
|
||||
expect(r.oversize).toBe(true);
|
||||
expect(r.findings[0].id).toBe("engine.input_too_large");
|
||||
expect(exitCodeFor(r)).toBe(3);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("validators", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue