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
7const TMP_PATH = /\/tmp\/briven-isolate-[a-zA-Z0-9_-]+/g;
8const 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)
10const 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
12const MAX_CHARS = 2048;
13const TRUNCATE_MARKER = '…';
14
15export 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}