error-sanitizer.ts27 lines · main
| 1 | /** |
| 2 | * Strip operational secrets from an error message before it leaves the |
| 3 | * runtime per CLAUDE.md §5.1. Order matters: paths and IPs first, then |
| 4 | * env-var values (which may overlap), then truncation. |
| 5 | */ |
| 6 | |
| 7 | const TMP_PATH = /\/tmp\/briven-isolate-[a-zA-Z0-9_-]+/g; |
| 8 | const IPV4 = /\b(?:25[0-5]|2[0-4]\d|1\d\d|\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|\d{1,2})){3}\b/g; |
| 9 | // IPv6: matches leading-::, mid-::, and full forms (including ::1 loopback) |
| 10 | const IPV6 = /::(?:[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4})*)?|[0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{0,4})*::[0-9a-fA-F]{0,4}|(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}/g; |
| 11 | |
| 12 | const MAX_CHARS = 2048; |
| 13 | const TRUNCATE_MARKER = '…'; |
| 14 | |
| 15 | export function sanitizeErrorMessage(input: string, envValues: readonly string[]): string { |
| 16 | let out = input.replace(TMP_PATH, '<bundle>'); |
| 17 | out = out.replace(IPV4, '<ip>'); |
| 18 | out = out.replace(IPV6, '<ip>'); |
| 19 | for (const value of envValues) { |
| 20 | if (value.length < 4) continue; // skip near-empty values |
| 21 | out = out.split(value).join('<redacted>'); |
| 22 | } |
| 23 | if (out.length > MAX_CHARS) { |
| 24 | out = out.slice(0, MAX_CHARS - TRUNCATE_MARKER.length) + TRUNCATE_MARKER; |
| 25 | } |
| 26 | return out; |
| 27 | } |