mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-11 23:49:01 +02:00
fix: pre-landing review fixes
Review army findings (1 critical, auto-fixed with regression tests):
- CRITICAL (security specialist, verified live): redactFindingSpans spliced
only the regex capture span, and pem.private_key / gcp.service_account
capture just the BEGIN-header — the key body survived "redaction" and
shipped via telemetry. Marker-only patterns now drop the whole payload
(null, fail closed). Overlapping spans (Bearer+JWT on the same bytes) are
coalesced before splicing so stale offsets can't leave partial secret
bytes behind.
- gitStrict: drop the dead `|| r.status === null` disjunct (null !== 0
already covers it); add the signal-kill/null-status regression test the
docstring promised.
- security-dashboard human mode flags stale snapshots ("figures may be out
of date") instead of presenting frozen counts as current.
- community-dashboard marker check uses jq when available — the grep-only
variant misclassified whitespaced/reserialized bodies as legacy.
- telemetry fail-closed test now shadows bun with a failing stub
(deterministic on any host layout); stale "five status cases" describe
title renamed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
88ca684929
commit
8c8e3b9e52
+38
-8
@@ -427,24 +427,54 @@ export function applyRedactions(
|
||||
return { body, diff: diffLines.reverse().join("\n"), skipped };
|
||||
}
|
||||
|
||||
/**
|
||||
* Patterns whose regex captures only a MARKER, not the secret payload itself
|
||||
* (the PEM header line; the GCP JSON key prefix). Span replacement on these
|
||||
* would redact the header and forward the key body — so redactFindingSpans
|
||||
* drops the whole payload instead.
|
||||
*/
|
||||
const MARKER_ONLY_PATTERN_IDS = new Set(["pem.private_key", "gcp.service_account"]);
|
||||
|
||||
/**
|
||||
* Replace EVERY finding's span with `<REDACTED-{id}>`, regardless of tier or
|
||||
* autoRedactable. For machine egress surfaces (telemetry error_message,
|
||||
* #1947) where structure preservation doesn't matter and fail-closed beats
|
||||
* fidelity. Returns null when any finding's span cannot be located — the
|
||||
* caller must then drop the whole payload rather than risk leaking an
|
||||
* unlocated secret. (Contrast applyRedactions, which is the interactive,
|
||||
* autoRedactable-only, structure-preserving path.)
|
||||
* fidelity. Returns null — caller must drop the whole payload — when:
|
||||
* - any finding's span cannot be located, or
|
||||
* - any finding matched a marker-only pattern (PEM / GCP service-account
|
||||
* JSON): their regexes capture the header, not the key material, so a
|
||||
* span splice would leak the body that follows the marker.
|
||||
* Overlapping spans (e.g. a Bearer token that is also a JWT) are coalesced
|
||||
* before splicing so stale offsets never leave partial secret bytes behind.
|
||||
* (Contrast applyRedactions, which is the interactive, autoRedactable-only,
|
||||
* structure-preserving path.)
|
||||
*/
|
||||
export function redactFindingSpans(input: string, opts: ScanOptions = {}): string | null {
|
||||
const { findings } = scan(input, opts);
|
||||
if (findings.some((f) => MARKER_ONLY_PATTERN_IDS.has(f.id))) return null;
|
||||
const targets = findings.map((f) => ({ f, ...locateSpan(input, f) }));
|
||||
if (targets.some((t) => t.start < 0)) return null;
|
||||
// Right-to-left so earlier offsets remain valid after splicing.
|
||||
targets.sort((a, b) => b.start - a.start);
|
||||
let body = input;
|
||||
|
||||
// Coalesce overlapping/touching ranges — splicing two intersecting spans
|
||||
// independently applies a stale end offset to already-modified text and
|
||||
// can leave trailing secret bytes in place.
|
||||
targets.sort((a, b) => a.start - b.start);
|
||||
const merged: Array<{ start: number; end: number; ids: string[] }> = [];
|
||||
for (const t of targets) {
|
||||
body = body.slice(0, t.start) + `<REDACTED-${t.f.id}>` + body.slice(t.end);
|
||||
const last = merged[merged.length - 1];
|
||||
if (last && t.start <= last.end) {
|
||||
last.end = Math.max(last.end, t.end);
|
||||
if (!last.ids.includes(t.f.id)) last.ids.push(t.f.id);
|
||||
} else {
|
||||
merged.push({ start: t.start, end: t.end, ids: [t.f.id] });
|
||||
}
|
||||
}
|
||||
|
||||
// Right-to-left so earlier offsets remain valid after splicing.
|
||||
let body = input;
|
||||
for (let i = merged.length - 1; i >= 0; i--) {
|
||||
const m = merged[i];
|
||||
body = body.slice(0, m.start) + `<REDACTED-${m.ids.join("+")}>` + body.slice(m.end);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user