/** * Scrubs debug text before it leaves the machine in a feedback report. Logs * are free text (intercepted console output, plugin messages, request * errors), so secrets are found by shape as well as by name. The server * scrubs again; this pass keeps them off the wire in the first place. */ const REDACTED = "[redacted]"; const SECRET_NAMES = "token|session_token|access_token|refresh_token|id_token|api[_-]?key|apikey|client_secret|secret|signature|password|passwd|authorization|cookie"; const TEXT_RULES: Array<[RegExp, string]> = [ [/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, `$1 ${REDACTED}`], [/\b(set-cookie|cookie)\s*:\s*[^\n]+/gi, `$1: ${REDACTED}`], // `token=...` in URLs and form bodies, `"password":"..."` in JSON. [new RegExp(`\\b(${SECRET_NAMES})=([^&\\s"'<>]+)`, "gi"), `$1=${REDACTED}`], [new RegExp(`"(${SECRET_NAMES}|accessToken|refreshToken|sessionToken|apiSecret|privateKey)"\\s*:\\s*"[^"]*"`, "gi"), `"$1":"${REDACTED}"`], [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, REDACTED], [/\b(?:phc|phx|sk|pk|rk|ghp|gho|ghs|ghu|xoxb|xoxp|xoxa)[-_][A-Za-z0-9_-]{10,}/g, REDACTED], // Opaque tokens: long runs that mix letters and digits. [/(?= 4) return "[truncated]"; if (Array.isArray(value)) return value.slice(0, 50).map((entry) => redactValue(entry, depth + 1)); const output: Record = {}; for (const [key, child] of Object.entries(value)) { if (SENSITIVE_KEY.test(key)) continue; output[key] = redactValue(child, depth + 1); } return output; }