@warlock.js/logger 4.14.0 → 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/cjs/index.cjs +312 -38
- package/cjs/index.cjs.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +4 -2
- package/esm/logger.mjs +1 -0
- package/esm/logger.mjs.map +1 -1
- package/esm/redact/default-keys.d.mts +63 -0
- package/esm/redact/default-keys.d.mts.map +1 -0
- package/esm/redact/default-keys.mjs +116 -0
- package/esm/redact/default-keys.mjs.map +1 -0
- package/esm/redact/index.mjs +4 -0
- package/esm/redact/redact.d.mts +41 -8
- package/esm/redact/redact.d.mts.map +1 -1
- package/esm/redact/redact.mjs +196 -38
- package/esm/redact/redact.mjs.map +1 -1
- package/esm/types.d.mts +49 -12
- package/esm/types.d.mts.map +1 -1
- package/llms-full.txt +39 -5
- package/llms.txt +1 -1
- package/package.json +18 -13
- package/skills/redact-sensitive-log-fields/SKILL.md +39 -5
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: redact-sensitive-log-fields
|
|
3
|
-
description: 'Strip secrets from log output — two-layer additive redaction via log.configure({redact: {paths}}) (logger floor) + per-channel redact (more
|
|
3
|
+
description: 'Strip secrets from log output — a built-in secret-key denylist on by default (DEFAULT_REDACT_KEYS), plus two-layer additive redaction via log.configure({redact: {paths, keys}}) (logger floor) + per-channel redact (more on top). Dotted glob paths (*, **). Triggers: `redact`, `paths`, `keys`, `defaultKeys`, `censor`, `log.setRedact`, `applyRedact`; "redact passwords in logs", "strip tokens from log output", "hide authorization headers", "scrub PII before logging", "turn off default redaction"; typical import `import { log } from "@warlock.js/logger"`. Skip: filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; competing libs `pino.redact`, `fast-redact`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Redaction — keeping secrets out of logs
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Three layers: a **built-in key denylist that is on by default**, plus opt-in path globs configured at the logger and/or per channel.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Layer 0 — the default denylist (since 4.15.0, no configuration needed)
|
|
11
|
+
|
|
12
|
+
Common secret **key names** are censored at any depth of `context`, `message`, and an `Error`'s own enumerable properties — before any of your config runs:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
log.error("auth", "login", "failed", { headers: req.headers, body: req.body });
|
|
16
|
+
// context.headers.authorization → "[REDACTED]"
|
|
17
|
+
// context.body.password → "[REDACTED]"
|
|
18
|
+
// context.body.email → untouched
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Keys are matched **exactly**, on a normalized form (lower-cased, separators stripped) — so one entry covers `apiKey` / `api_key` / `API-KEY` / `x-api-key`. It is not substring matching: `tokenCount` and `passwordUpdatedAt` survive. Read the exact set from `DEFAULT_REDACT_KEYS`.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { DEFAULT_REDACT_KEYS } from "@warlock.js/logger";
|
|
25
|
+
|
|
26
|
+
log.configure({
|
|
27
|
+
redact: {
|
|
28
|
+
keys: ["internalRef"], // union with the built-in set
|
|
29
|
+
defaultKeys: false, // opt out of the built-in set entirely
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`defaultKeys: false` is an escape hatch, not a tuning knob — it restores the pre-4.15.0 behavior where a `password` in `context` reaches every sink in cleartext. Prefer adding a function `censor` if you only need to keep a prefix.
|
|
35
|
+
|
|
36
|
+
**A channel cannot turn the default set off** (it can only add keys, or turn it back *on* if the logger-wide config disabled it) — same additive-only contract as paths, below. And `log.setRedact(undefined)` clears *your* paths, not the default denylist.
|
|
37
|
+
|
|
38
|
+
Not reachable by any layer: secrets interpolated into a `message` string (`` `token=${t}` ``), `Map`/`Set`/`Buffer` contents, and getter-backed or non-enumerable properties.
|
|
39
|
+
|
|
40
|
+
## Layers 1 & 2 — path globs, opt-in
|
|
41
|
+
|
|
42
|
+
For anything the denylist can't name by key — a secret under an app-specific key, or a value you want partially masked rather than blanked.
|
|
43
|
+
|
|
44
|
+
The model in one line:
|
|
11
45
|
|
|
12
46
|
> Logger-wide redaction is the security floor. Per-channel redaction adds more paths. **No channel can ever undo a logger-wide redaction.**
|
|
13
47
|
|
|
@@ -31,7 +65,7 @@ log.configure({
|
|
|
31
65
|
|
|
32
66
|
// runtime equivalent:
|
|
33
67
|
log.setRedact({ paths: ["context.password"] });
|
|
34
|
-
log.setRedact(undefined); // clear
|
|
68
|
+
log.setRedact(undefined); // clear your paths (the default denylist stays on)
|
|
35
69
|
```
|
|
36
70
|
|
|
37
71
|
Every channel sees the redacted entry. Cheap: applied **once** before fan-out; channels share the redacted clone unless they add their own paths.
|
|
@@ -114,7 +148,7 @@ If `message` is a plain object, paths under `message.*` work as expected. If `me
|
|
|
114
148
|
|
|
115
149
|
## Performance notes
|
|
116
150
|
|
|
117
|
-
- **No redact configured** →
|
|
151
|
+
- **No redact configured** → the default denylist still runs (since 4.15.0): a cheap presence scan for a denylisted key on every `log()` call, with the deep clone + censor pass skipped entirely when nothing matches. Only `{ defaultKeys: false }` (no `paths`, no extra `keys`) gets back to zero work.
|
|
118
152
|
- **Logger-wide redact only** → one deep clone + one path-walk per `log()` call, shared by every channel.
|
|
119
153
|
- **Channel adds paths** → that channel re-clones from the original input and runs the merged pass once. Other channels still share the cheaper logger-wide clone.
|
|
120
154
|
- Each path is matched independently; cost grows linearly with `paths.length`.
|