/** * @fileoverview Secret redaction for debug logs. * @module debug/redact */ const SENSITIVE_KEYS = new Set([ 'password', 'veraepassword', 'token', 'veraetoken', 'accesstoken', 'apikey', 'api_key', 'secret', 'authorization', 'x-api-key', 'cookie', 'sessionkey', ]); const BEARER_RE = /^Bearer\s+.+/i; const API_KEY_RE = /^zmw_[A-Za-z0-9_-]+/; const SESSION_RE = /^zmt_[A-Za-z0-9_.-]+/; const JWT_RE = /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/; /** * Redact a single string value if it looks like a secret. * * @param {string} value - Raw string. * @returns {string} Original or `[REDACTED]`. */ export function redactString(value) { if (typeof value !== 'string') return value; if ( BEARER_RE.test(value) || API_KEY_RE.test(value) || SESSION_RE.test(value) || JWT_RE.test(value) ) { return '[REDACTED]'; } return value; } /** * Deep-clone plain data structures while redacting sensitive keys and values. * * @param {unknown} input - Any JSON-like value. * @param {number} [depth=0] - Recursion depth guard. * @returns {unknown} Redacted structure safe for logging. * * @example * redact({ password: 'x', jobId: '1' }); // { password: '[REDACTED]', jobId: '1' } */ export function redact(input, depth = 0) { if (depth > 8) return '[MaxDepth]'; if (input === null || input === undefined) return input; if (typeof input === 'string') return redactString(input); if (typeof input === 'number' || typeof input === 'boolean') return input; if (input instanceof Error) { return { name: input.name, message: redactString(input.message), stack: undefined }; } if (Array.isArray(input)) { return input.map((item) => redact(item, depth + 1)); } if (typeof input === 'object') { /** @type {Record} */ const out = {}; for (const [key, value] of Object.entries(input)) { if (SENSITIVE_KEYS.has(key.toLowerCase())) { out[key] = '[REDACTED]'; } else if (key.toLowerCase() === 'targeturl' && typeof value === 'string') { try { const u = new URL(value); out[key] = `${u.origin}${u.pathname}`; } catch { out[key] = '[REDACTED_URL]'; } } else { out[key] = redact(value, depth + 1); } } return out; } return String(input); }