// Copyright (c) 2025 Gordon Bolton. MIT License. // Query-param secrets (SABnzbd apikey, generic token/password params) const QUERY_SECRET_PATTERN = /([?&](?:apikey|token|password|api_key|key|secret)=)[^&\s#]*/gi; // HTTP auth header values (X-Api-Key, X-MediaBrowser-Token, Authorization, X-Emby-Authorization) // Redact everything after the colon to end-of-line (MediaBrowser headers span the full line) const HEADER_PATTERN = /(?:x-api-key|x-mediabrowser-token|x-emby-authorization|authorization)\s*:[^\n]*/gi; // Bearer tokens const BEARER_PATTERN = /bearer\s+[A-Za-z0-9\-._~+/]+=*/gi; // Basic auth credentials in URLs (http://user:pass@host) const BASIC_AUTH_URL_PATTERN = /\/\/[^:@/\s]+:[^@/\s]+@/gi; // Redact only the host:port authority portion of URLs, preserving path/query so // other patterns (QUERY_SECRET_PATTERN etc.) can still act on them. // Negative lookahead skips URLs already handled by BASIC_AUTH_URL_PATTERN. const HOST_PATTERN = /(https?:\/\/)(?!\[REDACTED\]@)([^\s/?#]+)/gi; function sanitizeError(err) { let msg = (err && err.message) ? err.message : String(err); msg = msg.replace(QUERY_SECRET_PATTERN, '$1[REDACTED]'); msg = msg.replace(HEADER_PATTERN, (m) => m.split(/[\s:]/)[0] + ':[REDACTED]'); msg = msg.replace(BEARER_PATTERN, 'bearer [REDACTED]'); msg = msg.replace(BASIC_AUTH_URL_PATTERN, '//[REDACTED]@'); // must run before HOST_PATTERN msg = msg.replace(HOST_PATTERN, '$1[HOST]'); // Never leak stack traces to API responses return msg; } module.exports = sanitizeError;