/** * Shared secret detection/redaction used by secret-scanner, commit-guard and * llm-redaction extensions. * * CONFIGURE: add entries to SECRET_PATTERNS below. Each pattern has a `name` * (shown in warnings/audit, never the secret itself) and a `re` (no global * flag — the global flag is added on demand for redaction). */ export interface SecretPattern { name: string; re: RegExp; } // Patterns are deliberately conservative to limit false positives. The user // still confirms interactively, so a rare false positive is cheap. export const SECRET_PATTERNS: SecretPattern[] = [ { name: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/ }, { name: "gitlab-pat", re: /\bglpat-[0-9A-Za-z_-]{20,}\b/ }, { name: "github-pat", re: /\bghp_[0-9A-Za-z]{36}\b/ }, { name: "github-fine-grained-pat", re: /\bgithub_pat_[0-9A-Za-z_]{22,}\b/ }, { name: "slack-token", re: /\bxox[baprs]-[0-9A-Za-z-]{10,}\b/ }, { name: "private-key-block", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/ }, { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/ }, // Generic "secret: value" / "token=value" assignments with a non-trivial value. { name: "generic-credential", re: /\b(?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*["']?[^\s"']{8,}["']?/i }, ]; /** Return the names of secret patterns found in `text` (empty if none). */ export function scanSecrets(text: string): string[] { if (!text) return []; const hits: string[] = []; for (const p of SECRET_PATTERNS) { // Use a non-stateful copy (no global flag) to avoid lastIndex surprises. if (p.re.test(text)) hits.push(p.name); } return hits; } /** Replace every secret occurrence with a labelled placeholder. */ export function redact(text: string): string { if (!text) return text; let out = text; for (const p of SECRET_PATTERNS) { const g = new RegExp(p.re.source, p.re.flags.includes("g") ? p.re.flags : `${p.re.flags}g`); out = out.replace(g, `«REDACTED:${p.name}»`); } return out; }