Ariweb Suite 0.1.1 기반: 공통 라이브러리(db·http·세션·작업 엔진·할당/예약·알림·감사·보관소), 포털 뼈대, Webmaker/AriMail 도입 소스와 manifest
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
9ca4cee341
commit
c7aea8d419
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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)}`; }
|
||||
Reference in New Issue
Block a user