@warlock.js/logger 5.32.0 → 5.34.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 +8 -103
- package/cjs/index.cjs +230 -125
- package/cjs/index.cjs.map +1 -1
- package/esm/index.d.mts +2 -1
- package/esm/index.mjs +3 -2
- package/esm/log-channel.d.mts.map +1 -1
- package/esm/log-channel.mjs +3 -1
- package/esm/log-channel.mjs.map +1 -1
- package/esm/logger.d.mts.map +1 -1
- package/esm/logger.mjs +3 -2
- package/esm/logger.mjs.map +1 -1
- package/esm/redact/index.mjs +2 -1
- package/esm/redact/redact.d.mts +8 -6
- package/esm/redact/redact.d.mts.map +1 -1
- package/esm/redact/redact.mjs +11 -8
- package/esm/redact/redact.mjs.map +1 -1
- package/esm/redact/registered-secrets.d.mts +11 -0
- package/esm/redact/registered-secrets.d.mts.map +1 -0
- package/esm/redact/registered-secrets.mjs +105 -0
- package/esm/redact/registered-secrets.mjs.map +1 -0
- package/llms-full.txt +17 -2
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/redact-sensitive-log-fields/SKILL.md +17 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/logger` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.33.0 - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Add `registerLogSecret(value, keys?)` and `maskRegisteredLogSecrets(text)` for process-lifetime credential masking. Registered keys and literal values remain protected with defaults disabled or custom censors, including free text, Error causes and channel failure diagnostics. The safety pass masks opaque objects and does not invoke serializers/getters.
|
|
12
|
+
|
|
7
13
|
## 5.32.0 - 2026-10-18
|
|
8
14
|
|
|
9
15
|
### Changed
|
|
@@ -59,73 +65,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
59
65
|
|
|
60
66
|
### Changed
|
|
61
67
|
|
|
62
|
-
-
|
|
68
|
+
- Skills refer to other topics by name instead of file paths, so cross-references keep working after `agent-kit` copies them into an app.
|
|
63
69
|
|
|
64
70
|
## 5.28.0 - 2026-10-01
|
|
65
71
|
|
|
66
72
|
### Changed
|
|
67
73
|
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
## 5.27.0 - 2026-10-01
|
|
71
|
-
|
|
72
|
-
### Changed
|
|
73
|
-
|
|
74
|
-
- Lockstep release; package APIs are unchanged.
|
|
75
|
-
|
|
76
|
-
## 5.26.0 - 2026-09-30
|
|
77
|
-
|
|
78
|
-
### Changed
|
|
79
|
-
|
|
80
|
-
- Lockstep release; package APIs are unchanged.
|
|
81
|
-
|
|
82
|
-
## 5.25.0 - 2026-09-28
|
|
83
|
-
|
|
84
|
-
### Changed
|
|
85
|
-
|
|
86
|
-
- Lockstep release; package APIs are unchanged.
|
|
87
|
-
|
|
88
|
-
## 5.24.0 - 2026-09-27
|
|
89
|
-
|
|
90
|
-
### Changed
|
|
91
|
-
|
|
92
|
-
- Lockstep release; package APIs are unchanged.
|
|
93
|
-
|
|
94
|
-
## 5.23.2 - 2026-09-26
|
|
95
|
-
|
|
96
|
-
### Changed
|
|
97
|
-
|
|
98
|
-
- Lockstep patch release; package APIs are unchanged.
|
|
99
|
-
|
|
100
|
-
## 5.23.1 - 2026-09-26
|
|
101
|
-
|
|
102
|
-
### Changed
|
|
103
|
-
|
|
104
|
-
- Lockstep patch release; package APIs are unchanged.
|
|
105
|
-
|
|
106
|
-
## 5.23.0 - 2026-09-25
|
|
107
|
-
|
|
108
|
-
### Changed
|
|
109
|
-
|
|
110
|
-
- Lockstep patch release; package APIs are unchanged.
|
|
111
|
-
|
|
112
|
-
## 5.22.1 - 2026-09-25
|
|
113
|
-
|
|
114
|
-
### Changed
|
|
115
|
-
|
|
116
|
-
- Lockstep patch release; package APIs are unchanged.
|
|
117
|
-
|
|
118
|
-
## 5.22.0 - 2026-09-25
|
|
119
|
-
|
|
120
|
-
### Changed
|
|
121
|
-
|
|
122
|
-
- Lockstep release maintenance and dependency refresh.
|
|
123
|
-
|
|
124
|
-
## 5.21.0 - 2026-09-25
|
|
125
|
-
|
|
126
|
-
### Changed
|
|
127
|
-
|
|
128
|
-
- Lockstep release maintenance and dependency refresh.
|
|
74
|
+
- `llms.txt` now lists every skill topic with its description (frontmatter in CRLF files was skipped).
|
|
129
75
|
|
|
130
76
|
## 5.20.0 - 2026-09-24
|
|
131
77
|
|
|
@@ -145,18 +91,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
145
91
|
|
|
146
92
|
- Use the package-resolved TypeScript binary for type checking.
|
|
147
93
|
|
|
148
|
-
## 5.11.0 - 2026-09-14
|
|
149
|
-
|
|
150
|
-
_Released in lockstep with the `@warlock.js/*` family; no package-specific changes in 5.11.0._
|
|
151
|
-
|
|
152
|
-
## 5.10.0 - 2026-09-14
|
|
153
|
-
|
|
154
|
-
_Released in lockstep with the `@warlock.js/*` family; no package-specific changes in 5.10.0._
|
|
155
|
-
|
|
156
|
-
## 5.9.0 - 2026-09-13
|
|
157
|
-
|
|
158
|
-
_Released in lockstep with the `@warlock.js/*` family; no package-specific changes in 5.9.0._
|
|
159
|
-
|
|
160
94
|
## 5.7.0 - 2026-09-11
|
|
161
95
|
|
|
162
96
|
### Fixed
|
|
@@ -173,12 +107,6 @@ _Released in lockstep with the `@warlock.js/*` family; no package-specific chang
|
|
|
173
107
|
|
|
174
108
|
- Documentation shipped in this package's `skills/` told users to run `pnpm`-specific commands. `pnpm <binary>` has no npm equivalent, so those instructions failed outright for anyone not using pnpm. Commands are now package-manager neutral.
|
|
175
109
|
|
|
176
|
-
## 5.2.3 - 2026-09-02
|
|
177
|
-
|
|
178
|
-
### Fixed
|
|
179
|
-
|
|
180
|
-
- Released in exact lockstep with Core's Web generator repairs so every family dependency remains installable at 5.2.3.
|
|
181
|
-
|
|
182
110
|
## 5.2.2
|
|
183
111
|
|
|
184
112
|
- Documented: the README, CHANGELOG, and `overview` skill now describe the
|
|
@@ -189,29 +117,6 @@ _Released in lockstep with the `@warlock.js/*` family; no package-specific chang
|
|
|
189
117
|
code must not value-import this package, type-only imports are allowed, and
|
|
190
118
|
server loaders/controllers/modules may import it freely. No source change.
|
|
191
119
|
|
|
192
|
-
## 5.1.0
|
|
193
|
-
|
|
194
|
-
No changes to `@warlock.js/logger`. Released in lockstep with the `@warlock.js/web`
|
|
195
|
-
React-execution fix and the `@warlock.js/core` CLI additions — see those packages'
|
|
196
|
-
changelogs.
|
|
197
|
-
|
|
198
|
-
## 5.0.2 - 2026-08-25
|
|
199
|
-
|
|
200
|
-
No changes to `@warlock.js/logger`. Released in lockstep with the `@warlock.js/web` SSR
|
|
201
|
-
fix (`ssr.noExternal`) — see that package's changelog.
|
|
202
|
-
|
|
203
|
-
## 5.0.1 - 2026-08-25
|
|
204
|
-
|
|
205
|
-
No changes to `@warlock.js/logger`. Released in lockstep with the `create-warlock` vite
|
|
206
|
-
resolution pin and the `@warlock.js/web` peer narrowing — see those packages'
|
|
207
|
-
changelogs.
|
|
208
|
-
|
|
209
|
-
## 5.0.0 - 2026-08-25
|
|
210
|
-
|
|
211
|
-
### Changed
|
|
212
|
-
|
|
213
|
-
- This package is unchanged in 5.0.0; its version moved only because the Warlock family releases in lockstep.
|
|
214
|
-
|
|
215
120
|
## 4.16.0 - 2026-08-18
|
|
216
121
|
|
|
217
122
|
### Security
|
package/cjs/index.cjs
CHANGED
|
@@ -39,6 +39,221 @@ path = __toESM(path, 1);
|
|
|
39
39
|
let safe_stable_stringify = require("safe-stable-stringify");
|
|
40
40
|
let _mongez_reinforcements = require("@mongez/reinforcements");
|
|
41
41
|
|
|
42
|
+
//#region ../../packages/logger/src/redact/default-keys.ts
|
|
43
|
+
/**
|
|
44
|
+
* Built-in secret-key denylist — the redaction floor that applies with no
|
|
45
|
+
* configuration at all.
|
|
46
|
+
*
|
|
47
|
+
* ## Why a default list exists
|
|
48
|
+
*
|
|
49
|
+
* Path-based `redact.paths` is a *tool*; it only protects an application whose
|
|
50
|
+
* every call site was configured correctly, and nothing warns you when one
|
|
51
|
+
* wasn't. The overwhelmingly common leak is mundane and needs no attacker
|
|
52
|
+
* input: `log.error("auth", "login", "failed", { headers: req.headers, body:
|
|
53
|
+
* req.body })` puts a live bearer token and a cleartext password into every
|
|
54
|
+
* sink. A key denylist that runs by default turns redaction from a tool into a
|
|
55
|
+
* default protection.
|
|
56
|
+
*
|
|
57
|
+
* ## Matching semantics — exact, on a normalized key
|
|
58
|
+
*
|
|
59
|
+
* Keys are compared after {@link normalizeRedactKey}: lower-cased with every
|
|
60
|
+
* non-alphanumeric character removed. So one entry covers every spelling
|
|
61
|
+
* convention a codebase might use —
|
|
62
|
+
* `apiKey` / `api_key` / `API-KEY` / `apikey` all normalize to `apikey`.
|
|
63
|
+
*
|
|
64
|
+
* The match is **exact on the normalized key, not a substring**. That is a
|
|
65
|
+
* deliberate trade: substring matching (`*token*`) would also swallow
|
|
66
|
+
* `tokenCount`, `passwordUpdatedAt`, `secretsLoaded` — silent, hard-to-debug
|
|
67
|
+
* data loss in the one place engineers look when something is broken. An exact
|
|
68
|
+
* list is auditable: you can read it and know precisely what disappears. The
|
|
69
|
+
* cost is that unusual spellings are missed, which is why the list carries the
|
|
70
|
+
* concrete wire-format variants that actually show up in HTTP headers
|
|
71
|
+
* (`x-api-key`, `proxy-authorization`, `set-cookie`) and why apps can extend it
|
|
72
|
+
* with `redact.keys`.
|
|
73
|
+
*
|
|
74
|
+
* ## What is deliberately NOT here
|
|
75
|
+
*
|
|
76
|
+
* `key`, `auth`, `hash`, `signature`, `salt`, `id` — all too generic. Each
|
|
77
|
+
* would redact far more non-secret fields than secret ones (`key` alone would
|
|
78
|
+
* blank out every `key` in a keyed collection). Applications that want them
|
|
79
|
+
* add them via `redact.keys`.
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* Normalize a key for denylist comparison: lower-case, strip every
|
|
83
|
+
* non-alphanumeric character. Collapses `apiKey`, `api_key`, `API-KEY`,
|
|
84
|
+
* `Api Key` and `apikey` onto a single entry.
|
|
85
|
+
*/
|
|
86
|
+
function normalizeRedactKey(key) {
|
|
87
|
+
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The default denylist, in source spelling. Anything whose normalized key
|
|
91
|
+
* matches one of these is replaced by the configured censor (`"[REDACTED]"`
|
|
92
|
+
* by default) at any depth of `context`, `message`, or an `Error`'s own
|
|
93
|
+
* enumerable properties.
|
|
94
|
+
*
|
|
95
|
+
* Exported so applications can inspect, log, or build on the exact set they
|
|
96
|
+
* are getting rather than guessing at it.
|
|
97
|
+
*/
|
|
98
|
+
const DEFAULT_REDACT_KEYS = [
|
|
99
|
+
"password",
|
|
100
|
+
"passwd",
|
|
101
|
+
"pwd",
|
|
102
|
+
"passphrase",
|
|
103
|
+
"passwordHash",
|
|
104
|
+
"currentPassword",
|
|
105
|
+
"newPassword",
|
|
106
|
+
"passwordConfirmation",
|
|
107
|
+
"secret",
|
|
108
|
+
"clientSecret",
|
|
109
|
+
"appSecret",
|
|
110
|
+
"apiSecret",
|
|
111
|
+
"secretKey",
|
|
112
|
+
"token",
|
|
113
|
+
"accessToken",
|
|
114
|
+
"refreshToken",
|
|
115
|
+
"idToken",
|
|
116
|
+
"authToken",
|
|
117
|
+
"apiToken",
|
|
118
|
+
"bearerToken",
|
|
119
|
+
"csrfToken",
|
|
120
|
+
"sessionToken",
|
|
121
|
+
"resetToken",
|
|
122
|
+
"verificationToken",
|
|
123
|
+
"jwt",
|
|
124
|
+
"otp",
|
|
125
|
+
"apiKey",
|
|
126
|
+
"privateKey",
|
|
127
|
+
"encryptionKey",
|
|
128
|
+
"signingKey",
|
|
129
|
+
"authorization",
|
|
130
|
+
"proxyAuthorization",
|
|
131
|
+
"x-api-key",
|
|
132
|
+
"x-auth-token",
|
|
133
|
+
"cookie",
|
|
134
|
+
"set-cookie",
|
|
135
|
+
"sessionId",
|
|
136
|
+
"credentials",
|
|
137
|
+
"credential",
|
|
138
|
+
"connectionString",
|
|
139
|
+
"creditCard",
|
|
140
|
+
"creditCardNumber",
|
|
141
|
+
"cardNumber",
|
|
142
|
+
"cvv",
|
|
143
|
+
"cvc",
|
|
144
|
+
"ssn",
|
|
145
|
+
"socialSecurityNumber",
|
|
146
|
+
"iban",
|
|
147
|
+
"taxId"
|
|
148
|
+
];
|
|
149
|
+
/**
|
|
150
|
+
* The default denylist, pre-normalized. Frozen at module load so the hot path
|
|
151
|
+
* never rebuilds it.
|
|
152
|
+
*/
|
|
153
|
+
const DEFAULT_REDACT_KEY_SET = new Set(DEFAULT_REDACT_KEYS.map(normalizeRedactKey));
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region ../../packages/logger/src/redact/registered-secrets.ts
|
|
157
|
+
const keys = /* @__PURE__ */ new Set();
|
|
158
|
+
const values = /* @__PURE__ */ new Set();
|
|
159
|
+
const MASK = "[REDACTED]";
|
|
160
|
+
let orderedValues = [];
|
|
161
|
+
/**
|
|
162
|
+
* Register credentials for process-lifetime masking in every Logger instance.
|
|
163
|
+
* Empty values are ignored. Old values remain protected after rotation/removal.
|
|
164
|
+
* This safety floor is independent of redact config and custom censors.
|
|
165
|
+
*/
|
|
166
|
+
function registerLogSecret(value, secretKeys = []) {
|
|
167
|
+
for (const key of secretKeys) {
|
|
168
|
+
const normalized = normalizeRedactKey(key);
|
|
169
|
+
if (normalized) keys.add(normalized);
|
|
170
|
+
}
|
|
171
|
+
if (!value || values.has(value)) return;
|
|
172
|
+
values.add(value);
|
|
173
|
+
orderedValues = [...values].sort((left, right) => right.length - left.length);
|
|
174
|
+
}
|
|
175
|
+
/** Exact substring masking; no encoding, hashing, or fuzzy matching. */
|
|
176
|
+
function maskRegisteredLogSecrets(text) {
|
|
177
|
+
if (orderedValues.length === 0) return text;
|
|
178
|
+
let cursor = 0;
|
|
179
|
+
let result = "";
|
|
180
|
+
while (cursor < text.length) {
|
|
181
|
+
let next = -1;
|
|
182
|
+
let length = 0;
|
|
183
|
+
for (const secret of orderedValues) {
|
|
184
|
+
const index = text.indexOf(secret, cursor);
|
|
185
|
+
if (index !== -1 && (next === -1 || index < next)) {
|
|
186
|
+
next = index;
|
|
187
|
+
length = secret.length;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (next === -1) return result + text.slice(cursor);
|
|
191
|
+
result += text.slice(cursor, next) + MASK;
|
|
192
|
+
cursor = next + length;
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
/** @internal Final safety pass, after user censors have run. */
|
|
197
|
+
function redactRegisteredSecrets(value) {
|
|
198
|
+
if (keys.size === 0 && values.size === 0) return value;
|
|
199
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
200
|
+
function visit(value) {
|
|
201
|
+
if (typeof value === "string") return maskRegisteredLogSecrets(value);
|
|
202
|
+
if (typeof value === "function" || typeof value === "symbol") return MASK;
|
|
203
|
+
if (value === null || typeof value !== "object") return value;
|
|
204
|
+
if (seen.has(value)) return seen.get(value);
|
|
205
|
+
if (value instanceof Date) return new Date(Date.prototype.getTime.call(value));
|
|
206
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof WeakMap || value instanceof WeakSet || value instanceof Promise || value instanceof RegExp) return MASK;
|
|
207
|
+
if (value instanceof Map) {
|
|
208
|
+
const copy = /* @__PURE__ */ new Map();
|
|
209
|
+
seen.set(value, copy);
|
|
210
|
+
for (const [key, child] of Map.prototype.entries.call(value)) copy.set(visit(key), typeof key === "string" && keys.has(normalizeRedactKey(key)) ? MASK : visit(child));
|
|
211
|
+
return copy;
|
|
212
|
+
}
|
|
213
|
+
if (value instanceof Set) {
|
|
214
|
+
const copy = /* @__PURE__ */ new Set();
|
|
215
|
+
seen.set(value, copy);
|
|
216
|
+
for (const child of Set.prototype.values.call(value)) copy.add(visit(child));
|
|
217
|
+
return copy;
|
|
218
|
+
}
|
|
219
|
+
const copy = value instanceof Error ? /* @__PURE__ */ new Error() : Array.isArray(value) ? [] : {};
|
|
220
|
+
seen.set(value, copy);
|
|
221
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
222
|
+
if (Array.isArray(value) && key === "length") continue;
|
|
223
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
224
|
+
if (!descriptor) continue;
|
|
225
|
+
if (typeof key === "symbol") continue;
|
|
226
|
+
const name = maskRegisteredLogSecrets(key);
|
|
227
|
+
const hidden = keys.has(normalizeRedactKey(key));
|
|
228
|
+
const child = "value" in descriptor ? descriptor.value : MASK;
|
|
229
|
+
Object.defineProperty(copy, name, {
|
|
230
|
+
value: hidden ? MASK : visit(child),
|
|
231
|
+
enumerable: descriptor.enumerable,
|
|
232
|
+
writable: true,
|
|
233
|
+
configurable: true
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (value instanceof Error && !Object.hasOwn(copy, "name")) {
|
|
237
|
+
let prototype = Object.getPrototypeOf(value);
|
|
238
|
+
while (prototype) {
|
|
239
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, "name");
|
|
240
|
+
if (descriptor) {
|
|
241
|
+
const name = "value" in descriptor ? descriptor.value : MASK;
|
|
242
|
+
Object.defineProperty(copy, "name", {
|
|
243
|
+
value: typeof name === "string" ? maskRegisteredLogSecrets(name) : MASK,
|
|
244
|
+
configurable: true
|
|
245
|
+
});
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return copy;
|
|
252
|
+
}
|
|
253
|
+
return visit(value);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
42
257
|
//#region ../../packages/logger/src/log-channel.ts
|
|
43
258
|
var LogChannel = class {
|
|
44
259
|
/**
|
|
@@ -74,7 +289,7 @@ var LogChannel = class {
|
|
|
74
289
|
return;
|
|
75
290
|
}
|
|
76
291
|
const reason = error instanceof Error ? error.message : String(error);
|
|
77
|
-
process.stderr.write(`[@warlock.js/logger] channel "${this.name}" failed: ${reason}\n`);
|
|
292
|
+
process.stderr.write(maskRegisteredLogSecrets(`[@warlock.js/logger] channel "${this.name}" failed: ${reason}\n`));
|
|
78
293
|
} catch {}
|
|
79
294
|
}
|
|
80
295
|
/**
|
|
@@ -1082,120 +1297,6 @@ var SentryLog = class extends LogChannel {
|
|
|
1082
1297
|
}
|
|
1083
1298
|
};
|
|
1084
1299
|
|
|
1085
|
-
//#endregion
|
|
1086
|
-
//#region ../../packages/logger/src/redact/default-keys.ts
|
|
1087
|
-
/**
|
|
1088
|
-
* Built-in secret-key denylist — the redaction floor that applies with no
|
|
1089
|
-
* configuration at all.
|
|
1090
|
-
*
|
|
1091
|
-
* ## Why a default list exists
|
|
1092
|
-
*
|
|
1093
|
-
* Path-based `redact.paths` is a *tool*; it only protects an application whose
|
|
1094
|
-
* every call site was configured correctly, and nothing warns you when one
|
|
1095
|
-
* wasn't. The overwhelmingly common leak is mundane and needs no attacker
|
|
1096
|
-
* input: `log.error("auth", "login", "failed", { headers: req.headers, body:
|
|
1097
|
-
* req.body })` puts a live bearer token and a cleartext password into every
|
|
1098
|
-
* sink. A key denylist that runs by default turns redaction from a tool into a
|
|
1099
|
-
* default protection.
|
|
1100
|
-
*
|
|
1101
|
-
* ## Matching semantics — exact, on a normalized key
|
|
1102
|
-
*
|
|
1103
|
-
* Keys are compared after {@link normalizeRedactKey}: lower-cased with every
|
|
1104
|
-
* non-alphanumeric character removed. So one entry covers every spelling
|
|
1105
|
-
* convention a codebase might use —
|
|
1106
|
-
* `apiKey` / `api_key` / `API-KEY` / `apikey` all normalize to `apikey`.
|
|
1107
|
-
*
|
|
1108
|
-
* The match is **exact on the normalized key, not a substring**. That is a
|
|
1109
|
-
* deliberate trade: substring matching (`*token*`) would also swallow
|
|
1110
|
-
* `tokenCount`, `passwordUpdatedAt`, `secretsLoaded` — silent, hard-to-debug
|
|
1111
|
-
* data loss in the one place engineers look when something is broken. An exact
|
|
1112
|
-
* list is auditable: you can read it and know precisely what disappears. The
|
|
1113
|
-
* cost is that unusual spellings are missed, which is why the list carries the
|
|
1114
|
-
* concrete wire-format variants that actually show up in HTTP headers
|
|
1115
|
-
* (`x-api-key`, `proxy-authorization`, `set-cookie`) and why apps can extend it
|
|
1116
|
-
* with `redact.keys`.
|
|
1117
|
-
*
|
|
1118
|
-
* ## What is deliberately NOT here
|
|
1119
|
-
*
|
|
1120
|
-
* `key`, `auth`, `hash`, `signature`, `salt`, `id` — all too generic. Each
|
|
1121
|
-
* would redact far more non-secret fields than secret ones (`key` alone would
|
|
1122
|
-
* blank out every `key` in a keyed collection). Applications that want them
|
|
1123
|
-
* add them via `redact.keys`.
|
|
1124
|
-
*/
|
|
1125
|
-
/**
|
|
1126
|
-
* Normalize a key for denylist comparison: lower-case, strip every
|
|
1127
|
-
* non-alphanumeric character. Collapses `apiKey`, `api_key`, `API-KEY`,
|
|
1128
|
-
* `Api Key` and `apikey` onto a single entry.
|
|
1129
|
-
*/
|
|
1130
|
-
function normalizeRedactKey(key) {
|
|
1131
|
-
return key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* The default denylist, in source spelling. Anything whose normalized key
|
|
1135
|
-
* matches one of these is replaced by the configured censor (`"[REDACTED]"`
|
|
1136
|
-
* by default) at any depth of `context`, `message`, or an `Error`'s own
|
|
1137
|
-
* enumerable properties.
|
|
1138
|
-
*
|
|
1139
|
-
* Exported so applications can inspect, log, or build on the exact set they
|
|
1140
|
-
* are getting rather than guessing at it.
|
|
1141
|
-
*/
|
|
1142
|
-
const DEFAULT_REDACT_KEYS = [
|
|
1143
|
-
"password",
|
|
1144
|
-
"passwd",
|
|
1145
|
-
"pwd",
|
|
1146
|
-
"passphrase",
|
|
1147
|
-
"passwordHash",
|
|
1148
|
-
"currentPassword",
|
|
1149
|
-
"newPassword",
|
|
1150
|
-
"passwordConfirmation",
|
|
1151
|
-
"secret",
|
|
1152
|
-
"clientSecret",
|
|
1153
|
-
"appSecret",
|
|
1154
|
-
"apiSecret",
|
|
1155
|
-
"secretKey",
|
|
1156
|
-
"token",
|
|
1157
|
-
"accessToken",
|
|
1158
|
-
"refreshToken",
|
|
1159
|
-
"idToken",
|
|
1160
|
-
"authToken",
|
|
1161
|
-
"apiToken",
|
|
1162
|
-
"bearerToken",
|
|
1163
|
-
"csrfToken",
|
|
1164
|
-
"sessionToken",
|
|
1165
|
-
"resetToken",
|
|
1166
|
-
"verificationToken",
|
|
1167
|
-
"jwt",
|
|
1168
|
-
"otp",
|
|
1169
|
-
"apiKey",
|
|
1170
|
-
"privateKey",
|
|
1171
|
-
"encryptionKey",
|
|
1172
|
-
"signingKey",
|
|
1173
|
-
"authorization",
|
|
1174
|
-
"proxyAuthorization",
|
|
1175
|
-
"x-api-key",
|
|
1176
|
-
"x-auth-token",
|
|
1177
|
-
"cookie",
|
|
1178
|
-
"set-cookie",
|
|
1179
|
-
"sessionId",
|
|
1180
|
-
"credentials",
|
|
1181
|
-
"credential",
|
|
1182
|
-
"connectionString",
|
|
1183
|
-
"creditCard",
|
|
1184
|
-
"creditCardNumber",
|
|
1185
|
-
"cardNumber",
|
|
1186
|
-
"cvv",
|
|
1187
|
-
"cvc",
|
|
1188
|
-
"ssn",
|
|
1189
|
-
"socialSecurityNumber",
|
|
1190
|
-
"iban",
|
|
1191
|
-
"taxId"
|
|
1192
|
-
];
|
|
1193
|
-
/**
|
|
1194
|
-
* The default denylist, pre-normalized. Frozen at module load so the hot path
|
|
1195
|
-
* never rebuilds it.
|
|
1196
|
-
*/
|
|
1197
|
-
const DEFAULT_REDACT_KEY_SET = new Set(DEFAULT_REDACT_KEYS.map(normalizeRedactKey));
|
|
1198
|
-
|
|
1199
1300
|
//#endregion
|
|
1200
1301
|
//#region ../../packages/logger/src/redact/redact.ts
|
|
1201
1302
|
const DEFAULT_CENSOR = "[REDACTED]";
|
|
@@ -1402,17 +1503,19 @@ function resolveRedactKeys(config) {
|
|
|
1402
1503
|
*
|
|
1403
1504
|
* ## Residual gaps (by design, documented rather than silently absent)
|
|
1404
1505
|
*
|
|
1405
|
-
* - **
|
|
1406
|
-
*
|
|
1407
|
-
* - **`Map`/`Set`/`Buffer` contents** are
|
|
1408
|
-
*
|
|
1409
|
-
*
|
|
1410
|
-
*
|
|
1506
|
+
* - **Unregistered secrets interpolated into strings** cannot be reached.
|
|
1507
|
+
* registerLogSecret protects known literal values, including Error text.
|
|
1508
|
+
* - Without registered credentials, **`Map`/`Set`/`Buffer` contents** are
|
|
1509
|
+
* not traversed (see {@link isOpaque}). Registered credentials add a
|
|
1510
|
+
* final safety pass that traverses Maps/Sets and masks opaque objects.
|
|
1511
|
+
* - Key/path redaction does not walk non-enumerable properties. With
|
|
1512
|
+
* registered credentials the final pass walks data descriptors and replaces
|
|
1513
|
+
* accessors without invoking them.
|
|
1411
1514
|
*/
|
|
1412
1515
|
function applyRedact(data, config) {
|
|
1413
1516
|
const paths = config?.paths ?? [];
|
|
1414
1517
|
const keys = resolveRedactKeys(config);
|
|
1415
|
-
if (paths.length === 0 && !keys) return data;
|
|
1518
|
+
if (paths.length === 0 && !keys) return redactRegisteredSecrets(data);
|
|
1416
1519
|
const censor = config?.censor ?? DEFAULT_CENSOR;
|
|
1417
1520
|
let result = data;
|
|
1418
1521
|
const censored = /* @__PURE__ */ new Set();
|
|
@@ -1429,7 +1532,7 @@ function applyRedact(data, config) {
|
|
|
1429
1532
|
censor,
|
|
1430
1533
|
alreadyCensored: censored
|
|
1431
1534
|
});
|
|
1432
|
-
return result;
|
|
1535
|
+
return redactRegisteredSecrets(result);
|
|
1433
1536
|
}
|
|
1434
1537
|
/**
|
|
1435
1538
|
* Combine two redact configs into one effective config. Used to merge a
|
|
@@ -1774,7 +1877,7 @@ var Logger = class {
|
|
|
1774
1877
|
}
|
|
1775
1878
|
reportChannelError(error, channel, entry) {
|
|
1776
1879
|
try {
|
|
1777
|
-
const outcome = this.channelErrorHandler(error, channel, entry);
|
|
1880
|
+
const outcome = this.channelErrorHandler(redactRegisteredSecrets(error), channel, redactRegisteredSecrets(entry));
|
|
1778
1881
|
if (outcome && typeof outcome.then === "function") outcome.then(void 0, () => {});
|
|
1779
1882
|
} catch {}
|
|
1780
1883
|
}
|
|
@@ -2013,7 +2116,7 @@ function redactionFailedEntry(data) {
|
|
|
2013
2116
|
function reportChannelError(error, channel) {
|
|
2014
2117
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2015
2118
|
try {
|
|
2016
|
-
process.stderr.write(`[@warlock.js/logger] channel "${channel.name}" failed to log: ${reason}\n`);
|
|
2119
|
+
process.stderr.write(maskRegisteredLogSecrets(`[@warlock.js/logger] channel "${channel.name}" failed to log: ${reason}\n`));
|
|
2017
2120
|
} catch {}
|
|
2018
2121
|
}
|
|
2019
2122
|
/**
|
|
@@ -2102,8 +2205,10 @@ exports.escapeLogValue = escapeLogValue;
|
|
|
2102
2205
|
exports.log = log;
|
|
2103
2206
|
exports.maskEmail = maskEmail;
|
|
2104
2207
|
exports.maskEmailsInText = maskEmailsInText;
|
|
2208
|
+
exports.maskRegisteredLogSecrets = maskRegisteredLogSecrets;
|
|
2105
2209
|
exports.mergeRedact = mergeRedact;
|
|
2106
2210
|
exports.normalizeRedactKey = normalizeRedactKey;
|
|
2211
|
+
exports.registerLogSecret = registerLogSecret;
|
|
2107
2212
|
exports.resolveRedactKeys = resolveRedactKeys;
|
|
2108
2213
|
exports.safeJsonStringify = safeJsonStringify;
|
|
2109
2214
|
//# sourceMappingURL=index.cjs.map
|