fix(security): audit-logger PII masking parity with unified logger (DC-110) [glm-grade=A]
audit-logger.js (StateManager write path into audit-log.json) starred only 6 sensitive keys at the middleware layer; email-bearing resource paths (/invites/<email>/accept), DC-048 details.userEmail attribution, and emails in non-sensitive body keys landed RAW — while the parallel unified-logger path has masked at every sink since DC-095. DC-110 closes the parity gap using the SAME canonical primitives (sa****@example.com): log() masks resource (maskEmailsInString) and deep-masks details (maskEmails) at the single write-point, covering middleware AND direct route calls. The security-event mirror now uses the masked entry.resource for target/message (judge round-1 fix-first: the raw parameter leaked emails into security-events.jsonl). logging.js exports maskEmails (export-only). maskEmails clones — caller details objects are never mutated. Judge: GLM-5.3 cold read (standing Sami authorization 2026-08-17; Codex quota dead until 2026-08-29). Round 1 (deleg_ebb83285) C fix-first — caught the event-store mirror leak. Round 2 (deleg_923f9076) after in-commit fix + mirror test: grade A, ship. Verdict URN urn:ump:mwtxoj6dbdq7bfeba3l2am2rgx34zjimcjde5f6sxz6tozsjbskq (GET readback verified: grade A, topic codex-judge-verdict). Deferred (judge-accepted): one-time scrub of historical raw-email lines in the live 16MB security-events.jsonl — queued follow-up. Tests: 123 suites / 2801 green (+6 DC-110 pins: resource+details mask, non-mutation, middleware e2e with *** survival, idempotence, no-email regression, masked mirror target/message).
This commit is contained in:
@@ -2,6 +2,9 @@ const path = require('path');
|
||||
const StateManager = require('../managers/state-manager');
|
||||
const crypto = require('crypto');
|
||||
const platformPaths = require('../../platform-paths');
|
||||
// DC-110: canonical email-mask primitives from the unified logger — same
|
||||
// EMAIL_RE + maskEmailAddress shape every other sink uses (DC-095/DC-109).
|
||||
const { maskEmails, maskEmailsInString } = require('../utils/logging');
|
||||
|
||||
const AUDIT_LOG_FILE = process.env.AUDIT_LOG_FILE || path.join(platformPaths.dataDir, 'audit-log.json');
|
||||
const MAX_ENTRIES = parseInt(process.env.AUDIT_MAX_ENTRIES || '1000', 10);
|
||||
@@ -141,13 +144,23 @@ class AuditLogger {
|
||||
|
||||
async log({ action, resource, details, outcome, ip }) {
|
||||
try {
|
||||
// DC-110: PII parity with the unified logger (DC-095). Every string
|
||||
// that reaches audit-log.json gets email-masked with the SAME
|
||||
// canonical mask (sa****@example.com) the other sinks use, so one
|
||||
// consistent masked form everywhere. Applied HERE — the single
|
||||
// write-point — instead of at each call site: covers middleware
|
||||
// bodies, DC-048 userEmail attribution, direct route calls, and the
|
||||
// security-event-store mirror below, regardless of caller. `action`
|
||||
// is an internal token (service.create / dns.add-record) and never
|
||||
// contains PII; `ip` is an address literal. maskEmails() clones, so
|
||||
// the caller's `details` object is never mutated.
|
||||
const entry = {
|
||||
id: crypto.randomUUID(),
|
||||
timestamp: new Date().toISOString(),
|
||||
ip: ip || '',
|
||||
action: action || '',
|
||||
resource: resource || '',
|
||||
details: details || {},
|
||||
resource: maskEmailsInString(resource || ''),
|
||||
details: maskEmails(details || {}),
|
||||
outcome: outcome || 'unknown'
|
||||
};
|
||||
|
||||
@@ -172,11 +185,14 @@ class AuditLogger {
|
||||
source_host: hostname,
|
||||
source_type: 'api',
|
||||
actor: ip || null,
|
||||
target: resource || null,
|
||||
// DC-110 round 2 (judge fix-first): use the MASKED entry.resource
|
||||
// — the raw parameter leaked emails into security-events.jsonl
|
||||
// via target and the message template.
|
||||
target: entry.resource || null,
|
||||
action: action || 'unknown',
|
||||
outcome: outcome || 'unknown',
|
||||
severity,
|
||||
message: `${action} ${outcome} on ${resource}`.trim(),
|
||||
message: `${action} ${outcome} on ${entry.resource}`.trim(),
|
||||
metadata: {
|
||||
method: details?.body && Object.keys(details.body)[0] ? '(see audit-log)' : undefined,
|
||||
audit_id: entry.id,
|
||||
|
||||
@@ -691,4 +691,5 @@ module.exports = {
|
||||
EMAIL_RE,
|
||||
maskEmailAddress,
|
||||
maskEmailsInString,
|
||||
maskEmails,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user