Observed problem
gstack-redact's oversize check is documented and designed to fail closed (lib/redact-engine.ts: "Fail CLOSED on oversize input"). A malformed --max-bytes value silently turns it into fail open — the guard is skipped and oversized input is scanned (or, with a negative value, every input is blocked).
Root cause is a two-step gap:
-
bin/gstack-redact parses the flag with parseInt(maxBytes, 10) and passes the result straight through:
const maxBytes = arg("--max-bytes");
return {
...
...(maxBytes ? { maxBytes: parseInt(maxBytes, 10) } : {}),
};
parseInt("foo", 10) → NaN, which is passed to the engine as maxBytes: NaN.
-
The engine only guards against null/undefined:
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
...
if (byteLen > maxBytes) { /* fail-closed block */ }
?? does not catch NaN, so maxBytes stays NaN, and byteLen > NaN is always false. The fail-closed block never fires.
Current behavior on upstream main
gstack-redact --max-bytes notanumber < huge_input — the oversize fail-closed guard is skipped entirely (input that should be blocked at the cap is scanned anyway). The user believes they set a cap; they silently got no cap.
gstack-redact --max-bytes -5 < anything — byteLen > -5 is always true, so every input is blocked as "too large", with a nonsensical > -5 bytes message.
- Calling
scan(input, { maxBytes: NaN }) directly returns oversize: false for arbitrarily large input.
Expected behavior
An invalid size cap must never weaken the fail-closed guard:
- The engine should treat a non-finite or non-positive
maxBytes as invalid and fall back to the known-good DEFAULT_MAX_BYTES (1 MiB), so the guard stays intact for every caller.
- The CLI should reject a malformed
--max-bytes with a clear error and a usage exit code (1), instead of silently passing NaN.
Duplicate searches performed
Candidate fix shape
lib/redact-engine.ts: replace opts.maxBytes ?? DEFAULT_MAX_BYTES with a validity check (Number.isFinite && > 0, else DEFAULT_MAX_BYTES).
bin/gstack-redact: validate --max-bytes is a positive integer; otherwise write an error to stderr and process.exit(1).
- Regression tests in
test/redact-engine.test.ts (invalid maxBytes still fails closed) and test/gstack-redact-cli.test.ts (malformed --max-bytes exits 1).
Observed problem
gstack-redact's oversize check is documented and designed to fail closed (lib/redact-engine.ts: "Fail CLOSED on oversize input"). A malformed--max-bytesvalue silently turns it into fail open — the guard is skipped and oversized input is scanned (or, with a negative value, every input is blocked).Root cause is a two-step gap:
bin/gstack-redactparses the flag withparseInt(maxBytes, 10)and passes the result straight through:parseInt("foo", 10)→NaN, which is passed to the engine asmaxBytes: NaN.The engine only guards against null/undefined:
??does not catchNaN, somaxBytesstaysNaN, andbyteLen > NaNis alwaysfalse. The fail-closed block never fires.Current behavior on upstream
maingstack-redact --max-bytes notanumber < huge_input— the oversize fail-closed guard is skipped entirely (input that should be blocked at the cap is scanned anyway). The user believes they set a cap; they silently got no cap.gstack-redact --max-bytes -5 < anything—byteLen > -5is always true, so every input is blocked as "too large", with a nonsensical> -5 bytesmessage.scan(input, { maxBytes: NaN })directly returnsoversize: falsefor arbitrarily large input.Expected behavior
An invalid size cap must never weaken the fail-closed guard:
maxBytesas invalid and fall back to the known-goodDEFAULT_MAX_BYTES(1 MiB), so the guard stays intact for every caller.--max-byteswith a clear error and a usage exit code (1), instead of silently passingNaN.Duplicate searches performed
redact in:title, full-textmax-bytes— only old, unrelated browse-storage redaction PRs (Security: Redact sensitive values from command output #21, fix: redact sensitive values in storage read command #168, fix(security): redact sensitive values in browse storage command #238, fix(browse): redact form fields with sensitive names, not just type=password #860); no open PR toucheslib/redact-engine.tsor the size guard.redact,max-bytes fail-closed— none about the size cap.lib/redact-engine.tsorbin/gstack-redact.Candidate fix shape
lib/redact-engine.ts: replaceopts.maxBytes ?? DEFAULT_MAX_BYTESwith a validity check (Number.isFinite && > 0, elseDEFAULT_MAX_BYTES).bin/gstack-redact: validate--max-bytesis a positive integer; otherwise write an error to stderr andprocess.exit(1).test/redact-engine.test.ts(invalidmaxBytesstill fails closed) andtest/gstack-redact-cli.test.ts(malformed--max-bytesexits 1).