Skip to content

PII Masking

Accidentally logging secrets is one of the most common and costly security mistakes in application development. log-wiz solves this natively — no extra code, no middleware, no wrappers.


Every metadata object passed to any log call is deep-cloned and scanned before being written to any transport. Keys matching the masked-key list have their values replaced with the string [MASKED].

wiz.info("event", { meta: { password: "secret", userId: 42 } })
maskSensitiveData(meta, maskedKeys)
{ password: "[MASKED]", userId: 42 }
written to transport

The original object is never mutated.


These keys are masked out of the box. Matching is case-insensitive and ignores -, _, and spaces.

KeyAlso matches
passwordPassword, PASSWORD
passwdPasswd
tokenToken, TOKEN, accessToken, access_token
refreshtokenrefreshToken, refresh_token
secretSecret, SECRET
authorizationAuthorization, AUTHORIZATION
cookieCookie
card_numbercardNumber, card-number
cvvCVV
ssnSSN
apikeyapiKey, api_key, API_KEY
privatekeyprivateKey, private_key

Masking is fully recursive — it descends into nested objects and arrays at any depth:

wiz.info("checkout", {
meta: {
user: {
name: "Alice",
payment: {
card_number: "4111-1111-1111-1111", // -> [MASKED]
cvv: "123", // -> [MASKED]
expiry: "12/26", // visible
},
},
items: [
{ sku: "ABC", secret: "x" }, // secret -> [MASKED]
{ sku: "DEF", price: 9.99 }, // visible
],
},
});

log-wiz uses a WeakSet to track visited objects and safely handles circular references:

const req: Record<string, unknown> = { id: "r-1", method: "POST" };
req["self"] = req; // circular reference!
wiz.info("request", { meta: req });
// -> { id: "r-1", method: "POST", self: "[Circular]" }
// Never throws RangeError, never hangs

Extra keys are merged with the built-in defaults — defaults stay active:

import { Wiz } from "@gouranga_samrat/log-wiz";
const logger = new Wiz({
maskedKeys: ["nationalId", "medicalRecordNumber", "driverLicense"],
});
logger.info("patient record accessed", {
meta: {
patientName: "Jane Doe", // visible
nationalId: "123-45-6789", // -> [MASKED] (custom)
medicalRecordNumber: "MRN-00042", // -> [MASKED] (custom)
token: "session-token", // -> [MASKED] (built-in default still active)
},
});

Set replaceDefaultMaskedKeys: true to use only your custom list:

const logger = new Wiz({
maskedKeys: ["internalRef"],
replaceDefaultMaskedKeys: true,
});
logger.info("audit", {
meta: {
internalRef: "REF-999", // -> [MASKED]
token: "still-visible", // visible — defaults were replaced
action: "EXPORT",
},
});

Masked keys can be updated at any time via setConfig():

logger.setConfig({
maskedKeys: ["newSecret", "anotherField"],
});

No-op fast path

When level: 'none', masking is never executed — the method returns before reaching the masking step.

Level-filtered

If an entry is below the configured level threshold, masking is skipped entirely. Only entries that will be written incur the clone cost.

Non-mutating

Deep clone ensures the original object is never modified. Safe to log request bodies without side effects.

WeakSet lifecycle

The WeakSet is allocated per call and garbage-collected immediately — no global state, no memory leaks.