93 lines
2.3 KiB
JavaScript
93 lines
2.3 KiB
JavaScript
/**
|
|
* @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<string, unknown>} */
|
|
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);
|
|
}
|