@logtape/redaction 2.2.0-dev.679 → 2.2.0-dev.683
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/dist/field.cjs +87 -29
- package/dist/field.d.cts +27 -2
- package/dist/field.d.cts.map +1 -1
- package/dist/field.d.ts +27 -2
- package/dist/field.d.ts.map +1 -1
- package/dist/field.js +87 -29
- package/dist/field.js.map +1 -1
- package/dist/mod.d.cts +3 -2
- package/dist/mod.d.ts +3 -2
- package/dist/pattern.cjs +55 -8
- package/dist/pattern.d.cts +22 -3
- package/dist/pattern.d.cts.map +1 -1
- package/dist/pattern.d.ts +22 -3
- package/dist/pattern.d.ts.map +1 -1
- package/dist/pattern.js +55 -8
- package/dist/pattern.js.map +1 -1
- package/dist/traversal.cjs +30 -0
- package/dist/traversal.d.cts +20 -0
- package/dist/traversal.d.cts.map +1 -0
- package/dist/traversal.d.ts +20 -0
- package/dist/traversal.d.ts.map +1 -0
- package/dist/traversal.js +29 -0
- package/dist/traversal.js.map +1 -0
- package/package.json +2 -2
package/dist/field.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { createRedactionTraversalContext, redactionTruncatedValue } from "./traversal.js";
|
|
1
2
|
import { getLogger } from "@logtape/logtape";
|
|
2
3
|
|
|
3
4
|
//#region src/field.ts
|
|
4
5
|
const metaLogger = getLogger(["logtape", "meta"]);
|
|
5
6
|
let reportingRedactionFailure = false;
|
|
7
|
+
let reportingRedactionLimit = false;
|
|
6
8
|
/**
|
|
7
9
|
* Default field patterns for redaction. These patterns will match
|
|
8
10
|
* common sensitive fields such as passwords, tokens, and personal
|
|
@@ -53,15 +55,16 @@ const DEFAULT_REDACT_FIELDS = [
|
|
|
53
55
|
function redactByField(sink, options = DEFAULT_REDACT_FIELDS) {
|
|
54
56
|
const opts = Array.isArray(options) ? { fieldPatterns: options } : options;
|
|
55
57
|
const wrapped = (record) => {
|
|
56
|
-
const
|
|
58
|
+
const context = createFieldRedactionContext(opts);
|
|
59
|
+
const redactedProperties = redactProperties(record.properties, opts, context.visited, 0, context);
|
|
57
60
|
let redactedMessage = record.message;
|
|
58
61
|
if (typeof record.rawMessage === "string") {
|
|
59
62
|
const placeholders = extractPlaceholderNames(record.rawMessage);
|
|
60
63
|
const { redactedIndices, wildcardIndices } = getRedactedPlaceholderIndices(placeholders, opts.fieldPatterns);
|
|
61
|
-
redactedMessage = redactMessageArray(record.message, placeholders, redactedIndices, wildcardIndices, redactedProperties, opts.action);
|
|
64
|
+
redactedMessage = redactMessageArray(record.message, placeholders, redactedIndices, wildcardIndices, redactedProperties, opts.action, context.exceededLimits.size > 0);
|
|
62
65
|
} else {
|
|
63
66
|
const redactedValues = getRedactedValues(record.properties, redactedProperties);
|
|
64
|
-
if (redactedValues.size > 0) redactedMessage = redactMessageByValues(record.message, redactedValues);
|
|
67
|
+
if (redactedValues.size > 0 || context.exceededLimits.size > 0) redactedMessage = redactMessageByValues(record.message, redactedValues, context.exceededLimits.size > 0);
|
|
65
68
|
}
|
|
66
69
|
sink({
|
|
67
70
|
...record,
|
|
@@ -145,18 +148,19 @@ function redactByFieldAsync(sink, options) {
|
|
|
145
148
|
return wrapped;
|
|
146
149
|
}
|
|
147
150
|
async function redactLogRecordAsync(record, options) {
|
|
148
|
-
const
|
|
151
|
+
const context = createFieldRedactionContext(options);
|
|
152
|
+
const redactedProperties = await redactPropertiesAsync(record.properties, options, context.visited, 0, context);
|
|
149
153
|
if (typeof record.rawMessage === "string") {
|
|
150
154
|
const placeholders = extractPlaceholderNames(record.rawMessage);
|
|
151
155
|
const { redactedIndices, wildcardIndices } = getRedactedPlaceholderIndices(placeholders, options.fieldPatterns);
|
|
152
156
|
return {
|
|
153
|
-
message: await redactMessageArrayAsync(record.message, placeholders, redactedIndices, wildcardIndices, redactedProperties, options.action),
|
|
157
|
+
message: await redactMessageArrayAsync(record.message, placeholders, redactedIndices, wildcardIndices, redactedProperties, options.action, context.exceededLimits.size > 0),
|
|
154
158
|
properties: redactedProperties
|
|
155
159
|
};
|
|
156
160
|
}
|
|
157
161
|
let redactedMessage = record.message;
|
|
158
162
|
const redactedValues = getRedactedValues(record.properties, redactedProperties);
|
|
159
|
-
if (redactedValues.size > 0) redactedMessage = redactMessageByValues(record.message, redactedValues);
|
|
163
|
+
if (redactedValues.size > 0 || context.exceededLimits.size > 0) redactedMessage = redactMessageByValues(record.message, redactedValues, context.exceededLimits.size > 0);
|
|
160
164
|
return {
|
|
161
165
|
message: redactedMessage,
|
|
162
166
|
properties: redactedProperties
|
|
@@ -171,6 +175,25 @@ function reportRedactionFailure(error) {
|
|
|
171
175
|
reportingRedactionFailure = false;
|
|
172
176
|
}
|
|
173
177
|
}
|
|
178
|
+
function reportRedactionLimitExceeded(limit, limits) {
|
|
179
|
+
if (reportingRedactionLimit || typeof metaLogger.warn !== "function") return;
|
|
180
|
+
try {
|
|
181
|
+
reportingRedactionLimit = true;
|
|
182
|
+
metaLogger.warn("Redaction traversal exceeded {limit}; replacing or omitting remaining data to keep logging bounded.", {
|
|
183
|
+
limit,
|
|
184
|
+
...limits
|
|
185
|
+
});
|
|
186
|
+
} catch {} finally {
|
|
187
|
+
reportingRedactionLimit = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function createFieldRedactionContext(options, visited = /* @__PURE__ */ new Map()) {
|
|
191
|
+
return createRedactionTraversalContext(options, reportRedactionLimitExceeded, visited);
|
|
192
|
+
}
|
|
193
|
+
function reportLimitOnce(context, limit) {
|
|
194
|
+
if (context.exceededLimits.has(limit)) return;
|
|
195
|
+
context.reportLimitExceeded(limit);
|
|
196
|
+
}
|
|
174
197
|
/**
|
|
175
198
|
* Creates an asynchronous pseudonymizer based on HMAC using the Web Crypto API.
|
|
176
199
|
*
|
|
@@ -216,19 +239,27 @@ async function createHmacPseudonymizer(options) {
|
|
|
216
239
|
* @returns The redacted properties.
|
|
217
240
|
* @since 0.10.0
|
|
218
241
|
*/
|
|
219
|
-
function redactProperties(properties, options, visited = /* @__PURE__ */ new Map()) {
|
|
242
|
+
function redactProperties(properties, options, visited = /* @__PURE__ */ new Map(), depth = 0, context = createFieldRedactionContext(options, visited)) {
|
|
220
243
|
if (visited.has(properties)) return visited.get(properties);
|
|
221
244
|
const copy = {};
|
|
222
245
|
visited.set(properties, copy);
|
|
223
|
-
|
|
246
|
+
const fields = Object.keys(properties);
|
|
247
|
+
if (fields.length > context.limits.maxProperties) reportLimitOnce(context, "maxProperties");
|
|
248
|
+
for (const field of fields.slice(0, context.limits.maxProperties)) {
|
|
224
249
|
if (shouldFieldRedacted(field, options.fieldPatterns)) {
|
|
225
250
|
if (typeof options.action === "function") setProperty(copy, field, options.action(properties[field]));
|
|
226
251
|
continue;
|
|
227
252
|
}
|
|
228
253
|
const value = properties[field];
|
|
229
|
-
if (Array.isArray(value))
|
|
254
|
+
if (Array.isArray(value)) if (depth + 1 > context.limits.maxDepth) {
|
|
255
|
+
reportLimitOnce(context, "maxDepth");
|
|
256
|
+
setProperty(copy, field, redactionTruncatedValue);
|
|
257
|
+
} else setProperty(copy, field, redactArray(value, options, visited, depth + 1, context));
|
|
230
258
|
else if (typeof value === "object" && value !== null) if (isBuiltInObject(value)) setProperty(copy, field, value);
|
|
231
|
-
else
|
|
259
|
+
else if (depth + 1 > context.limits.maxDepth) {
|
|
260
|
+
reportLimitOnce(context, "maxDepth");
|
|
261
|
+
setProperty(copy, field, redactionTruncatedValue);
|
|
262
|
+
} else setProperty(copy, field, redactProperties(value, options, visited, depth + 1, context));
|
|
232
263
|
else setProperty(copy, field, value);
|
|
233
264
|
}
|
|
234
265
|
return copy;
|
|
@@ -241,22 +272,30 @@ function redactProperties(properties, options, visited = /* @__PURE__ */ new Map
|
|
|
241
272
|
* @returns The redacted properties.
|
|
242
273
|
* @since 2.1.0
|
|
243
274
|
*/
|
|
244
|
-
async function redactPropertiesAsync(properties, options, visited = /* @__PURE__ */ new Map()) {
|
|
275
|
+
async function redactPropertiesAsync(properties, options, visited = /* @__PURE__ */ new Map(), depth = 0, context = createFieldRedactionContext(options, visited)) {
|
|
245
276
|
if (visited.has(properties)) return visited.get(properties);
|
|
246
277
|
const copy = {};
|
|
247
278
|
visited.set(properties, copy);
|
|
248
279
|
const fields = [];
|
|
249
280
|
const values = [];
|
|
250
|
-
|
|
281
|
+
const propertyFields = Object.keys(properties);
|
|
282
|
+
if (propertyFields.length > context.limits.maxProperties) reportLimitOnce(context, "maxProperties");
|
|
283
|
+
for (const field of propertyFields.slice(0, context.limits.maxProperties)) {
|
|
251
284
|
fields.push(field);
|
|
252
285
|
if (shouldFieldRedacted(field, options.fieldPatterns)) {
|
|
253
286
|
values.push(Promise.resolve(options.action(properties[field])));
|
|
254
287
|
continue;
|
|
255
288
|
}
|
|
256
289
|
const value = properties[field];
|
|
257
|
-
if (Array.isArray(value))
|
|
290
|
+
if (Array.isArray(value)) if (depth + 1 > context.limits.maxDepth) {
|
|
291
|
+
reportLimitOnce(context, "maxDepth");
|
|
292
|
+
values.push(Promise.resolve(redactionTruncatedValue));
|
|
293
|
+
} else values.push(redactArrayAsync(value, options, visited, depth + 1, context));
|
|
258
294
|
else if (typeof value === "object" && value !== null) if (isBuiltInObject(value)) values.push(Promise.resolve(value));
|
|
259
|
-
else
|
|
295
|
+
else if (depth + 1 > context.limits.maxDepth) {
|
|
296
|
+
reportLimitOnce(context, "maxDepth");
|
|
297
|
+
values.push(Promise.resolve(redactionTruncatedValue));
|
|
298
|
+
} else values.push(redactPropertiesAsync(value, options, visited, depth + 1, context));
|
|
260
299
|
else values.push(Promise.resolve(value));
|
|
261
300
|
}
|
|
262
301
|
const redactedValues = await Promise.all(values);
|
|
@@ -279,36 +318,52 @@ function setProperty(object, field, value) {
|
|
|
279
318
|
* @param visited Map of visited objects to prevent circular reference issues.
|
|
280
319
|
* @returns A new array with redacted values.
|
|
281
320
|
*/
|
|
282
|
-
function redactArray(array, options, visited) {
|
|
321
|
+
function redactArray(array, options, visited, depth, context) {
|
|
283
322
|
if (visited.has(array)) return visited.get(array);
|
|
284
323
|
const copy = [];
|
|
285
|
-
|
|
324
|
+
const length = Math.min(array.length, context.limits.maxProperties);
|
|
325
|
+
copy.length = length;
|
|
286
326
|
visited.set(array, copy);
|
|
287
|
-
|
|
327
|
+
if (array.length > context.limits.maxProperties) reportLimitOnce(context, "maxProperties");
|
|
328
|
+
for (let i = 0; i < length; i++) {
|
|
288
329
|
if (!(i in array)) continue;
|
|
289
330
|
const item = array[i];
|
|
290
|
-
if (Array.isArray(item))
|
|
331
|
+
if (Array.isArray(item)) if (depth + 1 > context.limits.maxDepth) {
|
|
332
|
+
reportLimitOnce(context, "maxDepth");
|
|
333
|
+
copy[i] = redactionTruncatedValue;
|
|
334
|
+
} else copy[i] = redactArray(item, options, visited, depth + 1, context);
|
|
291
335
|
else if (typeof item === "object" && item !== null) if (isBuiltInObject(item)) copy[i] = item;
|
|
292
|
-
else
|
|
336
|
+
else if (depth + 1 > context.limits.maxDepth) {
|
|
337
|
+
reportLimitOnce(context, "maxDepth");
|
|
338
|
+
copy[i] = redactionTruncatedValue;
|
|
339
|
+
} else copy[i] = redactProperties(item, options, visited, depth + 1, context);
|
|
293
340
|
else copy[i] = item;
|
|
294
341
|
}
|
|
295
342
|
return copy;
|
|
296
343
|
}
|
|
297
|
-
async function redactArrayAsync(array, options, visited) {
|
|
344
|
+
async function redactArrayAsync(array, options, visited, depth, context) {
|
|
298
345
|
if (visited.has(array)) return visited.get(array);
|
|
299
346
|
const copy = [];
|
|
300
|
-
|
|
347
|
+
const length = Math.min(array.length, context.limits.maxProperties);
|
|
348
|
+
copy.length = length;
|
|
301
349
|
visited.set(array, copy);
|
|
302
350
|
const itemPromises = [];
|
|
303
|
-
|
|
351
|
+
if (array.length > context.limits.maxProperties) reportLimitOnce(context, "maxProperties");
|
|
352
|
+
for (let i = 0; i < length; i++) {
|
|
304
353
|
if (!(i in array)) {
|
|
305
354
|
itemPromises.push(Promise.resolve(void 0));
|
|
306
355
|
continue;
|
|
307
356
|
}
|
|
308
357
|
const item = array[i];
|
|
309
|
-
if (Array.isArray(item))
|
|
358
|
+
if (Array.isArray(item)) if (depth + 1 > context.limits.maxDepth) {
|
|
359
|
+
reportLimitOnce(context, "maxDepth");
|
|
360
|
+
itemPromises.push(Promise.resolve(redactionTruncatedValue));
|
|
361
|
+
} else itemPromises.push(redactArrayAsync(item, options, visited, depth + 1, context));
|
|
310
362
|
else if (typeof item === "object" && item !== null) if (isBuiltInObject(item)) itemPromises.push(Promise.resolve(item));
|
|
311
|
-
else
|
|
363
|
+
else if (depth + 1 > context.limits.maxDepth) {
|
|
364
|
+
reportLimitOnce(context, "maxDepth");
|
|
365
|
+
itemPromises.push(Promise.resolve(redactionTruncatedValue));
|
|
366
|
+
} else itemPromises.push(redactPropertiesAsync(item, options, visited, depth + 1, context));
|
|
312
367
|
else itemPromises.push(Promise.resolve(item));
|
|
313
368
|
}
|
|
314
369
|
const redactedItems = await Promise.all(itemPromises);
|
|
@@ -463,7 +518,7 @@ function getRedactedPlaceholderIndices(placeholders, fieldPatterns) {
|
|
|
463
518
|
* @param action The redaction action.
|
|
464
519
|
* @returns New message array with redacted values.
|
|
465
520
|
*/
|
|
466
|
-
function redactMessageArray(message, placeholders, redactedIndices, wildcardIndices, redactedProperties, action) {
|
|
521
|
+
function redactMessageArray(message, placeholders, redactedIndices, wildcardIndices, redactedProperties, action, truncateUnmappedValues = false) {
|
|
467
522
|
const result = [];
|
|
468
523
|
let placeholderIndex = 0;
|
|
469
524
|
for (let i = 0; i < message.length; i++) if (i % 2 === 0) result.push(message[i]);
|
|
@@ -475,13 +530,14 @@ function redactMessageArray(message, placeholders, redactedIndices, wildcardIndi
|
|
|
475
530
|
const placeholderName = placeholders[placeholderIndex];
|
|
476
531
|
const redactedValue = getPathValue(redactedProperties, placeholderName);
|
|
477
532
|
if (redactedValue.found) result.push(redactedValue.value);
|
|
533
|
+
else if (truncateUnmappedValues) result.push(redactionTruncatedValue);
|
|
478
534
|
else result.push(message[i]);
|
|
479
535
|
}
|
|
480
536
|
placeholderIndex++;
|
|
481
537
|
}
|
|
482
538
|
return result;
|
|
483
539
|
}
|
|
484
|
-
async function redactMessageArrayAsync(message, placeholders, redactedIndices, wildcardIndices, redactedProperties, action) {
|
|
540
|
+
async function redactMessageArrayAsync(message, placeholders, redactedIndices, wildcardIndices, redactedProperties, action, truncateUnmappedValues = false) {
|
|
485
541
|
const result = [];
|
|
486
542
|
const tasks = [];
|
|
487
543
|
let placeholderIndex = 0;
|
|
@@ -498,6 +554,7 @@ async function redactMessageArrayAsync(message, placeholders, redactedIndices, w
|
|
|
498
554
|
const placeholderName = placeholders[placeholderIndex];
|
|
499
555
|
const redactedValue = getPathValue(redactedProperties, placeholderName);
|
|
500
556
|
if (redactedValue.found) result.push(redactedValue.value);
|
|
557
|
+
else if (truncateUnmappedValues) result.push(redactionTruncatedValue);
|
|
501
558
|
else result.push(message[i]);
|
|
502
559
|
}
|
|
503
560
|
placeholderIndex++;
|
|
@@ -525,7 +582,7 @@ function getPathValue(properties, path) {
|
|
|
525
582
|
* @param map The map to populate with original -> redacted value pairs.
|
|
526
583
|
*/
|
|
527
584
|
function collectRedactedValues(original, redacted, map) {
|
|
528
|
-
for (const key of Object.keys(
|
|
585
|
+
for (const key of Object.keys(redacted)) {
|
|
529
586
|
const origVal = original[key];
|
|
530
587
|
const redVal = redacted[key];
|
|
531
588
|
if (origVal !== redVal) map.set(origVal, redVal);
|
|
@@ -550,13 +607,14 @@ function getRedactedValues(original, redacted) {
|
|
|
550
607
|
* @param redactedValues Map of original -> redacted values.
|
|
551
608
|
* @returns New message array with redacted values.
|
|
552
609
|
*/
|
|
553
|
-
function redactMessageByValues(message, redactedValues) {
|
|
554
|
-
if (redactedValues.size === 0) return message;
|
|
610
|
+
function redactMessageByValues(message, redactedValues, truncateUnmappedValues = false) {
|
|
611
|
+
if (redactedValues.size === 0 && !truncateUnmappedValues) return message;
|
|
555
612
|
const result = [];
|
|
556
613
|
for (let i = 0; i < message.length; i++) if (i % 2 === 0) result.push(message[i]);
|
|
557
614
|
else {
|
|
558
615
|
const val = message[i];
|
|
559
616
|
if (redactedValues.has(val)) result.push(redactedValues.get(val));
|
|
617
|
+
else if (truncateUnmappedValues) result.push(redactionTruncatedValue);
|
|
560
618
|
else result.push(val);
|
|
561
619
|
}
|
|
562
620
|
return result;
|
package/dist/field.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field.js","names":["DEFAULT_REDACT_FIELDS: FieldPatterns","sink: Sink | Sink & Disposable | Sink & AsyncDisposable","options: FieldRedactionOptions | FieldPatterns","record: LogRecord","options: AsyncFieldRedactionOptions","sinkErrors: unknown[]","wrapped: Sink & AsyncDisposable","disposeError: unknown","error: unknown","options: HmacPseudonymizerOptions","value: unknown","properties: Record<string, unknown>","options: FieldRedactionOptions","copy: Record<string, unknown>","fields: string[]","values: Promise<unknown>[]","object: Record<string, unknown>","field: string","array: unknown[]","visited: Map<object, object>","copy: unknown[]","itemPromises: Promise<unknown>[]","value: object","fieldPatterns: FieldPatterns","fieldPattern: RegExp","template: string","placeholders: string[]","path: string","segments: string[]","quote: '\"' | \"'\" | undefined","message: readonly unknown[]","redactedIndices: Set<number>","wildcardIndices: Set<number>","redactedProperties: Record<string, unknown>","action: \"delete\" | ((value: unknown) => unknown) | undefined","result: unknown[]","action: AsyncFieldRedactionAction","tasks: Promise<void>[]","original: Record<string, unknown>","redacted: Record<string, unknown>","map: Map<unknown, unknown>","redactedValues: Map<unknown, unknown>","key: string | Uint8Array | ArrayBuffer","key: string | Uint8Array | ArrayBuffer | CryptoKey","hash: HmacPseudonymizerOptions[\"hash\"]","key: CryptoKey","bytes: Uint8Array","encoding: \"base64url\" | \"hex\""],"sources":["../src/field.ts"],"sourcesContent":["import { getLogger, type LogRecord, type Sink } from \"@logtape/logtape\";\n\n/**\n * The type for a field pattern used in redaction. A string or a regular\n * expression that matches field names.\n * @since 0.10.0\n */\nexport type FieldPattern = string | RegExp;\n\n/**\n * An array of field patterns used for redaction. Each pattern can be\n * a string or a regular expression that matches field names.\n * @since 0.10.0\n */\nexport type FieldPatterns = FieldPattern[];\n\n/**\n * The synchronous action to perform on a redacted field value.\n * @since 0.10.0\n */\nexport type FieldRedactionAction = \"delete\" | ((value: unknown) => unknown);\n\n/**\n * The asynchronous action to perform on a redacted field value.\n * @since 2.1.0\n */\nexport type AsyncFieldRedactionAction = (\n value: unknown,\n) => PromiseLike<unknown>;\n\n/**\n * A pseudonymizer created by {@link createHmacPseudonymizer}.\n * @since 2.1.0\n */\nexport type HmacPseudonymizer = (value: unknown) => Promise<string>;\n\nconst metaLogger = getLogger([\"logtape\", \"meta\"]);\nlet reportingRedactionFailure = false;\n\n/**\n * Default field patterns for redaction. These patterns will match\n * common sensitive fields such as passwords, tokens, and personal\n * information.\n * @since 0.10.0\n */\nexport const DEFAULT_REDACT_FIELDS: FieldPatterns = [\n /pass(?:code|phrase|word)/i,\n /secret/i,\n /token/i,\n /key/i,\n /credential/i,\n /auth/i,\n /signature/i,\n /sensitive/i,\n /private/i,\n /ssn/i,\n /email/i,\n /phone/i,\n /address/i,\n];\n\n/**\n * Options for redacting fields in a {@link LogRecord}. Used by\n * the {@link redactByField} function.\n * @since 0.10.0\n */\nexport interface FieldRedactionOptions {\n /**\n * The field patterns to match against. This can be an array of\n * strings or regular expressions. If a field matches any of the\n * patterns, it will be redacted.\n * @defaultValue {@link DEFAULT_REDACT_FIELDS}\n */\n readonly fieldPatterns: FieldPatterns;\n\n /**\n * The action to perform on the matched fields. If not provided,\n * the default action is to delete the field from the properties.\n * If a function is provided, it will be called with the\n * value of the field, and the return value will be used to replace\n * the field in the properties.\n * If the action is `\"delete\"`, the field will be removed from the\n * properties.\n * @default `\"delete\"`\n */\n readonly action?: FieldRedactionAction;\n}\n\n/**\n * Options for asynchronously redacting fields in a {@link LogRecord}. Used by\n * the {@link redactByFieldAsync} function.\n * @since 2.1.0\n */\nexport interface AsyncFieldRedactionOptions {\n /**\n * The field patterns to match against. This can be an array of\n * strings or regular expressions. If a field matches any of the\n * patterns, it will be redacted.\n */\n readonly fieldPatterns: FieldPatterns;\n\n /**\n * The asynchronous action to perform on the matched fields.\n */\n readonly action: AsyncFieldRedactionAction;\n}\n\n/**\n * Options for the {@link createHmacPseudonymizer} function.\n * @since 2.1.0\n */\nexport interface HmacPseudonymizerOptions {\n /**\n * The secret key to use for HMAC. Strings are encoded as UTF-8.\n */\n readonly key: string | Uint8Array | ArrayBuffer | CryptoKey;\n\n /**\n * The HMAC hash algorithm.\n * @default `\"SHA-256\"`\n */\n readonly hash?: \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\n /**\n * The digest encoding.\n * @default `\"base64url\"`\n */\n readonly encoding?: \"base64url\" | \"hex\";\n\n /**\n * The string prefix to prepend to each pseudonym. If omitted, a prefix based\n * on the hash algorithm is used, such as `\"hmac-sha256:\"`. Set this to an\n * empty string to disable the prefix.\n */\n readonly prefix?: string;\n}\n\n/**\n * Redacts properties and message values in a {@link LogRecord} based on the\n * provided field patterns and action.\n *\n * Note that it is a decorator which wraps the sink and redacts properties\n * and message values before passing them to the sink.\n *\n * For string templates (e.g., `\"Hello, {name}!\"`), placeholder names are\n * matched against the field patterns to determine which values to redact.\n *\n * For tagged template literals (e.g., `` `Hello, ${name}!` ``), redaction\n * is performed by comparing message values with redacted property values.\n *\n * @example\n * ```ts\n * import { getConsoleSink } from \"@logtape/logtape\";\n * import { redactByField } from \"@logtape/redaction\";\n *\n * const sink = redactByField(getConsoleSink());\n * ```\n *\n * @param sink The sink to wrap.\n * @param options The redaction options.\n * @returns The wrapped sink.\n * @since 0.10.0\n */\nexport function redactByField(\n sink: Sink | Sink & Disposable | Sink & AsyncDisposable,\n options: FieldRedactionOptions | FieldPatterns = DEFAULT_REDACT_FIELDS,\n): Sink | Sink & Disposable | Sink & AsyncDisposable {\n const opts = Array.isArray(options) ? { fieldPatterns: options } : options;\n const wrapped = (record: LogRecord) => {\n const redactedProperties = redactProperties(record.properties, opts);\n let redactedMessage = record.message;\n\n if (typeof record.rawMessage === \"string\") {\n // String template: redact by placeholder names\n const placeholders = extractPlaceholderNames(record.rawMessage);\n const { redactedIndices, wildcardIndices } =\n getRedactedPlaceholderIndices(\n placeholders,\n opts.fieldPatterns,\n );\n redactedMessage = redactMessageArray(\n record.message,\n placeholders,\n redactedIndices,\n wildcardIndices,\n redactedProperties,\n opts.action,\n );\n } else {\n // Tagged template: redact by comparing values\n const redactedValues = getRedactedValues(\n record.properties,\n redactedProperties,\n );\n if (redactedValues.size > 0) {\n redactedMessage = redactMessageByValues(record.message, redactedValues);\n }\n }\n\n sink({\n ...record,\n message: redactedMessage,\n properties: redactedProperties,\n });\n };\n if (Symbol.dispose in sink) wrapped[Symbol.dispose] = sink[Symbol.dispose];\n if (Symbol.asyncDispose in sink) {\n wrapped[Symbol.asyncDispose] = sink[Symbol.asyncDispose];\n }\n return wrapped;\n}\n\n/**\n * Redacts properties and message values in a {@link LogRecord} based on the\n * provided field patterns and asynchronous action.\n *\n * The returned sink preserves record ordering by processing redaction work in\n * sequence and implements {@link AsyncDisposable} so callers can wait for all\n * pending redaction work before shutdown.\n *\n * @example\n * ```ts\n * import { getConsoleSink } from \"@logtape/logtape\";\n * import {\n * createHmacPseudonymizer,\n * redactByFieldAsync,\n * } from \"@logtape/redaction\";\n *\n * const pseudonymize = await createHmacPseudonymizer({ key: \"secret\" });\n * const sink = redactByFieldAsync(getConsoleSink(), {\n * fieldPatterns: [/userId/i, /email/i],\n * action: pseudonymize,\n * });\n * ```\n *\n * @param sink The sink to wrap.\n * @param options The async redaction options.\n * @returns The wrapped sink.\n * @since 2.1.0\n */\nexport function redactByFieldAsync(\n sink: Sink | Sink & Disposable | Sink & AsyncDisposable,\n options: AsyncFieldRedactionOptions,\n): Sink & AsyncDisposable {\n let lastPromise = Promise.resolve();\n let closed = false;\n const sinkErrors: unknown[] = [];\n const wrapped: Sink & AsyncDisposable = (record: LogRecord) => {\n if (closed) return;\n\n const work = redactLogRecordAsync(record, options).catch((error) => {\n reportRedactionFailure(error);\n return null;\n });\n lastPromise = lastPromise.then(async () => {\n const result = await work;\n if (result == null) return;\n\n try {\n await sink({\n ...record,\n message: result.message,\n properties: result.properties,\n });\n } catch (error) {\n sinkErrors.push(error);\n }\n });\n };\n wrapped[Symbol.asyncDispose] = async () => {\n closed = true;\n await lastPromise;\n\n let disposeError: unknown;\n try {\n if (Symbol.asyncDispose in sink) {\n await sink[Symbol.asyncDispose]();\n } else if (Symbol.dispose in sink) {\n sink[Symbol.dispose]();\n }\n } catch (error) {\n disposeError = error;\n }\n\n if (sinkErrors.length > 0) {\n const errors = disposeError == null\n ? sinkErrors\n : [...sinkErrors, disposeError];\n if (errors.length === 1) throw errors[0];\n throw new AggregateError(\n errors,\n \"One or more errors occurred while emitting redacted log records.\",\n );\n }\n if (disposeError != null) {\n throw disposeError;\n }\n };\n return wrapped;\n}\n\nasync function redactLogRecordAsync(\n record: LogRecord,\n options: AsyncFieldRedactionOptions,\n): Promise<Pick<LogRecord, \"message\" | \"properties\">> {\n const redactedProperties = await redactPropertiesAsync(\n record.properties,\n options,\n );\n\n if (typeof record.rawMessage === \"string\") {\n const placeholders = extractPlaceholderNames(record.rawMessage);\n const { redactedIndices, wildcardIndices } = getRedactedPlaceholderIndices(\n placeholders,\n options.fieldPatterns,\n );\n return {\n message: await redactMessageArrayAsync(\n record.message,\n placeholders,\n redactedIndices,\n wildcardIndices,\n redactedProperties,\n options.action,\n ),\n properties: redactedProperties,\n };\n }\n\n let redactedMessage = record.message;\n const redactedValues = getRedactedValues(\n record.properties,\n redactedProperties,\n );\n if (redactedValues.size > 0) {\n redactedMessage = redactMessageByValues(\n record.message,\n redactedValues,\n );\n }\n return { message: redactedMessage, properties: redactedProperties };\n}\n\nfunction reportRedactionFailure(error: unknown): void {\n if (reportingRedactionFailure || typeof metaLogger.warn !== \"function\") {\n return;\n }\n try {\n reportingRedactionFailure = true;\n metaLogger.warn(\n \"Failed to redact a log record; dropping the record to avoid leaking \" +\n \"sensitive data: {error}\",\n { error },\n );\n } catch {\n // Meta logging failures must not make normal logging fail.\n } finally {\n reportingRedactionFailure = false;\n }\n}\n\n/**\n * Creates an asynchronous pseudonymizer based on HMAC using the Web Crypto API.\n *\n * The returned function converts each value with `String(value)`, encodes it\n * as UTF-8, and returns a stable pseudonym. Because HMAC is keyed, this is\n * safer than a plain salted hash for values with small input spaces, such as\n * email addresses or numeric user IDs.\n *\n * @param options The HMAC pseudonymizer options.\n * @returns An async redaction action.\n * @since 2.1.0\n */\nexport async function createHmacPseudonymizer(\n options: HmacPseudonymizerOptions,\n): Promise<HmacPseudonymizer> {\n const subtle = globalThis.crypto?.subtle;\n if (subtle == null) {\n throw new TypeError(\"The Web Crypto API is not available.\");\n }\n const hash = getHmacHash(options.key, options.hash);\n const encoding = options.encoding ?? \"base64url\";\n const prefix = options.prefix ??\n `hmac-${hash.toLowerCase().replaceAll(\"-\", \"\")}:`;\n const key = isCryptoKey(options.key) ? options.key : await subtle.importKey(\n \"raw\",\n keyToBytes(options.key),\n { name: \"HMAC\", hash },\n false,\n [\"sign\"],\n );\n const encoder = new TextEncoder();\n\n return async (value: unknown): Promise<string> => {\n const data = encoder.encode(String(value));\n const signature = new Uint8Array(\n await subtle.sign(\"HMAC\", key, data),\n );\n return prefix + encodeBytes(signature, encoding);\n };\n}\n\n/**\n * Redacts properties from an object based on specified field patterns.\n *\n * This function creates a shallow copy of the input object and applies\n * redaction rules to its properties. For properties that match the redaction\n * patterns, the function either removes them or transforms their values based\n * on the provided action.\n *\n * The redaction process is recursive and will be applied to nested objects\n * as well, allowing for deep redaction of sensitive data in complex object\n * structures.\n * @param properties The properties to redact.\n * @param options The redaction options.\n * @returns The redacted properties.\n * @since 0.10.0\n */\nexport function redactProperties(\n properties: Record<string, unknown>,\n options: FieldRedactionOptions,\n visited = new Map<object, object>(),\n): Record<string, unknown> {\n if (visited.has(properties)) {\n return visited.get(properties) as Record<string, unknown>;\n }\n\n const copy: Record<string, unknown> = {};\n visited.set(properties, copy);\n\n for (const field of Object.keys(properties)) {\n if (shouldFieldRedacted(field, options.fieldPatterns)) {\n if (typeof options.action === \"function\") {\n setProperty(copy, field, options.action(properties[field]));\n }\n continue;\n }\n\n const value = properties[field];\n if (Array.isArray(value)) {\n setProperty(copy, field, redactArray(value, options, visited));\n } else if (typeof value === \"object\" && value !== null) {\n if (isBuiltInObject(value)) {\n setProperty(copy, field, value);\n } else {\n setProperty(\n copy,\n field,\n redactProperties(\n value as Record<string, unknown>,\n options,\n visited,\n ),\n );\n }\n } else {\n setProperty(copy, field, value);\n }\n }\n return copy;\n}\n\n/**\n * Redacts properties from an object using an asynchronous action.\n * @param properties The properties to redact.\n * @param options The async redaction options.\n * @param visited Map of visited objects to prevent circular reference issues.\n * @returns The redacted properties.\n * @since 2.1.0\n */\nexport async function redactPropertiesAsync(\n properties: Record<string, unknown>,\n options: AsyncFieldRedactionOptions,\n visited = new Map<object, object>(),\n): Promise<Record<string, unknown>> {\n if (visited.has(properties)) {\n return visited.get(properties) as Record<string, unknown>;\n }\n\n const copy: Record<string, unknown> = {};\n visited.set(properties, copy);\n\n const fields: string[] = [];\n const values: Promise<unknown>[] = [];\n for (const field of Object.keys(properties)) {\n fields.push(field);\n if (shouldFieldRedacted(field, options.fieldPatterns)) {\n values.push(Promise.resolve(options.action(properties[field])));\n continue;\n }\n\n const value = properties[field];\n if (Array.isArray(value)) {\n values.push(redactArrayAsync(value, options, visited));\n } else if (typeof value === \"object\" && value !== null) {\n if (isBuiltInObject(value)) {\n values.push(Promise.resolve(value));\n } else {\n values.push(\n redactPropertiesAsync(\n value as Record<string, unknown>,\n options,\n visited,\n ),\n );\n }\n } else {\n values.push(Promise.resolve(value));\n }\n }\n const redactedValues = await Promise.all(values);\n for (let i = 0; i < fields.length; i++) {\n setProperty(copy, fields[i], redactedValues[i]);\n }\n return copy;\n}\n\nfunction setProperty(\n object: Record<string, unknown>,\n field: string,\n value: unknown,\n): void {\n if (field === \"__proto__\") {\n Object.defineProperty(object, field, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n } else {\n object[field] = value;\n }\n}\n\n/**\n * Redacts sensitive fields in an array recursively.\n * @param array The array to process.\n * @param options The redaction options.\n * @param visited Map of visited objects to prevent circular reference issues.\n * @returns A new array with redacted values.\n */\nfunction redactArray(\n array: unknown[],\n options: FieldRedactionOptions,\n visited: Map<object, object>,\n): unknown[] {\n if (visited.has(array)) return visited.get(array) as unknown[];\n\n const copy: unknown[] = [];\n copy.length = array.length;\n visited.set(array, copy);\n for (let i = 0; i < array.length; i++) {\n if (!(i in array)) continue;\n const item = array[i];\n if (Array.isArray(item)) {\n copy[i] = redactArray(item, options, visited);\n } else if (typeof item === \"object\" && item !== null) {\n if (isBuiltInObject(item)) {\n copy[i] = item;\n } else {\n copy[i] = redactProperties(\n item as Record<string, unknown>,\n options,\n visited,\n );\n }\n } else {\n copy[i] = item;\n }\n }\n return copy;\n}\n\nasync function redactArrayAsync(\n array: unknown[],\n options: AsyncFieldRedactionOptions,\n visited: Map<object, object>,\n): Promise<unknown[]> {\n if (visited.has(array)) return visited.get(array) as unknown[];\n\n const copy: unknown[] = [];\n copy.length = array.length;\n visited.set(array, copy);\n const itemPromises: Promise<unknown>[] = [];\n for (let i = 0; i < array.length; i++) {\n if (!(i in array)) {\n itemPromises.push(Promise.resolve(undefined));\n continue;\n }\n const item = array[i];\n if (Array.isArray(item)) {\n itemPromises.push(redactArrayAsync(item, options, visited));\n } else if (typeof item === \"object\" && item !== null) {\n if (isBuiltInObject(item)) {\n itemPromises.push(Promise.resolve(item));\n } else {\n itemPromises.push(\n redactPropertiesAsync(\n item as Record<string, unknown>,\n options,\n visited,\n ),\n );\n }\n } else {\n itemPromises.push(Promise.resolve(item));\n }\n }\n const redactedItems = await Promise.all(itemPromises);\n for (let i = 0; i < redactedItems.length; i++) {\n if (i in array) copy[i] = redactedItems[i];\n }\n return copy;\n}\n\n/**\n * Checks if a value is a built-in object that should not be recursively\n * processed (e.g., Error, Date, RegExp, Map, Set, etc.).\n * @param value The value to check.\n * @returns `true` if the value is a built-in object, `false` otherwise.\n */\nfunction isBuiltInObject(value: object): boolean {\n return value instanceof Error ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Promise ||\n value instanceof ArrayBuffer ||\n (typeof SharedArrayBuffer !== \"undefined\" &&\n value instanceof SharedArrayBuffer) ||\n ArrayBuffer.isView(value);\n}\n\n/**\n * Checks if a field should be redacted based on the provided field patterns.\n * @param field The field name to check.\n * @param fieldPatterns The field patterns to match against.\n * @returns `true` if the field should be redacted, `false` otherwise.\n * @since 0.10.0\n */\nexport function shouldFieldRedacted(\n field: string,\n fieldPatterns: FieldPatterns,\n): boolean {\n for (const fieldPattern of fieldPatterns) {\n if (typeof fieldPattern === \"string\") {\n if (fieldPattern === field) return true;\n } else {\n const matched = testFieldPattern(field, fieldPattern);\n if (matched) return true;\n }\n }\n return false;\n}\n\nfunction testFieldPattern(field: string, fieldPattern: RegExp): boolean {\n if (!fieldPattern.global && !fieldPattern.sticky) {\n return fieldPattern.test(field);\n }\n const descriptor = Object.getOwnPropertyDescriptor(fieldPattern, \"lastIndex\");\n if (descriptor?.writable === false) {\n return new RegExp(fieldPattern).test(field);\n }\n return RegExp.prototype[Symbol.search].call(fieldPattern, field) !== -1;\n}\n\n/**\n * Extracts placeholder names from a message template string in order.\n * @param template The message template string.\n * @returns An array of placeholder names in the order they appear.\n */\nfunction extractPlaceholderNames(template: string): string[] {\n const placeholders: string[] = [];\n for (let i = 0; i < template.length; i++) {\n if (template[i] === \"{\") {\n // Check for escaped brace\n if (i + 1 < template.length && template[i + 1] === \"{\") {\n i++;\n continue;\n }\n const closeIndex = template.indexOf(\"}\", i + 1);\n if (closeIndex === -1) continue;\n const key = template.slice(i + 1, closeIndex).trim();\n placeholders.push(key);\n i = closeIndex;\n }\n }\n return placeholders;\n}\n\n/**\n * Parses a property path into its segments.\n * @param path The property path (e.g., \"user.password\" or \"users[0].email\").\n * @returns An array of path segments.\n */\nfunction parsePathSegments(path: string): string[] {\n const segments: string[] = [];\n let current = \"\";\n let inBracket = false;\n let quotedBracketSegment = false;\n let quote: '\"' | \"'\" | undefined;\n let escaped = false;\n\n const pushCurrent = (trim = false): void => {\n const segment = trim ? current.trimEnd() : current;\n if (segment) segments.push(segment);\n current = \"\";\n };\n\n for (const char of path) {\n if (quote != null) {\n if (escaped) {\n current += char;\n escaped = false;\n } else if (char === \"\\\\\") {\n escaped = true;\n } else if (char === quote) {\n quote = undefined;\n } else {\n current += char;\n }\n continue;\n }\n\n if (inBracket && current === \"\" && /\\s/.test(char)) {\n continue;\n }\n if (inBracket && (char === '\"' || char === \"'\") && current === \"\") {\n quote = char;\n quotedBracketSegment = true;\n continue;\n }\n if (inBracket && quotedBracketSegment && /\\s/.test(char)) {\n continue;\n }\n if (char === \".\" && !inBracket) {\n pushCurrent();\n continue;\n }\n if (char === \"[\") {\n pushCurrent();\n inBracket = true;\n continue;\n }\n if (char === \"]\") {\n pushCurrent(!quotedBracketSegment);\n inBracket = false;\n quotedBracketSegment = false;\n continue;\n }\n if (char === \"?\") continue;\n current += char;\n }\n pushCurrent();\n return segments;\n}\n\n/**\n * Determines which placeholder indices should be redacted based on field\n * patterns, and which are wildcard placeholders.\n * @param placeholders Array of placeholder names from the template.\n * @param fieldPatterns Field patterns to match against.\n * @returns Object with redactedIndices and wildcardIndices.\n */\nfunction getRedactedPlaceholderIndices(\n placeholders: string[],\n fieldPatterns: FieldPatterns,\n): { redactedIndices: Set<number>; wildcardIndices: Set<number> } {\n const redactedIndices = new Set<number>();\n const wildcardIndices = new Set<number>();\n\n for (let i = 0; i < placeholders.length; i++) {\n const placeholder = placeholders[i];\n\n // Track wildcard {*} separately\n if (placeholder === \"*\") {\n wildcardIndices.add(i);\n continue;\n }\n\n // Check the full placeholder name\n if (shouldFieldRedacted(placeholder, fieldPatterns)) {\n redactedIndices.add(i);\n continue;\n }\n // For nested paths, check each segment\n const segments = parsePathSegments(placeholder);\n for (const segment of segments) {\n if (shouldFieldRedacted(segment, fieldPatterns)) {\n redactedIndices.add(i);\n break;\n }\n }\n }\n return { redactedIndices, wildcardIndices };\n}\n\n/**\n * Redacts values in the message array based on the redacted placeholder\n * indices and wildcard indices.\n * @param message The original message array.\n * @param placeholders Array of placeholder names from the template.\n * @param redactedIndices Set of placeholder indices to redact.\n * @param wildcardIndices Set of wildcard placeholder indices.\n * @param redactedProperties The redacted properties object.\n * @param action The redaction action.\n * @returns New message array with redacted values.\n */\nfunction redactMessageArray(\n message: readonly unknown[],\n placeholders: string[],\n redactedIndices: Set<number>,\n wildcardIndices: Set<number>,\n redactedProperties: Record<string, unknown>,\n action: \"delete\" | ((value: unknown) => unknown) | undefined,\n): readonly unknown[] {\n const result: unknown[] = [];\n let placeholderIndex = 0;\n\n for (let i = 0; i < message.length; i++) {\n if (i % 2 === 0) {\n // Even index: text segment\n result.push(message[i]);\n } else {\n // Odd index: value/placeholder\n if (wildcardIndices.has(placeholderIndex)) {\n // Wildcard {*}: replace with redacted properties\n result.push(redactedProperties);\n } else if (redactedIndices.has(placeholderIndex)) {\n if (action == null || action === \"delete\") {\n result.push(\"\");\n } else {\n result.push(action(message[i]));\n }\n } else {\n // For non-redacted placeholders, use value from redactedProperties\n // to ensure nested sensitive fields are redacted\n const placeholderName = placeholders[placeholderIndex];\n const redactedValue = getPathValue(redactedProperties, placeholderName);\n if (redactedValue.found) {\n result.push(redactedValue.value);\n } else {\n result.push(message[i]);\n }\n }\n placeholderIndex++;\n }\n }\n return result;\n}\n\nasync function redactMessageArrayAsync(\n message: readonly unknown[],\n placeholders: string[],\n redactedIndices: Set<number>,\n wildcardIndices: Set<number>,\n redactedProperties: Record<string, unknown>,\n action: AsyncFieldRedactionAction,\n): Promise<readonly unknown[]> {\n const result: unknown[] = [];\n const tasks: Promise<void>[] = [];\n let placeholderIndex = 0;\n\n for (let i = 0; i < message.length; i++) {\n if (i % 2 === 0) {\n result.push(message[i]);\n } else {\n if (wildcardIndices.has(placeholderIndex)) {\n result.push(redactedProperties);\n } else if (redactedIndices.has(placeholderIndex)) {\n const index = result.length;\n result.push(undefined);\n tasks.push(\n Promise.resolve(action(message[i])).then((redacted) => {\n result[index] = redacted;\n }),\n );\n } else {\n const placeholderName = placeholders[placeholderIndex];\n const redactedValue = getPathValue(redactedProperties, placeholderName);\n if (redactedValue.found) {\n result.push(redactedValue.value);\n } else {\n result.push(message[i]);\n }\n }\n placeholderIndex++;\n }\n }\n await Promise.all(tasks);\n return result;\n}\n\nfunction getPathValue(\n properties: Record<string, unknown>,\n path: string,\n): { found: true; value: unknown } | { found: false } {\n const segments = parsePathSegments(path);\n if (segments.length < 1) return { found: false };\n\n let value: unknown = properties;\n for (const segment of segments) {\n if (\n (typeof value !== \"object\" && typeof value !== \"function\") ||\n value == null ||\n !Object.hasOwn(value, segment)\n ) {\n return { found: false };\n }\n value = (value as Record<string, unknown>)[segment];\n }\n return { found: true, value };\n}\n\n/**\n * Collects redacted value mappings from original to redacted properties.\n * @param original The original properties.\n * @param redacted The redacted properties.\n * @param map The map to populate with original -> redacted value pairs.\n */\nfunction collectRedactedValues(\n original: Record<string, unknown>,\n redacted: Record<string, unknown>,\n map: Map<unknown, unknown>,\n): void {\n for (const key of Object.keys(original)) {\n const origVal = original[key];\n const redVal = redacted[key];\n\n if (origVal !== redVal) {\n map.set(origVal, redVal);\n }\n\n // Recurse into nested objects\n if (\n typeof origVal === \"object\" && origVal !== null &&\n typeof redVal === \"object\" && redVal !== null &&\n !Array.isArray(origVal)\n ) {\n collectRedactedValues(\n origVal as Record<string, unknown>,\n redVal as Record<string, unknown>,\n map,\n );\n }\n }\n}\n\n/**\n * Gets a map of original values to their redacted replacements.\n * @param original The original properties.\n * @param redacted The redacted properties.\n * @returns A map of original -> redacted values.\n */\nfunction getRedactedValues(\n original: Record<string, unknown>,\n redacted: Record<string, unknown>,\n): Map<unknown, unknown> {\n const map = new Map<unknown, unknown>();\n collectRedactedValues(original, redacted, map);\n return map;\n}\n\n/**\n * Redacts message array values by comparing with redacted property values.\n * Used for tagged template literals where placeholder names are not available.\n * @param message The original message array.\n * @param redactedValues Map of original -> redacted values.\n * @returns New message array with redacted values.\n */\nfunction redactMessageByValues(\n message: readonly unknown[],\n redactedValues: Map<unknown, unknown>,\n): readonly unknown[] {\n if (redactedValues.size === 0) return message;\n\n const result: unknown[] = [];\n for (let i = 0; i < message.length; i++) {\n if (i % 2 === 0) {\n result.push(message[i]);\n } else {\n const val = message[i];\n if (redactedValues.has(val)) {\n result.push(redactedValues.get(val));\n } else {\n result.push(val);\n }\n }\n }\n return result;\n}\n\nfunction keyToBytes(key: string | Uint8Array | ArrayBuffer): BufferSource {\n if (typeof key === \"string\") return new TextEncoder().encode(key);\n if (key instanceof ArrayBuffer) return key;\n return key as Uint8Array<ArrayBuffer>;\n}\n\nfunction isCryptoKey(\n key: string | Uint8Array | ArrayBuffer | CryptoKey,\n): key is CryptoKey {\n return typeof key === \"object\" && key !== null &&\n Object.prototype.toString.call(key) === \"[object CryptoKey]\";\n}\n\nfunction getHmacHash(\n key: string | Uint8Array | ArrayBuffer | CryptoKey,\n hash: HmacPseudonymizerOptions[\"hash\"],\n): NonNullable<HmacPseudonymizerOptions[\"hash\"]> {\n if (!isCryptoKey(key)) return hash ?? \"SHA-256\";\n if (!key.usages.includes(\"sign\")) {\n throw new TypeError('The HMAC CryptoKey must include the \"sign\" usage.');\n }\n const keyHash = getCryptoKeyHmacHash(key);\n if (hash != null && hash !== keyHash) {\n throw new TypeError(\n `The HMAC CryptoKey uses ${keyHash}, but the \"hash\" option is ${hash}.`,\n );\n }\n return keyHash;\n}\n\nfunction getCryptoKeyHmacHash(\n key: CryptoKey,\n): NonNullable<HmacPseudonymizerOptions[\"hash\"]> {\n const algorithm = key.algorithm;\n if (algorithm.name !== \"HMAC\" || !(\"hash\" in algorithm)) {\n throw new TypeError(\"The CryptoKey must be an HMAC key.\");\n }\n const hashAlgorithm = algorithm.hash;\n if (\n typeof hashAlgorithm !== \"object\" || hashAlgorithm == null ||\n !(\"name\" in hashAlgorithm) || typeof hashAlgorithm.name !== \"string\"\n ) {\n throw new TypeError(\"The CryptoKey must specify an HMAC hash algorithm.\");\n }\n const hash = hashAlgorithm.name;\n switch (hash) {\n case \"SHA-256\":\n case \"SHA-384\":\n case \"SHA-512\":\n return hash;\n default:\n throw new TypeError(`Unsupported HMAC hash algorithm: ${hash}.`);\n }\n}\n\nfunction encodeBytes(bytes: Uint8Array, encoding: \"base64url\" | \"hex\"): string {\n switch (encoding) {\n case \"base64url\":\n return encodeBase64Url(bytes);\n case \"hex\":\n return encodeHex(bytes);\n }\n}\n\nfunction encodeBase64Url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary)\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/u, \"\");\n}\n\nfunction encodeHex(bytes: Uint8Array): string {\n let result = \"\";\n for (const byte of bytes) {\n result += byte.toString(16).padStart(2, \"0\");\n }\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"field.js","names":["DEFAULT_REDACT_FIELDS: FieldPatterns","sink: Sink | Sink & Disposable | Sink & AsyncDisposable","options: FieldRedactionOptions | FieldPatterns","record: LogRecord","options: AsyncFieldRedactionOptions","sinkErrors: unknown[]","wrapped: Sink & AsyncDisposable","disposeError: unknown","error: unknown","limit: RedactionLimit","limits: RedactionTraversalLimits","options: RedactionTraversalOptions","context: RedactionTraversalContext","options: HmacPseudonymizerOptions","value: unknown","properties: Record<string, unknown>","options: FieldRedactionOptions","copy: Record<string, unknown>","fields: string[]","values: Promise<unknown>[]","object: Record<string, unknown>","field: string","array: unknown[]","visited: Map<object, object>","depth: number","copy: unknown[]","itemPromises: Promise<unknown>[]","value: object","fieldPatterns: FieldPatterns","fieldPattern: RegExp","template: string","placeholders: string[]","path: string","segments: string[]","quote: '\"' | \"'\" | undefined","message: readonly unknown[]","redactedIndices: Set<number>","wildcardIndices: Set<number>","redactedProperties: Record<string, unknown>","action: \"delete\" | ((value: unknown) => unknown) | undefined","result: unknown[]","action: AsyncFieldRedactionAction","tasks: Promise<void>[]","original: Record<string, unknown>","redacted: Record<string, unknown>","map: Map<unknown, unknown>","redactedValues: Map<unknown, unknown>","key: string | Uint8Array | ArrayBuffer","key: string | Uint8Array | ArrayBuffer | CryptoKey","hash: HmacPseudonymizerOptions[\"hash\"]","key: CryptoKey","bytes: Uint8Array","encoding: \"base64url\" | \"hex\""],"sources":["../src/field.ts"],"sourcesContent":["import { getLogger, type LogRecord, type Sink } from \"@logtape/logtape\";\nimport {\n createRedactionTraversalContext,\n type RedactionLimit,\n type RedactionTraversalContext,\n type RedactionTraversalLimits,\n type RedactionTraversalOptions,\n redactionTruncatedValue,\n} from \"./traversal.ts\";\n\n/**\n * The type for a field pattern used in redaction. A string or a regular\n * expression that matches field names.\n * @since 0.10.0\n */\nexport type FieldPattern = string | RegExp;\n\n/**\n * An array of field patterns used for redaction. Each pattern can be\n * a string or a regular expression that matches field names.\n * @since 0.10.0\n */\nexport type FieldPatterns = FieldPattern[];\n\n/**\n * The synchronous action to perform on a redacted field value.\n * @since 0.10.0\n */\nexport type FieldRedactionAction = \"delete\" | ((value: unknown) => unknown);\n\n/**\n * The asynchronous action to perform on a redacted field value.\n * @since 2.1.0\n */\nexport type AsyncFieldRedactionAction = (\n value: unknown,\n) => PromiseLike<unknown>;\n\n/**\n * A pseudonymizer created by {@link createHmacPseudonymizer}.\n * @since 2.1.0\n */\nexport type HmacPseudonymizer = (value: unknown) => Promise<string>;\n\nconst metaLogger = getLogger([\"logtape\", \"meta\"]);\nlet reportingRedactionFailure = false;\nlet reportingRedactionLimit = false;\n\n/**\n * Default field patterns for redaction. These patterns will match\n * common sensitive fields such as passwords, tokens, and personal\n * information.\n * @since 0.10.0\n */\nexport const DEFAULT_REDACT_FIELDS: FieldPatterns = [\n /pass(?:code|phrase|word)/i,\n /secret/i,\n /token/i,\n /key/i,\n /credential/i,\n /auth/i,\n /signature/i,\n /sensitive/i,\n /private/i,\n /ssn/i,\n /email/i,\n /phone/i,\n /address/i,\n];\n\n/**\n * Options for redacting fields in a {@link LogRecord}. Used by\n * the {@link redactByField} function.\n * @since 0.10.0\n */\nexport interface FieldRedactionOptions extends RedactionTraversalOptions {\n /**\n * The field patterns to match against. This can be an array of\n * strings or regular expressions. If a field matches any of the\n * patterns, it will be redacted.\n * @defaultValue {@link DEFAULT_REDACT_FIELDS}\n */\n readonly fieldPatterns: FieldPatterns;\n\n /**\n * The action to perform on the matched fields. If not provided,\n * the default action is to delete the field from the properties.\n * If a function is provided, it will be called with the\n * value of the field, and the return value will be used to replace\n * the field in the properties.\n * If the action is `\"delete\"`, the field will be removed from the\n * properties.\n * @default `\"delete\"`\n */\n readonly action?: FieldRedactionAction;\n\n /**\n * Maximum recursion depth for object and array traversal.\n * @default `20`\n * @since 2.2.0\n */\n readonly maxDepth?: number;\n\n /**\n * Maximum number of properties or array elements to process per object.\n * @default `1000`\n * @since 2.2.0\n */\n readonly maxProperties?: number;\n}\n\n/**\n * Options for asynchronously redacting fields in a {@link LogRecord}. Used by\n * the {@link redactByFieldAsync} function.\n * @since 2.1.0\n */\nexport interface AsyncFieldRedactionOptions extends RedactionTraversalOptions {\n /**\n * The field patterns to match against. This can be an array of\n * strings or regular expressions. If a field matches any of the\n * patterns, it will be redacted.\n */\n readonly fieldPatterns: FieldPatterns;\n\n /**\n * The asynchronous action to perform on the matched fields.\n */\n readonly action: AsyncFieldRedactionAction;\n\n /**\n * Maximum recursion depth for object and array traversal.\n * @default `20`\n * @since 2.2.0\n */\n readonly maxDepth?: number;\n\n /**\n * Maximum number of properties or array elements to process per object.\n * @default `1000`\n * @since 2.2.0\n */\n readonly maxProperties?: number;\n}\n\n/**\n * Options for the {@link createHmacPseudonymizer} function.\n * @since 2.1.0\n */\nexport interface HmacPseudonymizerOptions {\n /**\n * The secret key to use for HMAC. Strings are encoded as UTF-8.\n */\n readonly key: string | Uint8Array | ArrayBuffer | CryptoKey;\n\n /**\n * The HMAC hash algorithm.\n * @default `\"SHA-256\"`\n */\n readonly hash?: \"SHA-256\" | \"SHA-384\" | \"SHA-512\";\n\n /**\n * The digest encoding.\n * @default `\"base64url\"`\n */\n readonly encoding?: \"base64url\" | \"hex\";\n\n /**\n * The string prefix to prepend to each pseudonym. If omitted, a prefix based\n * on the hash algorithm is used, such as `\"hmac-sha256:\"`. Set this to an\n * empty string to disable the prefix.\n */\n readonly prefix?: string;\n}\n\n/**\n * Redacts properties and message values in a {@link LogRecord} based on the\n * provided field patterns and action.\n *\n * Note that it is a decorator which wraps the sink and redacts properties\n * and message values before passing them to the sink.\n *\n * For string templates (e.g., `\"Hello, {name}!\"`), placeholder names are\n * matched against the field patterns to determine which values to redact.\n *\n * For tagged template literals (e.g., `` `Hello, ${name}!` ``), redaction\n * is performed by comparing message values with redacted property values.\n *\n * @example\n * ```ts\n * import { getConsoleSink } from \"@logtape/logtape\";\n * import { redactByField } from \"@logtape/redaction\";\n *\n * const sink = redactByField(getConsoleSink());\n * ```\n *\n * @param sink The sink to wrap.\n * @param options The redaction options.\n * @returns The wrapped sink.\n * @since 0.10.0\n */\nexport function redactByField(\n sink: Sink | Sink & Disposable | Sink & AsyncDisposable,\n options: FieldRedactionOptions | FieldPatterns = DEFAULT_REDACT_FIELDS,\n): Sink | Sink & Disposable | Sink & AsyncDisposable {\n const opts = Array.isArray(options) ? { fieldPatterns: options } : options;\n const wrapped = (record: LogRecord) => {\n const context = createFieldRedactionContext(opts);\n const redactedProperties = redactProperties(\n record.properties,\n opts,\n context.visited,\n 0,\n context,\n );\n let redactedMessage = record.message;\n\n if (typeof record.rawMessage === \"string\") {\n // String template: redact by placeholder names\n const placeholders = extractPlaceholderNames(record.rawMessage);\n const { redactedIndices, wildcardIndices } =\n getRedactedPlaceholderIndices(\n placeholders,\n opts.fieldPatterns,\n );\n redactedMessage = redactMessageArray(\n record.message,\n placeholders,\n redactedIndices,\n wildcardIndices,\n redactedProperties,\n opts.action,\n context.exceededLimits.size > 0,\n );\n } else {\n // Tagged template: redact by comparing values\n const redactedValues = getRedactedValues(\n record.properties,\n redactedProperties,\n );\n if (redactedValues.size > 0 || context.exceededLimits.size > 0) {\n redactedMessage = redactMessageByValues(\n record.message,\n redactedValues,\n context.exceededLimits.size > 0,\n );\n }\n }\n\n sink({\n ...record,\n message: redactedMessage,\n properties: redactedProperties,\n });\n };\n if (Symbol.dispose in sink) wrapped[Symbol.dispose] = sink[Symbol.dispose];\n if (Symbol.asyncDispose in sink) {\n wrapped[Symbol.asyncDispose] = sink[Symbol.asyncDispose];\n }\n return wrapped;\n}\n\n/**\n * Redacts properties and message values in a {@link LogRecord} based on the\n * provided field patterns and asynchronous action.\n *\n * The returned sink preserves record ordering by processing redaction work in\n * sequence and implements {@link AsyncDisposable} so callers can wait for all\n * pending redaction work before shutdown.\n *\n * @example\n * ```ts\n * import { getConsoleSink } from \"@logtape/logtape\";\n * import {\n * createHmacPseudonymizer,\n * redactByFieldAsync,\n * } from \"@logtape/redaction\";\n *\n * const pseudonymize = await createHmacPseudonymizer({ key: \"secret\" });\n * const sink = redactByFieldAsync(getConsoleSink(), {\n * fieldPatterns: [/userId/i, /email/i],\n * action: pseudonymize,\n * });\n * ```\n *\n * @param sink The sink to wrap.\n * @param options The async redaction options.\n * @returns The wrapped sink.\n * @since 2.1.0\n */\nexport function redactByFieldAsync(\n sink: Sink | Sink & Disposable | Sink & AsyncDisposable,\n options: AsyncFieldRedactionOptions,\n): Sink & AsyncDisposable {\n let lastPromise = Promise.resolve();\n let closed = false;\n const sinkErrors: unknown[] = [];\n const wrapped: Sink & AsyncDisposable = (record: LogRecord) => {\n if (closed) return;\n\n const work = redactLogRecordAsync(record, options).catch((error) => {\n reportRedactionFailure(error);\n return null;\n });\n lastPromise = lastPromise.then(async () => {\n const result = await work;\n if (result == null) return;\n\n try {\n await sink({\n ...record,\n message: result.message,\n properties: result.properties,\n });\n } catch (error) {\n sinkErrors.push(error);\n }\n });\n };\n wrapped[Symbol.asyncDispose] = async () => {\n closed = true;\n await lastPromise;\n\n let disposeError: unknown;\n try {\n if (Symbol.asyncDispose in sink) {\n await sink[Symbol.asyncDispose]();\n } else if (Symbol.dispose in sink) {\n sink[Symbol.dispose]();\n }\n } catch (error) {\n disposeError = error;\n }\n\n if (sinkErrors.length > 0) {\n const errors = disposeError == null\n ? sinkErrors\n : [...sinkErrors, disposeError];\n if (errors.length === 1) throw errors[0];\n throw new AggregateError(\n errors,\n \"One or more errors occurred while emitting redacted log records.\",\n );\n }\n if (disposeError != null) {\n throw disposeError;\n }\n };\n return wrapped;\n}\n\nasync function redactLogRecordAsync(\n record: LogRecord,\n options: AsyncFieldRedactionOptions,\n): Promise<Pick<LogRecord, \"message\" | \"properties\">> {\n const context = createFieldRedactionContext(options);\n const redactedProperties = await redactPropertiesAsync(\n record.properties,\n options,\n context.visited,\n 0,\n context,\n );\n\n if (typeof record.rawMessage === \"string\") {\n const placeholders = extractPlaceholderNames(record.rawMessage);\n const { redactedIndices, wildcardIndices } = getRedactedPlaceholderIndices(\n placeholders,\n options.fieldPatterns,\n );\n return {\n message: await redactMessageArrayAsync(\n record.message,\n placeholders,\n redactedIndices,\n wildcardIndices,\n redactedProperties,\n options.action,\n context.exceededLimits.size > 0,\n ),\n properties: redactedProperties,\n };\n }\n\n let redactedMessage = record.message;\n const redactedValues = getRedactedValues(\n record.properties,\n redactedProperties,\n );\n if (redactedValues.size > 0 || context.exceededLimits.size > 0) {\n redactedMessage = redactMessageByValues(\n record.message,\n redactedValues,\n context.exceededLimits.size > 0,\n );\n }\n return { message: redactedMessage, properties: redactedProperties };\n}\n\nfunction reportRedactionFailure(error: unknown): void {\n if (reportingRedactionFailure || typeof metaLogger.warn !== \"function\") {\n return;\n }\n try {\n reportingRedactionFailure = true;\n metaLogger.warn(\n \"Failed to redact a log record; dropping the record to avoid leaking \" +\n \"sensitive data: {error}\",\n { error },\n );\n } catch {\n // Meta logging failures must not make normal logging fail.\n } finally {\n reportingRedactionFailure = false;\n }\n}\n\nfunction reportRedactionLimitExceeded(\n limit: RedactionLimit,\n limits: RedactionTraversalLimits,\n): void {\n if (reportingRedactionLimit || typeof metaLogger.warn !== \"function\") {\n return;\n }\n try {\n reportingRedactionLimit = true;\n metaLogger.warn(\n \"Redaction traversal exceeded {limit}; replacing or omitting \" +\n \"remaining data to keep logging bounded.\",\n { limit, ...limits },\n );\n } catch {\n // Meta logging failures must not make normal logging fail.\n } finally {\n reportingRedactionLimit = false;\n }\n}\n\nfunction createFieldRedactionContext(\n options: RedactionTraversalOptions,\n visited = new Map<object, object>(),\n): RedactionTraversalContext {\n return createRedactionTraversalContext(\n options,\n reportRedactionLimitExceeded,\n visited,\n );\n}\n\nfunction reportLimitOnce(\n context: RedactionTraversalContext,\n limit: RedactionLimit,\n): void {\n if (context.exceededLimits.has(limit)) return;\n context.reportLimitExceeded(limit);\n}\n\n/**\n * Creates an asynchronous pseudonymizer based on HMAC using the Web Crypto API.\n *\n * The returned function converts each value with `String(value)`, encodes it\n * as UTF-8, and returns a stable pseudonym. Because HMAC is keyed, this is\n * safer than a plain salted hash for values with small input spaces, such as\n * email addresses or numeric user IDs.\n *\n * @param options The HMAC pseudonymizer options.\n * @returns An async redaction action.\n * @since 2.1.0\n */\nexport async function createHmacPseudonymizer(\n options: HmacPseudonymizerOptions,\n): Promise<HmacPseudonymizer> {\n const subtle = globalThis.crypto?.subtle;\n if (subtle == null) {\n throw new TypeError(\"The Web Crypto API is not available.\");\n }\n const hash = getHmacHash(options.key, options.hash);\n const encoding = options.encoding ?? \"base64url\";\n const prefix = options.prefix ??\n `hmac-${hash.toLowerCase().replaceAll(\"-\", \"\")}:`;\n const key = isCryptoKey(options.key) ? options.key : await subtle.importKey(\n \"raw\",\n keyToBytes(options.key),\n { name: \"HMAC\", hash },\n false,\n [\"sign\"],\n );\n const encoder = new TextEncoder();\n\n return async (value: unknown): Promise<string> => {\n const data = encoder.encode(String(value));\n const signature = new Uint8Array(\n await subtle.sign(\"HMAC\", key, data),\n );\n return prefix + encodeBytes(signature, encoding);\n };\n}\n\n/**\n * Redacts properties from an object based on specified field patterns.\n *\n * This function creates a shallow copy of the input object and applies\n * redaction rules to its properties. For properties that match the redaction\n * patterns, the function either removes them or transforms their values based\n * on the provided action.\n *\n * The redaction process is recursive and will be applied to nested objects\n * as well, allowing for deep redaction of sensitive data in complex object\n * structures.\n * @param properties The properties to redact.\n * @param options The redaction options.\n * @returns The redacted properties.\n * @since 0.10.0\n */\nexport function redactProperties(\n properties: Record<string, unknown>,\n options: FieldRedactionOptions,\n visited = new Map<object, object>(),\n depth = 0,\n context = createFieldRedactionContext(options, visited),\n): Record<string, unknown> {\n if (visited.has(properties)) {\n return visited.get(properties) as Record<string, unknown>;\n }\n\n const copy: Record<string, unknown> = {};\n visited.set(properties, copy);\n\n const fields = Object.keys(properties);\n if (fields.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n\n for (const field of fields.slice(0, context.limits.maxProperties)) {\n if (shouldFieldRedacted(field, options.fieldPatterns)) {\n if (typeof options.action === \"function\") {\n setProperty(copy, field, options.action(properties[field]));\n }\n continue;\n }\n\n const value = properties[field];\n if (Array.isArray(value)) {\n if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n setProperty(copy, field, redactionTruncatedValue);\n } else {\n setProperty(\n copy,\n field,\n redactArray(value, options, visited, depth + 1, context),\n );\n }\n } else if (typeof value === \"object\" && value !== null) {\n if (isBuiltInObject(value)) {\n setProperty(copy, field, value);\n } else if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n setProperty(copy, field, redactionTruncatedValue);\n } else {\n setProperty(\n copy,\n field,\n redactProperties(\n value as Record<string, unknown>,\n options,\n visited,\n depth + 1,\n context,\n ),\n );\n }\n } else {\n setProperty(copy, field, value);\n }\n }\n return copy;\n}\n\n/**\n * Redacts properties from an object using an asynchronous action.\n * @param properties The properties to redact.\n * @param options The async redaction options.\n * @param visited Map of visited objects to prevent circular reference issues.\n * @returns The redacted properties.\n * @since 2.1.0\n */\nexport async function redactPropertiesAsync(\n properties: Record<string, unknown>,\n options: AsyncFieldRedactionOptions,\n visited = new Map<object, object>(),\n depth = 0,\n context = createFieldRedactionContext(options, visited),\n): Promise<Record<string, unknown>> {\n if (visited.has(properties)) {\n return visited.get(properties) as Record<string, unknown>;\n }\n\n const copy: Record<string, unknown> = {};\n visited.set(properties, copy);\n\n const fields: string[] = [];\n const values: Promise<unknown>[] = [];\n const propertyFields = Object.keys(properties);\n if (propertyFields.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n for (const field of propertyFields.slice(0, context.limits.maxProperties)) {\n fields.push(field);\n if (shouldFieldRedacted(field, options.fieldPatterns)) {\n values.push(Promise.resolve(options.action(properties[field])));\n continue;\n }\n\n const value = properties[field];\n if (Array.isArray(value)) {\n if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n values.push(Promise.resolve(redactionTruncatedValue));\n } else {\n values.push(\n redactArrayAsync(value, options, visited, depth + 1, context),\n );\n }\n } else if (typeof value === \"object\" && value !== null) {\n if (isBuiltInObject(value)) {\n values.push(Promise.resolve(value));\n } else if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n values.push(Promise.resolve(redactionTruncatedValue));\n } else {\n values.push(\n redactPropertiesAsync(\n value as Record<string, unknown>,\n options,\n visited,\n depth + 1,\n context,\n ),\n );\n }\n } else {\n values.push(Promise.resolve(value));\n }\n }\n const redactedValues = await Promise.all(values);\n for (let i = 0; i < fields.length; i++) {\n setProperty(copy, fields[i], redactedValues[i]);\n }\n return copy;\n}\n\nfunction setProperty(\n object: Record<string, unknown>,\n field: string,\n value: unknown,\n): void {\n if (field === \"__proto__\") {\n Object.defineProperty(object, field, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n } else {\n object[field] = value;\n }\n}\n\n/**\n * Redacts sensitive fields in an array recursively.\n * @param array The array to process.\n * @param options The redaction options.\n * @param visited Map of visited objects to prevent circular reference issues.\n * @returns A new array with redacted values.\n */\nfunction redactArray(\n array: unknown[],\n options: FieldRedactionOptions,\n visited: Map<object, object>,\n depth: number,\n context: RedactionTraversalContext,\n): unknown[] {\n if (visited.has(array)) return visited.get(array) as unknown[];\n\n const copy: unknown[] = [];\n const length = Math.min(array.length, context.limits.maxProperties);\n copy.length = length;\n visited.set(array, copy);\n if (array.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n for (let i = 0; i < length; i++) {\n if (!(i in array)) continue;\n const item = array[i];\n if (Array.isArray(item)) {\n if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n copy[i] = redactionTruncatedValue;\n } else {\n copy[i] = redactArray(item, options, visited, depth + 1, context);\n }\n } else if (typeof item === \"object\" && item !== null) {\n if (isBuiltInObject(item)) {\n copy[i] = item;\n } else if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n copy[i] = redactionTruncatedValue;\n } else {\n copy[i] = redactProperties(\n item as Record<string, unknown>,\n options,\n visited,\n depth + 1,\n context,\n );\n }\n } else {\n copy[i] = item;\n }\n }\n return copy;\n}\n\nasync function redactArrayAsync(\n array: unknown[],\n options: AsyncFieldRedactionOptions,\n visited: Map<object, object>,\n depth: number,\n context: RedactionTraversalContext,\n): Promise<unknown[]> {\n if (visited.has(array)) return visited.get(array) as unknown[];\n\n const copy: unknown[] = [];\n const length = Math.min(array.length, context.limits.maxProperties);\n copy.length = length;\n visited.set(array, copy);\n const itemPromises: Promise<unknown>[] = [];\n if (array.length > context.limits.maxProperties) {\n reportLimitOnce(context, \"maxProperties\");\n }\n for (let i = 0; i < length; i++) {\n if (!(i in array)) {\n itemPromises.push(Promise.resolve(undefined));\n continue;\n }\n const item = array[i];\n if (Array.isArray(item)) {\n if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n itemPromises.push(Promise.resolve(redactionTruncatedValue));\n } else {\n itemPromises.push(\n redactArrayAsync(item, options, visited, depth + 1, context),\n );\n }\n } else if (typeof item === \"object\" && item !== null) {\n if (isBuiltInObject(item)) {\n itemPromises.push(Promise.resolve(item));\n } else if (depth + 1 > context.limits.maxDepth) {\n reportLimitOnce(context, \"maxDepth\");\n itemPromises.push(Promise.resolve(redactionTruncatedValue));\n } else {\n itemPromises.push(\n redactPropertiesAsync(\n item as Record<string, unknown>,\n options,\n visited,\n depth + 1,\n context,\n ),\n );\n }\n } else {\n itemPromises.push(Promise.resolve(item));\n }\n }\n const redactedItems = await Promise.all(itemPromises);\n for (let i = 0; i < redactedItems.length; i++) {\n if (i in array) copy[i] = redactedItems[i];\n }\n return copy;\n}\n\n/**\n * Checks if a value is a built-in object that should not be recursively\n * processed (e.g., Error, Date, RegExp, Map, Set, etc.).\n * @param value The value to check.\n * @returns `true` if the value is a built-in object, `false` otherwise.\n */\nfunction isBuiltInObject(value: object): boolean {\n return value instanceof Error ||\n value instanceof Date ||\n value instanceof RegExp ||\n value instanceof Map ||\n value instanceof Set ||\n value instanceof WeakMap ||\n value instanceof WeakSet ||\n value instanceof Promise ||\n value instanceof ArrayBuffer ||\n (typeof SharedArrayBuffer !== \"undefined\" &&\n value instanceof SharedArrayBuffer) ||\n ArrayBuffer.isView(value);\n}\n\n/**\n * Checks if a field should be redacted based on the provided field patterns.\n * @param field The field name to check.\n * @param fieldPatterns The field patterns to match against.\n * @returns `true` if the field should be redacted, `false` otherwise.\n * @since 0.10.0\n */\nexport function shouldFieldRedacted(\n field: string,\n fieldPatterns: FieldPatterns,\n): boolean {\n for (const fieldPattern of fieldPatterns) {\n if (typeof fieldPattern === \"string\") {\n if (fieldPattern === field) return true;\n } else {\n const matched = testFieldPattern(field, fieldPattern);\n if (matched) return true;\n }\n }\n return false;\n}\n\nfunction testFieldPattern(field: string, fieldPattern: RegExp): boolean {\n if (!fieldPattern.global && !fieldPattern.sticky) {\n return fieldPattern.test(field);\n }\n const descriptor = Object.getOwnPropertyDescriptor(fieldPattern, \"lastIndex\");\n if (descriptor?.writable === false) {\n return new RegExp(fieldPattern).test(field);\n }\n return RegExp.prototype[Symbol.search].call(fieldPattern, field) !== -1;\n}\n\n/**\n * Extracts placeholder names from a message template string in order.\n * @param template The message template string.\n * @returns An array of placeholder names in the order they appear.\n */\nfunction extractPlaceholderNames(template: string): string[] {\n const placeholders: string[] = [];\n for (let i = 0; i < template.length; i++) {\n if (template[i] === \"{\") {\n // Check for escaped brace\n if (i + 1 < template.length && template[i + 1] === \"{\") {\n i++;\n continue;\n }\n const closeIndex = template.indexOf(\"}\", i + 1);\n if (closeIndex === -1) continue;\n const key = template.slice(i + 1, closeIndex).trim();\n placeholders.push(key);\n i = closeIndex;\n }\n }\n return placeholders;\n}\n\n/**\n * Parses a property path into its segments.\n * @param path The property path (e.g., \"user.password\" or \"users[0].email\").\n * @returns An array of path segments.\n */\nfunction parsePathSegments(path: string): string[] {\n const segments: string[] = [];\n let current = \"\";\n let inBracket = false;\n let quotedBracketSegment = false;\n let quote: '\"' | \"'\" | undefined;\n let escaped = false;\n\n const pushCurrent = (trim = false): void => {\n const segment = trim ? current.trimEnd() : current;\n if (segment) segments.push(segment);\n current = \"\";\n };\n\n for (const char of path) {\n if (quote != null) {\n if (escaped) {\n current += char;\n escaped = false;\n } else if (char === \"\\\\\") {\n escaped = true;\n } else if (char === quote) {\n quote = undefined;\n } else {\n current += char;\n }\n continue;\n }\n\n if (inBracket && current === \"\" && /\\s/.test(char)) {\n continue;\n }\n if (inBracket && (char === '\"' || char === \"'\") && current === \"\") {\n quote = char;\n quotedBracketSegment = true;\n continue;\n }\n if (inBracket && quotedBracketSegment && /\\s/.test(char)) {\n continue;\n }\n if (char === \".\" && !inBracket) {\n pushCurrent();\n continue;\n }\n if (char === \"[\") {\n pushCurrent();\n inBracket = true;\n continue;\n }\n if (char === \"]\") {\n pushCurrent(!quotedBracketSegment);\n inBracket = false;\n quotedBracketSegment = false;\n continue;\n }\n if (char === \"?\") continue;\n current += char;\n }\n pushCurrent();\n return segments;\n}\n\n/**\n * Determines which placeholder indices should be redacted based on field\n * patterns, and which are wildcard placeholders.\n * @param placeholders Array of placeholder names from the template.\n * @param fieldPatterns Field patterns to match against.\n * @returns Object with redactedIndices and wildcardIndices.\n */\nfunction getRedactedPlaceholderIndices(\n placeholders: string[],\n fieldPatterns: FieldPatterns,\n): { redactedIndices: Set<number>; wildcardIndices: Set<number> } {\n const redactedIndices = new Set<number>();\n const wildcardIndices = new Set<number>();\n\n for (let i = 0; i < placeholders.length; i++) {\n const placeholder = placeholders[i];\n\n // Track wildcard {*} separately\n if (placeholder === \"*\") {\n wildcardIndices.add(i);\n continue;\n }\n\n // Check the full placeholder name\n if (shouldFieldRedacted(placeholder, fieldPatterns)) {\n redactedIndices.add(i);\n continue;\n }\n // For nested paths, check each segment\n const segments = parsePathSegments(placeholder);\n for (const segment of segments) {\n if (shouldFieldRedacted(segment, fieldPatterns)) {\n redactedIndices.add(i);\n break;\n }\n }\n }\n return { redactedIndices, wildcardIndices };\n}\n\n/**\n * Redacts values in the message array based on the redacted placeholder\n * indices and wildcard indices.\n * @param message The original message array.\n * @param placeholders Array of placeholder names from the template.\n * @param redactedIndices Set of placeholder indices to redact.\n * @param wildcardIndices Set of wildcard placeholder indices.\n * @param redactedProperties The redacted properties object.\n * @param action The redaction action.\n * @returns New message array with redacted values.\n */\nfunction redactMessageArray(\n message: readonly unknown[],\n placeholders: string[],\n redactedIndices: Set<number>,\n wildcardIndices: Set<number>,\n redactedProperties: Record<string, unknown>,\n action: \"delete\" | ((value: unknown) => unknown) | undefined,\n truncateUnmappedValues = false,\n): readonly unknown[] {\n const result: unknown[] = [];\n let placeholderIndex = 0;\n\n for (let i = 0; i < message.length; i++) {\n if (i % 2 === 0) {\n // Even index: text segment\n result.push(message[i]);\n } else {\n // Odd index: value/placeholder\n if (wildcardIndices.has(placeholderIndex)) {\n // Wildcard {*}: replace with redacted properties\n result.push(redactedProperties);\n } else if (redactedIndices.has(placeholderIndex)) {\n if (action == null || action === \"delete\") {\n result.push(\"\");\n } else {\n result.push(action(message[i]));\n }\n } else {\n // For non-redacted placeholders, use value from redactedProperties\n // to ensure nested sensitive fields are redacted\n const placeholderName = placeholders[placeholderIndex];\n const redactedValue = getPathValue(redactedProperties, placeholderName);\n if (redactedValue.found) {\n result.push(redactedValue.value);\n } else if (truncateUnmappedValues) {\n result.push(redactionTruncatedValue);\n } else {\n result.push(message[i]);\n }\n }\n placeholderIndex++;\n }\n }\n return result;\n}\n\nasync function redactMessageArrayAsync(\n message: readonly unknown[],\n placeholders: string[],\n redactedIndices: Set<number>,\n wildcardIndices: Set<number>,\n redactedProperties: Record<string, unknown>,\n action: AsyncFieldRedactionAction,\n truncateUnmappedValues = false,\n): Promise<readonly unknown[]> {\n const result: unknown[] = [];\n const tasks: Promise<void>[] = [];\n let placeholderIndex = 0;\n\n for (let i = 0; i < message.length; i++) {\n if (i % 2 === 0) {\n result.push(message[i]);\n } else {\n if (wildcardIndices.has(placeholderIndex)) {\n result.push(redactedProperties);\n } else if (redactedIndices.has(placeholderIndex)) {\n const index = result.length;\n result.push(undefined);\n tasks.push(\n Promise.resolve(action(message[i])).then((redacted) => {\n result[index] = redacted;\n }),\n );\n } else {\n const placeholderName = placeholders[placeholderIndex];\n const redactedValue = getPathValue(redactedProperties, placeholderName);\n if (redactedValue.found) {\n result.push(redactedValue.value);\n } else if (truncateUnmappedValues) {\n result.push(redactionTruncatedValue);\n } else {\n result.push(message[i]);\n }\n }\n placeholderIndex++;\n }\n }\n await Promise.all(tasks);\n return result;\n}\n\nfunction getPathValue(\n properties: Record<string, unknown>,\n path: string,\n): { found: true; value: unknown } | { found: false } {\n const segments = parsePathSegments(path);\n if (segments.length < 1) return { found: false };\n\n let value: unknown = properties;\n for (const segment of segments) {\n if (\n (typeof value !== \"object\" && typeof value !== \"function\") ||\n value == null ||\n !Object.hasOwn(value, segment)\n ) {\n return { found: false };\n }\n value = (value as Record<string, unknown>)[segment];\n }\n return { found: true, value };\n}\n\n/**\n * Collects redacted value mappings from original to redacted properties.\n * @param original The original properties.\n * @param redacted The redacted properties.\n * @param map The map to populate with original -> redacted value pairs.\n */\nfunction collectRedactedValues(\n original: Record<string, unknown>,\n redacted: Record<string, unknown>,\n map: Map<unknown, unknown>,\n): void {\n for (const key of Object.keys(redacted)) {\n const origVal = original[key];\n const redVal = redacted[key];\n\n if (origVal !== redVal) {\n map.set(origVal, redVal);\n }\n\n // Recurse into nested objects\n if (\n typeof origVal === \"object\" && origVal !== null &&\n typeof redVal === \"object\" && redVal !== null &&\n !Array.isArray(origVal)\n ) {\n collectRedactedValues(\n origVal as Record<string, unknown>,\n redVal as Record<string, unknown>,\n map,\n );\n }\n }\n}\n\n/**\n * Gets a map of original values to their redacted replacements.\n * @param original The original properties.\n * @param redacted The redacted properties.\n * @returns A map of original -> redacted values.\n */\nfunction getRedactedValues(\n original: Record<string, unknown>,\n redacted: Record<string, unknown>,\n): Map<unknown, unknown> {\n const map = new Map<unknown, unknown>();\n collectRedactedValues(original, redacted, map);\n return map;\n}\n\n/**\n * Redacts message array values by comparing with redacted property values.\n * Used for tagged template literals where placeholder names are not available.\n * @param message The original message array.\n * @param redactedValues Map of original -> redacted values.\n * @returns New message array with redacted values.\n */\nfunction redactMessageByValues(\n message: readonly unknown[],\n redactedValues: Map<unknown, unknown>,\n truncateUnmappedValues = false,\n): readonly unknown[] {\n if (redactedValues.size === 0 && !truncateUnmappedValues) return message;\n\n const result: unknown[] = [];\n for (let i = 0; i < message.length; i++) {\n if (i % 2 === 0) {\n result.push(message[i]);\n } else {\n const val = message[i];\n if (redactedValues.has(val)) {\n result.push(redactedValues.get(val));\n } else if (truncateUnmappedValues) {\n result.push(redactionTruncatedValue);\n } else {\n result.push(val);\n }\n }\n }\n return result;\n}\n\nfunction keyToBytes(key: string | Uint8Array | ArrayBuffer): BufferSource {\n if (typeof key === \"string\") return new TextEncoder().encode(key);\n if (key instanceof ArrayBuffer) return key;\n return key as Uint8Array<ArrayBuffer>;\n}\n\nfunction isCryptoKey(\n key: string | Uint8Array | ArrayBuffer | CryptoKey,\n): key is CryptoKey {\n return typeof key === \"object\" && key !== null &&\n Object.prototype.toString.call(key) === \"[object CryptoKey]\";\n}\n\nfunction getHmacHash(\n key: string | Uint8Array | ArrayBuffer | CryptoKey,\n hash: HmacPseudonymizerOptions[\"hash\"],\n): NonNullable<HmacPseudonymizerOptions[\"hash\"]> {\n if (!isCryptoKey(key)) return hash ?? \"SHA-256\";\n if (!key.usages.includes(\"sign\")) {\n throw new TypeError('The HMAC CryptoKey must include the \"sign\" usage.');\n }\n const keyHash = getCryptoKeyHmacHash(key);\n if (hash != null && hash !== keyHash) {\n throw new TypeError(\n `The HMAC CryptoKey uses ${keyHash}, but the \"hash\" option is ${hash}.`,\n );\n }\n return keyHash;\n}\n\nfunction getCryptoKeyHmacHash(\n key: CryptoKey,\n): NonNullable<HmacPseudonymizerOptions[\"hash\"]> {\n const algorithm = key.algorithm;\n if (algorithm.name !== \"HMAC\" || !(\"hash\" in algorithm)) {\n throw new TypeError(\"The CryptoKey must be an HMAC key.\");\n }\n const hashAlgorithm = algorithm.hash;\n if (\n typeof hashAlgorithm !== \"object\" || hashAlgorithm == null ||\n !(\"name\" in hashAlgorithm) || typeof hashAlgorithm.name !== \"string\"\n ) {\n throw new TypeError(\"The CryptoKey must specify an HMAC hash algorithm.\");\n }\n const hash = hashAlgorithm.name;\n switch (hash) {\n case \"SHA-256\":\n case \"SHA-384\":\n case \"SHA-512\":\n return hash;\n default:\n throw new TypeError(`Unsupported HMAC hash algorithm: ${hash}.`);\n }\n}\n\nfunction encodeBytes(bytes: Uint8Array, encoding: \"base64url\" | \"hex\"): string {\n switch (encoding) {\n case \"base64url\":\n return encodeBase64Url(bytes);\n case \"hex\":\n return encodeHex(bytes);\n }\n}\n\nfunction encodeBase64Url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary)\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/u, \"\");\n}\n\nfunction encodeHex(bytes: Uint8Array): string {\n let result = \"\";\n for (const byte of bytes) {\n result += byte.toString(16).padStart(2, \"0\");\n }\n return result;\n}\n"],"mappings":"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"}
|
package/dist/mod.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RedactionTraversalOptions } from "./traversal.cjs";
|
|
1
2
|
import { AsyncFieldRedactionAction, AsyncFieldRedactionOptions, DEFAULT_REDACT_FIELDS, FieldPattern, FieldPatterns, FieldRedactionAction, FieldRedactionOptions, HmacPseudonymizer, HmacPseudonymizerOptions, createHmacPseudonymizer, redactByField, redactByFieldAsync } from "./field.cjs";
|
|
2
|
-
import { CREDIT_CARD_NUMBER_PATTERN, EMAIL_ADDRESS_PATTERN, JWT_PATTERN, KR_RRN_PATTERN, RedactionPattern, RedactionPatterns, US_SSN_PATTERN, redactByPattern } from "./pattern.cjs";
|
|
3
|
-
export { AsyncFieldRedactionAction, AsyncFieldRedactionOptions, CREDIT_CARD_NUMBER_PATTERN, DEFAULT_REDACT_FIELDS, EMAIL_ADDRESS_PATTERN, FieldPattern, FieldPatterns, FieldRedactionAction, FieldRedactionOptions, HmacPseudonymizer, HmacPseudonymizerOptions, JWT_PATTERN, KR_RRN_PATTERN, RedactionPattern, RedactionPatterns, US_SSN_PATTERN, createHmacPseudonymizer, redactByField, redactByFieldAsync, redactByPattern };
|
|
3
|
+
import { CREDIT_CARD_NUMBER_PATTERN, EMAIL_ADDRESS_PATTERN, JWT_PATTERN, KR_RRN_PATTERN, PatternRedactionOptions, RedactionPattern, RedactionPatterns, US_SSN_PATTERN, redactByPattern } from "./pattern.cjs";
|
|
4
|
+
export { AsyncFieldRedactionAction, AsyncFieldRedactionOptions, CREDIT_CARD_NUMBER_PATTERN, DEFAULT_REDACT_FIELDS, EMAIL_ADDRESS_PATTERN, FieldPattern, FieldPatterns, FieldRedactionAction, FieldRedactionOptions, HmacPseudonymizer, HmacPseudonymizerOptions, JWT_PATTERN, KR_RRN_PATTERN, PatternRedactionOptions, RedactionPattern, RedactionPatterns, RedactionTraversalOptions, US_SSN_PATTERN, createHmacPseudonymizer, redactByField, redactByFieldAsync, redactByPattern };
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RedactionTraversalOptions } from "./traversal.js";
|
|
1
2
|
import { AsyncFieldRedactionAction, AsyncFieldRedactionOptions, DEFAULT_REDACT_FIELDS, FieldPattern, FieldPatterns, FieldRedactionAction, FieldRedactionOptions, HmacPseudonymizer, HmacPseudonymizerOptions, createHmacPseudonymizer, redactByField, redactByFieldAsync } from "./field.js";
|
|
2
|
-
import { CREDIT_CARD_NUMBER_PATTERN, EMAIL_ADDRESS_PATTERN, JWT_PATTERN, KR_RRN_PATTERN, RedactionPattern, RedactionPatterns, US_SSN_PATTERN, redactByPattern } from "./pattern.js";
|
|
3
|
-
export { AsyncFieldRedactionAction, AsyncFieldRedactionOptions, CREDIT_CARD_NUMBER_PATTERN, DEFAULT_REDACT_FIELDS, EMAIL_ADDRESS_PATTERN, FieldPattern, FieldPatterns, FieldRedactionAction, FieldRedactionOptions, HmacPseudonymizer, HmacPseudonymizerOptions, JWT_PATTERN, KR_RRN_PATTERN, RedactionPattern, RedactionPatterns, US_SSN_PATTERN, createHmacPseudonymizer, redactByField, redactByFieldAsync, redactByPattern };
|
|
3
|
+
import { CREDIT_CARD_NUMBER_PATTERN, EMAIL_ADDRESS_PATTERN, JWT_PATTERN, KR_RRN_PATTERN, PatternRedactionOptions, RedactionPattern, RedactionPatterns, US_SSN_PATTERN, redactByPattern } from "./pattern.js";
|
|
4
|
+
export { AsyncFieldRedactionAction, AsyncFieldRedactionOptions, CREDIT_CARD_NUMBER_PATTERN, DEFAULT_REDACT_FIELDS, EMAIL_ADDRESS_PATTERN, FieldPattern, FieldPatterns, FieldRedactionAction, FieldRedactionOptions, HmacPseudonymizer, HmacPseudonymizerOptions, JWT_PATTERN, KR_RRN_PATTERN, PatternRedactionOptions, RedactionPattern, RedactionPatterns, RedactionTraversalOptions, US_SSN_PATTERN, createHmacPseudonymizer, redactByField, redactByFieldAsync, redactByPattern };
|
package/dist/pattern.cjs
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_traversal = require('./traversal.cjs');
|
|
3
|
+
const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
|
|
1
4
|
|
|
2
5
|
//#region src/pattern.ts
|
|
6
|
+
const metaLogger = (0, __logtape_logtape.getLogger)(["logtape", "meta"]);
|
|
7
|
+
let reportingRedactionLimit = false;
|
|
3
8
|
/**
|
|
4
9
|
* A redaction pattern for email addresses.
|
|
5
10
|
* @since 0.10.0
|
|
@@ -50,28 +55,46 @@ const JWT_PATTERN = {
|
|
|
50
55
|
function isBuiltInObject(value) {
|
|
51
56
|
return value instanceof Error || value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet || value instanceof Promise || value instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer || ArrayBuffer.isView(value);
|
|
52
57
|
}
|
|
53
|
-
function redactByPattern(formatter, patterns) {
|
|
58
|
+
function redactByPattern(formatter, patterns, options = {}) {
|
|
54
59
|
for (const { pattern } of patterns) if (!pattern.global) throw new TypeError(`Pattern ${pattern} does not have the global flag set.`);
|
|
55
60
|
function replaceString(str) {
|
|
56
61
|
for (const p of patterns) str = typeof p.replacement === "string" ? str.replaceAll(p.pattern, p.replacement) : str.replaceAll(p.pattern, p.replacement);
|
|
57
62
|
return str;
|
|
58
63
|
}
|
|
59
|
-
function replaceObject(object,
|
|
64
|
+
function replaceObject(object, context, depth) {
|
|
60
65
|
if (typeof object === "object" && object !== null) {
|
|
61
|
-
if (visited.has(object)) return visited.get(object);
|
|
66
|
+
if (context.visited.has(object)) return context.visited.get(object);
|
|
62
67
|
}
|
|
63
68
|
if (typeof object === "string") return replaceString(object);
|
|
64
69
|
if (Array.isArray(object)) {
|
|
65
70
|
const copy = [];
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
const length = Math.min(object.length, context.limits.maxProperties);
|
|
72
|
+
copy.length = length;
|
|
73
|
+
context.visited.set(object, copy);
|
|
74
|
+
if (object.length > context.limits.maxProperties) reportLimitOnce(context, "maxProperties");
|
|
75
|
+
for (let i = 0; i < length; i++) {
|
|
76
|
+
if (!(i in object)) continue;
|
|
77
|
+
const item = object[i];
|
|
78
|
+
if (shouldTraverseValue(item) && depth + 1 > context.limits.maxDepth) {
|
|
79
|
+
reportLimitOnce(context, "maxDepth");
|
|
80
|
+
copy[i] = require_traversal.redactionTruncatedValue;
|
|
81
|
+
} else copy[i] = replaceObject(item, context, depth + 1);
|
|
82
|
+
}
|
|
68
83
|
return copy;
|
|
69
84
|
}
|
|
70
85
|
if (typeof object === "object" && object !== null) {
|
|
71
86
|
if (isBuiltInObject(object)) return object;
|
|
72
87
|
const redacted = {};
|
|
73
|
-
visited.set(object, redacted);
|
|
74
|
-
|
|
88
|
+
context.visited.set(object, redacted);
|
|
89
|
+
const keys = Object.keys(object);
|
|
90
|
+
if (keys.length > context.limits.maxProperties) reportLimitOnce(context, "maxProperties");
|
|
91
|
+
for (const key of keys.slice(0, context.limits.maxProperties)) {
|
|
92
|
+
const value = object[key];
|
|
93
|
+
if (shouldTraverseValue(value) && depth + 1 > context.limits.maxDepth) {
|
|
94
|
+
reportLimitOnce(context, "maxDepth");
|
|
95
|
+
redacted[key] = require_traversal.redactionTruncatedValue;
|
|
96
|
+
} else redacted[key] = replaceObject(value, context, depth + 1);
|
|
97
|
+
}
|
|
75
98
|
return redacted;
|
|
76
99
|
}
|
|
77
100
|
return object;
|
|
@@ -79,9 +102,33 @@ function redactByPattern(formatter, patterns) {
|
|
|
79
102
|
return (record) => {
|
|
80
103
|
const output = formatter(record);
|
|
81
104
|
if (typeof output === "string") return replaceString(output);
|
|
82
|
-
|
|
105
|
+
const context = createPatternRedactionContext(options);
|
|
106
|
+
if (output.length > context.limits.maxProperties) reportLimitOnce(context, "maxProperties");
|
|
107
|
+
return output.slice(0, context.limits.maxProperties).map((obj) => replaceObject(obj, context, 0));
|
|
83
108
|
};
|
|
84
109
|
}
|
|
110
|
+
function reportRedactionLimitExceeded(limit, limits) {
|
|
111
|
+
if (reportingRedactionLimit || typeof metaLogger.warn !== "function") return;
|
|
112
|
+
try {
|
|
113
|
+
reportingRedactionLimit = true;
|
|
114
|
+
metaLogger.warn("Redaction traversal exceeded {limit}; replacing or omitting remaining data to keep logging bounded.", {
|
|
115
|
+
limit,
|
|
116
|
+
...limits
|
|
117
|
+
});
|
|
118
|
+
} catch {} finally {
|
|
119
|
+
reportingRedactionLimit = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function shouldTraverseValue(value) {
|
|
123
|
+
return typeof value === "object" && value !== null && !isBuiltInObject(value);
|
|
124
|
+
}
|
|
125
|
+
function createPatternRedactionContext(options) {
|
|
126
|
+
return require_traversal.createRedactionTraversalContext(options, reportRedactionLimitExceeded);
|
|
127
|
+
}
|
|
128
|
+
function reportLimitOnce(context, limit) {
|
|
129
|
+
if (context.exceededLimits.has(limit)) return;
|
|
130
|
+
context.reportLimitExceeded(limit);
|
|
131
|
+
}
|
|
85
132
|
|
|
86
133
|
//#endregion
|
|
87
134
|
exports.CREDIT_CARD_NUMBER_PATTERN = CREDIT_CARD_NUMBER_PATTERN;
|