Files
my-pi/pi-permission-system/src/log-redaction.ts
T

45 lines
1.6 KiB
TypeScript

import { createJsonSafeReplacer } from "./json-safe-stringify";
/**
* Key-name redaction for the permission logs.
*
* The technique is deliberately structural rather than predictive: a value is
* masked because of the *name* it is bound to, never because of what it looks
* like. Value-shape secret detection (provider prefixes, entropy heuristics)
* was measured against a real 6.7 MB review log and declined — see
* `docs/decisions/0010-permission-log-secret-exposure.md`.
*
* The boundary that follows from this, stated once: a value bound to a
* sensitive key name is masked; a secret embedded in a bash command string is
* not, because a command string has no keys.
*/
export const REDACTED_PLACEHOLDER = "[redacted]";
const SENSITIVE_KEY_PATTERN =
/authorization|api[-_]?key|secret|token|password|passwd|credential|cookie|private[-_]?key/i;
/** True when a log key names a credential-bearing value. */
export function isSensitiveLogKey(key: string): boolean {
return SENSITIVE_KEY_PATTERN.test(key);
}
/**
* `safeJsonStringify` with sensitive-keyed values masked.
*
* Masking runs inside the replacer, so the structure beneath a sensitive key
* is never visited and the traversal's existing cycle guard is reused — one
* walk, not two. A `null` or `undefined` value is left alone so an absent
* field does not read as a suppressed one.
*/
export function redactedJsonStringify(value: unknown): string | undefined {
return JSON.stringify(
value,
createJsonSafeReplacer((key, currentValue) =>
currentValue != null && isSensitiveLogKey(key)
? REDACTED_PLACEHOLDER
: currentValue,
),
);
}