export interface SecretPatternRule { name: string; pattern: string; } export interface CompiledSecretPatternRule { name: string; pattern: RegExp; } export const DEFAULT_LOG_REDACTION_PATTERNS: SecretPatternRule[] = [ { name: "Tavily API key", pattern: "tvly-[a-zA-Z0-9\\-_]{20,}" }, { name: "OpenAI API key", pattern: "sk-[a-zA-Z0-9]{20,}" }, { name: "Anthropic API key", pattern: "sk-ant-[a-zA-Z0-9\\-_]{20,}" }, { name: "GitHub token", pattern: "gh[pousr]_[a-zA-Z0-9]{20,}" }, { name: "AWS key", pattern: "AKIA[0-9A-Z]{16}" }, { name: "Bearer token", pattern: "Bearer\\s+[a-zA-Z0-9\\-_.]{10,}" }, { name: "Authorization header", pattern: "Authorization:\\s*\\w+\\s+[a-zA-Z0-9\\-_.]{10,}", }, { name: "Generic secret", pattern: "\\b(?:api[_-]?key|apikey|secret|password|passwd|pwd)\\s*[:=]\\s*[\"']?[a-zA-Z0-9\\-_.]{8,}", }, ]; export const DEFAULT_GIT_BLOCKING_PATTERNS: SecretPatternRule[] = [ { name: "Tavily API key", pattern: "tvly-[a-zA-Z0-9\\-_]{20,}" }, { name: "OpenAI API key", pattern: "sk-[a-zA-Z0-9]{20,}" }, { name: "Anthropic API key", pattern: "sk-ant-[a-zA-Z0-9\\-_]{20,}" }, { name: "GitHub token", pattern: "gh[pousr]_[a-zA-Z0-9]{20,}" }, { name: "AWS key", pattern: "AKIA[0-9A-Z]{16}" }, { name: "Bearer token", pattern: "Bearer\\s+[a-zA-Z0-9\\-_.]{20,}", }, { name: "Authorization header", pattern: "Authorization:\\s*\\w+\\s+[a-zA-Z0-9\\-_.]{20,}", }, { name: "Generic secret assignment", pattern: "\\b(?:api[_-]?key|apikey|secret|password|passwd|pwd)\\s*[:=]\\s*[\"']?[a-zA-Z0-9\\-_.]{12,}", }, ]; export const COMPILED_DEFAULT_LOG_REDACTION_PATTERNS = compileSecretPatterns( DEFAULT_LOG_REDACTION_PATTERNS, ); export const COMPILED_DEFAULT_GIT_BLOCKING_PATTERNS = compileSecretPatterns( DEFAULT_GIT_BLOCKING_PATTERNS, ); /** @deprecated Use DEFAULT_LOG_REDACTION_PATTERNS */ export const DEFAULT_REDACTION_SECRET_PATTERNS = DEFAULT_LOG_REDACTION_PATTERNS; /** @deprecated Use DEFAULT_GIT_BLOCKING_PATTERNS */ export const DEFAULT_GIT_BLOCKING_SECRET_PATTERNS = DEFAULT_GIT_BLOCKING_PATTERNS; /** @deprecated Use COMPILED_DEFAULT_LOG_REDACTION_PATTERNS */ export const COMPILED_DEFAULT_REDACTION_SECRET_PATTERNS = COMPILED_DEFAULT_LOG_REDACTION_PATTERNS; /** @deprecated Use COMPILED_DEFAULT_GIT_BLOCKING_PATTERNS */ export const COMPILED_DEFAULT_GIT_BLOCKING_SECRET_PATTERNS = COMPILED_DEFAULT_GIT_BLOCKING_PATTERNS; export const TEST_SECRETS = new Set([ "sk-test12345678901234567890", "tvly-test12345678901234567890", "sk-ant-test12345678901234567890", "ghp_test12345678901234567890", "AKIATEST1234567890", "test-secret-1234567890", "test-password-1234567890", "test-api-key-1234567890", "Bearer test-token-1234567890", "Authorization: Basic test-token-1234567890", "password=test-password-1234567890", "api_key=test-api-key-1234567890", ]); export function isTestSecret(value: string): boolean { for (const testSecret of TEST_SECRETS) { if (value.includes(testSecret)) return true; } return false; } export interface CompileSecretPatternsResult { compiled: CompiledSecretPatternRule[]; warnings: string[]; } export function compileSecretPatterns( patterns: SecretPatternRule[], flags = "gi", ): CompiledSecretPatternRule[] { return patterns.map(({ name, pattern }) => ({ name, pattern: new RegExp(pattern, flags), })); } /** * Compile secret patterns with invalid-regex protection. * * Invalid patterns are skipped (not included in the compiled output) and * reported as warning strings. The caller can log these warnings and * continue operating with the valid subset. * * Use this when compiling user-provided patterns. Use `compileSecretPatterns` * for trusted/built-in patterns that are known to be valid. */ export function compileSecretPatternsSafe( patterns: SecretPatternRule[], flags = "gi", ): CompileSecretPatternsResult { const compiled: CompiledSecretPatternRule[] = []; const warnings: string[] = []; for (const { name, pattern } of patterns) { try { compiled.push({ name, pattern: new RegExp(pattern, flags), }); } catch (err) { warnings.push( `Invalid secret pattern regex "${pattern}" (${name}): ${err instanceof Error ? err.message : String(err)}`, ); } } return { compiled, warnings }; } export function findSecretMatches( text: string, patterns: CompiledSecretPatternRule[], ): string[] { const found = new Set(); for (const { name, pattern } of patterns) { pattern.lastIndex = 0; if (pattern.test(text)) { found.add(name); } } return [...found]; } export function redactSecrets( text: string, patterns: CompiledSecretPatternRule[] = COMPILED_DEFAULT_LOG_REDACTION_PATTERNS, allowTestSecrets = true, ): { text: string; found: string[] } { let result = text; const found: string[] = []; for (const { name, pattern } of patterns) { pattern.lastIndex = 0; result = result.replace(pattern, (match) => { if (allowTestSecrets && isTestSecret(match)) { return match; } found.push(`[REDACTED ${name}]`); return `[REDACTED ${name}]`; }); } return { text: result, found }; } export function sanitizeTextForLogging(text: string): string { return redactSecrets(text).text; }