Update
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const SENSITIVE_KEY = /(^|_)(token|password|passwd|authorization|secret|credential|clientsecret|client_secret|apikey|api_key|privatekey|private_key|encryptedtoken|encrypted_token)($|_)/i;
|
||||
const MAX_DIAGNOSTIC_STRING = 200_000;
|
||||
|
||||
function redactSecrets(value, secrets = []) {
|
||||
let text = String(value ?? '');
|
||||
const candidates = [...new Set((secrets || []).map((item) => String(item || '').trim()).filter((item) => item.length >= 4))]
|
||||
.sort((a, b) => b.length - a.length);
|
||||
for (const secret of candidates) text = text.split(secret).join('[REDACTED]');
|
||||
|
||||
text = text
|
||||
.replace(/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/gi, '[REDACTED PRIVATE KEY]')
|
||||
.replace(/(authorization\s*[:=]\s*(?:token|bearer|basic)\s+)[^\s,;]+/gi, '$1[REDACTED]')
|
||||
.replace(/([?&](?:access_token|token|api_key|apikey|key|secret|password)=)[^&#\s]+/gi, '$1[REDACTED]')
|
||||
.replace(/((?:access_token|token|api_key|apikey|client_secret|password|passwd|secret)\s*[=:]\s*)[^\s,;]+/gi, '$1[REDACTED]')
|
||||
.replace(/("(?:access_token|token|api_key|apikey|client_secret|password|passwd|secret)"\s*:\s*")[^"]+("?)/gi, '$1[REDACTED]$2')
|
||||
.replace(/(https?:\/\/[^\s:@/]+:)[^@\s/]+@/gi, '$1[REDACTED]@')
|
||||
.replace(/\b(?:ghp|github_pat|glpat|gitea)_[A-Za-z0-9_-]{16,}\b/g, '[REDACTED TOKEN]');
|
||||
|
||||
return text.length > MAX_DIAGNOSTIC_STRING ? `${text.slice(0, MAX_DIAGNOSTIC_STRING)}\n[TRUNCATED]` : text;
|
||||
}
|
||||
|
||||
function pathAlias(value, { homeDir = os.homedir(), cwd = process.cwd() } = {}) {
|
||||
let text = String(value ?? '');
|
||||
const replacements = [
|
||||
[homeDir, '<HOME>'],
|
||||
[cwd, '<APP_ROOT>']
|
||||
].filter(([candidate]) => candidate && candidate.length > 3)
|
||||
.sort((a, b) => b[0].length - a[0].length);
|
||||
for (const [candidate, replacement] of replacements) {
|
||||
const normalized = path.resolve(candidate);
|
||||
text = text.split(normalized).join(replacement);
|
||||
text = text.split(normalized.replace(/\\/g, '/')).join(replacement);
|
||||
text = text.split(normalized.replace(/\//g, '\\')).join(replacement);
|
||||
}
|
||||
text = text
|
||||
.replace(/[A-Za-z]:\\Users\\[^\\\s]+/g, '<HOME>')
|
||||
.replace(/\/(?:home|Users)\/[^/\s]+/g, '<HOME>');
|
||||
return text;
|
||||
}
|
||||
|
||||
function stableAlias(value, prefix = 'item') {
|
||||
const hash = crypto.createHash('sha256').update(String(value || '')).digest('hex').slice(0, 12);
|
||||
return `${prefix}-${hash}`;
|
||||
}
|
||||
|
||||
function sanitizeForDiagnostics(value, options = {}, seen = new WeakSet()) {
|
||||
const {
|
||||
secrets = [],
|
||||
pathMode = 'alias',
|
||||
homeDir = os.homedir(),
|
||||
cwd = process.cwd(),
|
||||
strictIdentifiers = false
|
||||
} = options;
|
||||
|
||||
if (value === null || value === undefined || typeof value === 'boolean' || typeof value === 'number') return value;
|
||||
if (typeof value === 'bigint') return value.toString();
|
||||
if (typeof value === 'string') {
|
||||
let output = redactSecrets(value, secrets);
|
||||
if (pathMode === 'alias') output = pathAlias(output, { homeDir, cwd });
|
||||
return output;
|
||||
}
|
||||
if (value instanceof Error) {
|
||||
return sanitizeForDiagnostics({ name: value.name, message: value.message, code: value.code, stack: value.stack }, options, seen);
|
||||
}
|
||||
if (Array.isArray(value)) return value.slice(0, 1000).map((item) => sanitizeForDiagnostics(item, options, seen));
|
||||
if (typeof value !== 'object') return redactSecrets(String(value), secrets);
|
||||
if (seen.has(value)) return '[CIRCULAR]';
|
||||
seen.add(value);
|
||||
|
||||
const output = {};
|
||||
for (const [key, item] of Object.entries(value)) {
|
||||
const normalizedKey = key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[-.]/g, '_');
|
||||
if (SENSITIVE_KEY.test(normalizedKey)) {
|
||||
output[key] = '[REDACTED]';
|
||||
continue;
|
||||
}
|
||||
if (strictIdentifiers && ['fullName', 'repository', 'owner', 'user', 'login', 'email'].includes(key)) {
|
||||
output[key] = stableAlias(typeof item === 'object' ? JSON.stringify(item) : item, key.toLowerCase());
|
||||
continue;
|
||||
}
|
||||
output[key] = sanitizeForDiagnostics(item, options, seen);
|
||||
}
|
||||
seen.delete(value);
|
||||
return output;
|
||||
}
|
||||
|
||||
module.exports = { redactSecrets, sanitizeForDiagnostics, pathAlias, stableAlias, SENSITIVE_KEY };
|
||||
Reference in New Issue
Block a user