Files

46 lines
2.7 KiB
JavaScript

/**
* lib/redact.mjs — 비밀값 가리기(§37).
*
* 감사 기록·알림·작업 결과·오류 메시지·로그에 들어가는 모든 객체는 이 함수를 거친다.
* - 키 이름이 비밀로 보이면(password, secret, token, key, credential, cookie, authorization, recovery, otp, privkey …) 값을 '[redacted]' 로 바꾼다.
* - 문자열 값이 비밀 형태(sk-…, re_…, au_…, JWT, 40자 이상 base64url/hex, PEM 블록)면 가린다.
* - 깊이 6 까지 재귀. 배열·객체·문자열 모두.
*/
const KEY_RE = /pass(word|wd)?|secret|token|api[-_]?key|private[-_]?key|privkey|credential|cookie|authorization|recovery|otp|totp|session|vault|dkim|\bkey\b/i;
const VALUE_RES = [
/^(sk|rk|re|au|aw|ag|ghp|gho|xox[a-z])[-_][A-Za-z0-9_-]{16,}$/, // 서비스 키 접두어 형식
/^eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}$/, // JWT
/-----BEGIN [A-Z ]*PRIVATE KEY-----/, // PEM 개인키
/^[A-Za-z0-9_-]{40,}$/, // 긴 무작위 토큰
/^[a-f0-9]{48,}$/i, // 긴 hex 비밀
];
const KEEP_KEYS = /^(id|_id|site_id|group_id|user_id|job_id|idempotency_?key|incident_?key|public_?key_?fingerprint|key_?name|keyName|kind|resource)$/i;
/** 값 하나가 비밀 형태인지 */
export function looksSecret(v) { if (typeof v !== 'string' || v.length < 12) return false; return VALUE_RES.some((re) => re.test(v)); }
/**
* 객체/배열/문자열에서 비밀값을 가린다. 원본은 바꾸지 않는다.
* @param {*} v
* @param {number} depth 내부 재귀 깊이
*/
export function redact(v, depth = 0) {
if (depth > 6) return '[depth]';
if (v == null) return v;
if (typeof v === 'string') return looksSecret(v) ? '[redacted]' : v;
if (Array.isArray(v)) return v.map((x) => redact(x, depth + 1));
if (typeof v === 'object') {
const out = {};
for (const [k, val] of Object.entries(v)) {
if (!KEEP_KEYS.test(k) && KEY_RE.test(k)) { out[k] = val == null || val === '' ? val : '[redacted]'; continue; }
out[k] = redact(val, depth + 1);
}
return out;
}
return v;
}
/** 오류 객체를 기록용 안전 문자열로 */
export function safeError(e) { const m = e && e.message ? String(e.message) : String(e || ''); return redact(m.replace(/(Bearer\s+)[A-Za-z0-9._-]+/g, '$1[redacted]')); }
/** 비밀값의 앞뒤만 보이는 힌트(예: au_12…9f) — 화면에 "등록됨" 을 보여 주되 값은 못 알아보게 */
export function hint(secret) { const s = String(secret || ''); if (s.length < 10) return s ? '••••' : ''; return `${s.slice(0, 4)}…${s.slice(-2)}`; }