/** * Best-effort credential scrubbing for durable crash records. * * This is a *persistence-time* scrub: it keeps obvious credential shapes out of * a file GJC keeps indefinitely. It is explicitly NOT a privacy guarantee and * must never be treated as one for data that leaves the machine — outbound * text goes through `sanitizeExternalCrashV1` instead. */ /** * Scrub credential material from crash text before it is persisted. * Covers bearer/basic-style headers, key=value or JSON key forms of common * credential names, and well-known vendor token shapes. Normal messages and * stack frames are untouched; matches are replaced in place so surrounding * diagnostic context survives. */ export function redactCrashSecrets(text: string): string { let redacted = text; redacted = redacted.replace(/\b(?:Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "«redacted-auth»"); redacted = redacted.replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, "«redacted-jwt»"); redacted = redacted.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/g, "«redacted-api-key»"); // `gh[opsur]_` covers the classic PAT/OAuth/server/user/refresh prefixes; // fine-grained PATs use an entirely different `github_pat_` prefix and would // otherwise survive into a log the module keeps indefinitely. redacted = redacted.replace(/\bgh[opsur]_[A-Za-z0-9]{16,}\b/g, "«redacted-github-token»"); redacted = redacted.replace(/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, "«redacted-github-token»"); redacted = redacted.replace(/\bxox[baprs]-[A-Za-z0-9-]{8,}\b/g, "«redacted-slack-token»"); // The five shapes below are the ones `crash/upstream/envelope.ts` already // classifies as credential-like and refuses to transmit. That refusal only // guards the Sentry frame fields; the persisted crash log and the // `gjc crash report` body — which the user files as a public issue — reach // egress through this function alone, so the same shapes have to be named here. redacted = redacted.replace(/\bnpm_[A-Za-z0-9]{20,}\b/g, "«redacted-npm-token»"); redacted = redacted.replace(/\bglpat-[A-Za-z0-9_-]{20,}\b/g, "«redacted-gitlab-token»"); // Stripe separates with `_`, so the `sk-` rule above never matched one. redacted = redacted.replace(/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g, "«redacted-api-key»"); redacted = redacted.replace(/\bhf_[A-Za-z0-9]{20,}\b/g, "«redacted-api-key»"); // A PEM block carries the key material itself, so it is redacted whole rather // than line by line. It runs before the narrower rules because they would // otherwise chew on the base64 body and leave a truncated key behind. redacted = redacted.replace( /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g, "«redacted-private-key»", ); // Google API keys are a fixed 39-character shape that carries no label of its // own, so the labeled-value rule below never sees one. redacted = redacted.replace( /(?