const ACCESS_KEY_PATTERN = /\b(A[KS]IA[A-Z0-9]{16})\b/g const SECRET_KEY_PATTERN = /(?:aws_secret_access_key|secret[_-]?access[_-]?key|secretAccessKey)\s*[:=]\s*["']?[A-Za-z0-9/+=]{20,}["']?/gi const PRIVATE_KEY_PATTERN = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g const SESSION_TOKEN_PATTERN = /(?:aws_session_token|sessionToken|x-amz-security-token)\s*[:=]\s*["']?[A-Za-z0-9/+=]{20,}["']?/gi const PLACEHOLDER_PATTERN = /\{\{secret:[A-Za-z0-9._-]+(?:\|[^}]*)?\}\}/g export function looksLikeCredentialText(value: string): boolean { return ( ACCESS_KEY_PATTERN.test(value) || SECRET_KEY_PATTERN.test(value) || PRIVATE_KEY_PATTERN.test(value) || SESSION_TOKEN_PATTERN.test(value) || PLACEHOLDER_PATTERN.test(value) ) } export function redactSecrets(value: string): string { return value .replace(PRIVATE_KEY_PATTERN, '[redacted-private-key]') .replace(SECRET_KEY_PATTERN, '[redacted-secret-key]') .replace(SESSION_TOKEN_PATTERN, '[redacted-session-token]') .replace(ACCESS_KEY_PATTERN, '[redacted-access-key]') .replace(PLACEHOLDER_PATTERN, '{{secret:}}') } export function redactUnknown(value: unknown, depth = 0): unknown { if (depth > 6) return '[truncated]' if (typeof value === 'string') { const trimmed = value.length > 4000 ? value.slice(0, 4000) + '…' : value return redactSecrets(trimmed) } if (Array.isArray(value)) { return value.slice(0, 50).map((item) => redactUnknown(item, depth + 1)) } if (value && typeof value === 'object') { const out: Record = {} for (const [key, item] of Object.entries(value)) { if (/secret|token|password|authorization|credential/i.test(key)) { out[key] = '[redacted]' continue } out[key] = redactUnknown(item, depth + 1) } return out } return value }