fix(authz): sanitize control characters in console audit sink

Prevents audit log injection: subjectId, tenantId, and reason trace back
to request input, so an unsanitized newline could forge a second,
fully-formed audit line indistinguishable from a real entry. Adds
logSafe() to strip control characters before interpolation and logs the
previously-missing policy field.
This commit is contained in:
2026-08-04 17:21:09 +05:30
parent ba83038d8d
commit e710756baf
2 changed files with 62 additions and 4 deletions
+19 -3
View File
@@ -29,14 +29,30 @@ export function memoryAuditSink(): MemoryAuditSink {
};
}
/**
* Subject ids, tenant ids, and denial reasons trace back to request input, so
* a newline in one would forge a second audit line indistinguishable from a
* real entry. Strip control characters before interpolating.
*/
function logSafe(value: string): string {
let out = "";
for (const character of value) {
const code = character.codePointAt(0)!;
out += code < 0x20 || code === 0x7f ? " " : character;
}
return out;
}
export function consoleAuditSink(): AuthzAuditSink {
return {
record(event) {
const verdict = event.allowed ? "allow" : "deny";
console.info(
`[wrnexus:authz] ${verdict} ${event.permission} subject=${event.subjectId ?? "anonymous"}` +
`${event.scope?.tenantId ? ` tenant=${event.scope.tenantId}` : ""}` +
`${event.reason ? ` reason=${event.reason}` : ""}`,
`[wrnexus:authz] ${verdict} ${logSafe(event.permission)} ` +
`subject=${logSafe(event.subjectId ?? "anonymous")}` +
`${event.scope?.tenantId ? ` tenant=${logSafe(event.scope.tenantId)}` : ""}` +
`${event.reason ? ` reason=${logSafe(event.reason)}` : ""}` +
`${event.policy ? ` policy=${logSafe(event.policy)}` : ""}`,
);
},
};