88 lines
2.5 KiB
JavaScript
88 lines
2.5 KiB
JavaScript
const REDACTION = '[REDACTED]';
|
|
|
|
const REDACT_KEY_RE = /(authorization|x-api-key|api[-_]?key|access_token|refresh_token|client_secret|private_key|set-cookie|cookie|password|secret)/i;
|
|
const EMAIL_KEY_RE = /email/i;
|
|
const IP_KEY_RE = /(^ip$|ip_address|remote_address|x-forwarded-for)/i;
|
|
|
|
function maskEmail(value) {
|
|
if (typeof value !== 'string') return value;
|
|
return value.replace(/([A-Za-z0-9._%+-])([A-Za-z0-9._%+-]*)(@[A-Za-z0-9.-]+\.[A-Za-z]{2,})/g, '$1***$3');
|
|
}
|
|
|
|
function maskIp(value) {
|
|
if (typeof value !== 'string') return value;
|
|
let masked = value.replace(/\b(\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,3}\b/g, '$1.xxx');
|
|
masked = masked.replace(/\b([A-Fa-f0-9]{0,4}:){2,7}[A-Fa-f0-9]{0,4}\b/g, '****');
|
|
return masked;
|
|
}
|
|
|
|
function maskTokensInString(value) {
|
|
if (typeof value !== 'string') return value;
|
|
let masked = value.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+\b/g, 'Bearer ' + REDACTION);
|
|
masked = masked.replace(/\b(api_key|apikey|access_token|refresh_token|client_secret|password)=([^\s&]+)/gi, '$1=' + REDACTION);
|
|
return masked;
|
|
}
|
|
|
|
function sanitizeString(value) {
|
|
if (typeof value !== 'string') return value;
|
|
let masked = value;
|
|
masked = maskTokensInString(masked);
|
|
masked = maskEmail(masked);
|
|
masked = maskIp(masked);
|
|
return masked;
|
|
}
|
|
|
|
function sanitizeValue(value, key, seen) {
|
|
if (value === null || value === undefined) return value;
|
|
|
|
if (key && REDACT_KEY_RE.test(key)) {
|
|
return REDACTION;
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
if (key && EMAIL_KEY_RE.test(key)) {
|
|
return maskEmail(value);
|
|
}
|
|
if (key && IP_KEY_RE.test(key)) {
|
|
return maskIp(value);
|
|
}
|
|
return sanitizeString(value);
|
|
}
|
|
|
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
return value;
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
return value.map(item => sanitizeValue(item, key, seen));
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
return sanitizeObject(value, seen);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function sanitizeObject(value, seen) {
|
|
if (!value || typeof value !== 'object') return value;
|
|
if (!seen) seen = new WeakSet();
|
|
if (seen.has(value)) return '[Circular]';
|
|
seen.add(value);
|
|
|
|
const output = Array.isArray(value) ? [] : {};
|
|
for (const [key, val] of Object.entries(value)) {
|
|
output[key] = sanitizeValue(val, key, seen);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
export function sanitizeForLog(value) {
|
|
return sanitizeValue(value, null, new WeakSet());
|
|
}
|
|
|
|
export function sanitizeLogMessage(message) {
|
|
if (typeof message !== 'string') return message;
|
|
return sanitizeString(message);
|
|
}
|