@socprime/logtotal-sanitizer 0.0.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,944 @@
1
+ 'use strict';
2
+
3
+ var chunkVW3VVW6L_cjs = require('./chunk-VW3VVW6L.cjs');
4
+
5
+ // src/core/constants.ts
6
+ var DEFAULT_PREVIEW_BYTES = 256 * 1024;
7
+ var DEFAULT_MAX_LINE_CHARS = 1024 * 1024;
8
+ var DEFAULT_LINE_OVERLAP_CHARS = 1024;
9
+ var DEFAULT_KEY_BYTES = 32;
10
+ var MASK_TOKEN_PREFIX = "R";
11
+ var ALWAYS_REDACT_RULE_ID = "custom";
12
+ var ALWAYS_REDACT_TOKEN = "CUSTOM";
13
+
14
+ // src/core/hmac.ts
15
+ var BLOCK_BYTES = 64;
16
+ var DIGEST_BYTES = 32;
17
+ var K = new Uint32Array([
18
+ 1116352408,
19
+ 1899447441,
20
+ 3049323471,
21
+ 3921009573,
22
+ 961987163,
23
+ 1508970993,
24
+ 2453635748,
25
+ 2870763221,
26
+ 3624381080,
27
+ 310598401,
28
+ 607225278,
29
+ 1426881987,
30
+ 1925078388,
31
+ 2162078206,
32
+ 2614888103,
33
+ 3248222580,
34
+ 3835390401,
35
+ 4022224774,
36
+ 264347078,
37
+ 604807628,
38
+ 770255983,
39
+ 1249150122,
40
+ 1555081692,
41
+ 1996064986,
42
+ 2554220882,
43
+ 2821834349,
44
+ 2952996808,
45
+ 3210313671,
46
+ 3336571891,
47
+ 3584528711,
48
+ 113926993,
49
+ 338241895,
50
+ 666307205,
51
+ 773529912,
52
+ 1294757372,
53
+ 1396182291,
54
+ 1695183700,
55
+ 1986661051,
56
+ 2177026350,
57
+ 2456956037,
58
+ 2730485921,
59
+ 2820302411,
60
+ 3259730800,
61
+ 3345764771,
62
+ 3516065817,
63
+ 3600352804,
64
+ 4094571909,
65
+ 275423344,
66
+ 430227734,
67
+ 506948616,
68
+ 659060556,
69
+ 883997877,
70
+ 958139571,
71
+ 1322822218,
72
+ 1537002063,
73
+ 1747873779,
74
+ 1955562222,
75
+ 2024104815,
76
+ 2227730452,
77
+ 2361852424,
78
+ 2428436474,
79
+ 2756734187,
80
+ 3204031479,
81
+ 3329325298
82
+ ]);
83
+ var INITIAL_STATE = new Uint32Array([
84
+ 1779033703,
85
+ 3144134277,
86
+ 1013904242,
87
+ 2773480762,
88
+ 1359893119,
89
+ 2600822924,
90
+ 528734635,
91
+ 1541459225
92
+ ]);
93
+ function rotr(value, bits) {
94
+ return (value >>> bits | value << 32 - bits) >>> 0;
95
+ }
96
+ function concatBytes(a, b) {
97
+ const out = new Uint8Array(a.length + b.length);
98
+ out.set(a, 0);
99
+ out.set(b, a.length);
100
+ return out;
101
+ }
102
+ function padMessage(message) {
103
+ const bitLength = message.length * 8;
104
+ const paddedLength = Math.ceil((message.length + 9) / BLOCK_BYTES) * BLOCK_BYTES;
105
+ const padded = new Uint8Array(paddedLength);
106
+ padded.set(message);
107
+ padded[message.length] = 128;
108
+ const view = new DataView(padded.buffer);
109
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 2 ** 32), false);
110
+ view.setUint32(paddedLength - 4, bitLength >>> 0, false);
111
+ return padded;
112
+ }
113
+ function sha256(message) {
114
+ const padded = padMessage(message);
115
+ const view = new DataView(padded.buffer);
116
+ const state = Uint32Array.from(INITIAL_STATE);
117
+ const w = new Uint32Array(64);
118
+ for (let offset = 0; offset < padded.length; offset += BLOCK_BYTES) {
119
+ for (let i = 0; i < 16; i += 1) {
120
+ w[i] = view.getUint32(offset + i * 4, false);
121
+ }
122
+ for (let i = 16; i < 64; i += 1) {
123
+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
124
+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
125
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
126
+ }
127
+ let a = state[0];
128
+ let b = state[1];
129
+ let c = state[2];
130
+ let d = state[3];
131
+ let e = state[4];
132
+ let f = state[5];
133
+ let g = state[6];
134
+ let h = state[7];
135
+ for (let i = 0; i < 64; i += 1) {
136
+ const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
137
+ const ch = e & f ^ ~e & g;
138
+ const temp1 = h + s1 + ch + K[i] + w[i] >>> 0;
139
+ const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
140
+ const maj = a & b ^ a & c ^ b & c;
141
+ const temp2 = s0 + maj >>> 0;
142
+ h = g;
143
+ g = f;
144
+ f = e;
145
+ e = d + temp1 >>> 0;
146
+ d = c;
147
+ c = b;
148
+ b = a;
149
+ a = temp1 + temp2 >>> 0;
150
+ }
151
+ state[0] = state[0] + a >>> 0;
152
+ state[1] = state[1] + b >>> 0;
153
+ state[2] = state[2] + c >>> 0;
154
+ state[3] = state[3] + d >>> 0;
155
+ state[4] = state[4] + e >>> 0;
156
+ state[5] = state[5] + f >>> 0;
157
+ state[6] = state[6] + g >>> 0;
158
+ state[7] = state[7] + h >>> 0;
159
+ }
160
+ const digest = new Uint8Array(DIGEST_BYTES);
161
+ const digestView = new DataView(digest.buffer);
162
+ for (let i = 0; i < 8; i += 1) {
163
+ digestView.setUint32(i * 4, state[i], false);
164
+ }
165
+ return digest;
166
+ }
167
+ function normalizeKey(key) {
168
+ const hashed = key.length > BLOCK_BYTES ? sha256(key) : key;
169
+ if (hashed.length === BLOCK_BYTES) {
170
+ return hashed;
171
+ }
172
+ const padded = new Uint8Array(BLOCK_BYTES);
173
+ padded.set(hashed);
174
+ return padded;
175
+ }
176
+ function createHmacSha256(key) {
177
+ const block = normalizeKey(key);
178
+ const ipad = new Uint8Array(BLOCK_BYTES);
179
+ const opad = new Uint8Array(BLOCK_BYTES);
180
+ for (let i = 0; i < BLOCK_BYTES; i += 1) {
181
+ ipad[i] = block[i] ^ 54;
182
+ opad[i] = block[i] ^ 92;
183
+ }
184
+ return (message) => sha256(concatBytes(opad, sha256(concatBytes(ipad, message))));
185
+ }
186
+ function hexToBytes(hex) {
187
+ const bytes = new Uint8Array(hex.length / 2);
188
+ for (let i = 0; i < bytes.length; i += 1) {
189
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
190
+ }
191
+ return bytes;
192
+ }
193
+ function bytesToHex(bytes) {
194
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
195
+ }
196
+
197
+ // src/core/key.ts
198
+ var HEX_PATTERN = /^(?:[0-9a-fA-F]{2})+$/;
199
+ function generateKey(byteLength = DEFAULT_KEY_BYTES) {
200
+ if (!Number.isInteger(byteLength) || byteLength < 16) {
201
+ throw new chunkVW3VVW6L_cjs.InvalidKeyError(`Key length must be an integer of at least 16 bytes, got ${byteLength}.`);
202
+ }
203
+ const source = globalThis.crypto;
204
+ if (typeof source?.getRandomValues !== "function") {
205
+ throw new chunkVW3VVW6L_cjs.InvalidKeyError(
206
+ "No secure random source available. Pass an explicit `key` or provide a Web Crypto implementation on globalThis.crypto."
207
+ );
208
+ }
209
+ const bytes = new Uint8Array(byteLength);
210
+ source.getRandomValues(bytes);
211
+ return bytesToHex(bytes);
212
+ }
213
+ function assertKey(key, encoding) {
214
+ if (key.length === 0) {
215
+ throw new chunkVW3VVW6L_cjs.InvalidKeyError("Key must not be empty.");
216
+ }
217
+ if (encoding === "hex" && !HEX_PATTERN.test(key)) {
218
+ throw new chunkVW3VVW6L_cjs.InvalidKeyError(
219
+ 'A hex key must contain an even number of hexadecimal characters. Pass `keyEncoding: "utf8"` for a passphrase.'
220
+ );
221
+ }
222
+ }
223
+
224
+ // src/rules/alwaysRedact.ts
225
+ function escapeRegExp(value) {
226
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
227
+ }
228
+ function normalizeValues(values) {
229
+ const unique = new Set(values.map((value) => value.trim()).filter((value) => value.length > 0));
230
+ return [...unique].sort((left, right) => right.length - left.length || left.localeCompare(right));
231
+ }
232
+ function normalizePatterns(patterns) {
233
+ return patterns.map((pattern) => {
234
+ const source = typeof pattern === "string" ? pattern : pattern.source;
235
+ try {
236
+ new RegExp(source, "u");
237
+ } catch (cause) {
238
+ throw new chunkVW3VVW6L_cjs.InvalidOptionError(
239
+ `alwaysRedact.patterns contains an invalid regular expression: ${String(cause)}`
240
+ );
241
+ }
242
+ return `(?:${source})`;
243
+ });
244
+ }
245
+ function createAlwaysRedactRule(options) {
246
+ if (!options) {
247
+ return null;
248
+ }
249
+ const patterns = [
250
+ ...normalizeValues(options.values ?? []).map((value) => `(?:${escapeRegExp(value)})`),
251
+ ...normalizePatterns(options.patterns ?? [])
252
+ ];
253
+ if (patterns.length === 0) {
254
+ return null;
255
+ }
256
+ return {
257
+ id: options.ruleId ?? ALWAYS_REDACT_RULE_ID,
258
+ label: "Always redacted",
259
+ description: "Values and patterns the caller marked as always sensitive.",
260
+ mode: options.mode ?? "pseudo",
261
+ token: options.token ?? ALWAYS_REDACT_TOKEN,
262
+ patterns
263
+ };
264
+ }
265
+
266
+ // src/core/allowlist.ts
267
+ function patternSource(pattern) {
268
+ return typeof pattern === "string" ? pattern : pattern.source;
269
+ }
270
+ function compileAnchored(patterns) {
271
+ const sources = patterns.map(patternSource).filter((source) => source.length > 0);
272
+ if (sources.length === 0) {
273
+ return null;
274
+ }
275
+ const combined = sources.map((source) => `(?:${source})`).join("|");
276
+ try {
277
+ return new RegExp(`^(?:${combined})$`, "u");
278
+ } catch (cause) {
279
+ throw new chunkVW3VVW6L_cjs.InvalidOptionError(
280
+ `neverRedact.patterns contains an invalid regular expression: ${String(cause)}`
281
+ );
282
+ }
283
+ }
284
+ function createAllowList(options) {
285
+ const values = new Set((options?.values ?? []).filter((value) => value.length > 0));
286
+ const pattern = compileAnchored(options?.patterns ?? []);
287
+ const byRule = /* @__PURE__ */ new Map();
288
+ for (const entry of options?.byRule ?? []) {
289
+ const existing = byRule.get(entry.ruleId) ?? /* @__PURE__ */ new Set();
290
+ for (const value of entry.values) {
291
+ if (value.length > 0) {
292
+ existing.add(value);
293
+ }
294
+ }
295
+ byRule.set(entry.ruleId, existing);
296
+ }
297
+ return {
298
+ empty: values.size === 0 && pattern === null && byRule.size === 0,
299
+ values,
300
+ pattern,
301
+ byRule
302
+ };
303
+ }
304
+ function isAllowed(allow, ruleId, value) {
305
+ if (allow.empty) {
306
+ return false;
307
+ }
308
+ if (allow.values.has(value)) {
309
+ return true;
310
+ }
311
+ if (allow.byRule.get(ruleId)?.has(value) === true) {
312
+ return true;
313
+ }
314
+ return allow.pattern !== null && allow.pattern.test(value);
315
+ }
316
+
317
+ // src/core/pseudonymize.ts
318
+ var TOKEN_HEX_LENGTH = 16;
319
+ var TOKEN_BYTES = TOKEN_HEX_LENGTH / 2;
320
+ var textEncoder = new TextEncoder();
321
+ function createPseudonymizer(key, encoding) {
322
+ const keyBytes = encoding === "utf8" ? textEncoder.encode(key) : hexToBytes(key);
323
+ const sign = createHmacSha256(keyBytes);
324
+ const cache = /* @__PURE__ */ new Map();
325
+ return (ruleId, value) => {
326
+ const cacheKey = `${ruleId}\0${value}`;
327
+ const cached = cache.get(cacheKey);
328
+ if (cached !== void 0) {
329
+ return cached;
330
+ }
331
+ const digest = sign(textEncoder.encode(cacheKey));
332
+ const token = bytesToHex(digest.subarray(0, TOKEN_BYTES));
333
+ cache.set(cacheKey, token);
334
+ return token;
335
+ };
336
+ }
337
+ function replacementPrefix(rule) {
338
+ if (rule.mode === "mask") {
339
+ return MASK_TOKEN_PREFIX;
340
+ }
341
+ return rule.token ?? rule.id.toUpperCase();
342
+ }
343
+
344
+ // src/core/compile.ts
345
+ function compileRules(rules, aggressive) {
346
+ const parts = rules.map((rule) => ({
347
+ id: rule.id,
348
+ patterns: aggressive ? [...rule.patterns, ...rule.aggressivePatterns ?? []] : rule.patterns
349
+ })).filter(({ patterns }) => patterns.length > 0).map(({ id, patterns }) => `(?<${id}>${patterns.join("|")})`);
350
+ if (parts.length === 0) {
351
+ return null;
352
+ }
353
+ try {
354
+ return new RegExp(parts.join("|"), "gu");
355
+ } catch (cause) {
356
+ throw new chunkVW3VVW6L_cjs.InvalidRuleError(`Failed to compile the combined rule pattern: ${String(cause)}`);
357
+ }
358
+ }
359
+ function createRuleContext(input) {
360
+ const { rules } = input;
361
+ return {
362
+ compiled: compileRules(rules, input.aggressive),
363
+ rules,
364
+ ruleIds: rules.map((rule) => rule.id),
365
+ rulesById: new Map(rules.map((rule) => [rule.id, rule])),
366
+ prefixById: new Map(rules.map((rule) => [rule.id, replacementPrefix(rule)])),
367
+ pseudonymize: input.pseudonymize,
368
+ allow: input.allow,
369
+ contextChars: input.contextChars,
370
+ json: input.json
371
+ };
372
+ }
373
+ function buildReplacement(ctx, ruleId, value) {
374
+ return `<${ctx.prefixById.get(ruleId) ?? ruleId.toUpperCase()}:${ctx.pseudonymize(ruleId, value)}>`;
375
+ }
376
+
377
+ // src/core/lineSplitter.ts
378
+ function extractLines(buffer) {
379
+ const lines = [];
380
+ let start = 0;
381
+ for (let i = 0; i < buffer.length; i += 1) {
382
+ if (buffer[i] === "\n") {
383
+ lines.push(buffer.slice(start, i + 1));
384
+ start = i + 1;
385
+ }
386
+ }
387
+ return { lines, rest: buffer.slice(start) };
388
+ }
389
+ function createLineSplitter(options = {}) {
390
+ const maxLineChars = options.maxLineChars ?? DEFAULT_MAX_LINE_CHARS;
391
+ const overlapChars = options.overlapChars ?? DEFAULT_LINE_OVERLAP_CHARS;
392
+ if (maxLineChars < 1) {
393
+ throw new chunkVW3VVW6L_cjs.InvalidOptionError("lines.maxLineChars must be at least 1.");
394
+ }
395
+ if (overlapChars < 0 || overlapChars >= maxLineChars) {
396
+ throw new chunkVW3VVW6L_cjs.InvalidOptionError(
397
+ `lines.overlapChars must be between 0 and lines.maxLineChars - 1 (${maxLineChars - 1}), got ${overlapChars}.`
398
+ );
399
+ }
400
+ let carry = "";
401
+ function push(chunk) {
402
+ carry += chunk;
403
+ const { lines, rest } = extractLines(carry);
404
+ carry = rest;
405
+ while (carry.length > maxLineChars) {
406
+ const cut = carry.length - overlapChars;
407
+ lines.push(carry.slice(0, cut));
408
+ carry = carry.slice(cut);
409
+ }
410
+ return lines;
411
+ }
412
+ function flush() {
413
+ if (carry.length === 0) {
414
+ return [];
415
+ }
416
+ const rest = carry;
417
+ carry = "";
418
+ return [rest];
419
+ }
420
+ return { push, flush };
421
+ }
422
+
423
+ // src/core/redactLine.ts
424
+ function sliceContext(text, index, length, chars) {
425
+ return {
426
+ contextBefore: text.slice(Math.max(0, index - chars), index),
427
+ contextAfter: text.slice(index + length, index + length + chars)
428
+ };
429
+ }
430
+ function matchedRuleId(ruleIds, groups) {
431
+ for (const id of ruleIds) {
432
+ if (groups[id] !== void 0) {
433
+ return id;
434
+ }
435
+ }
436
+ return void 0;
437
+ }
438
+ function redactLine(line, ctx, options = {}) {
439
+ const { compiled, ruleIds, rulesById, allow, contextChars } = ctx;
440
+ const withSegments = options.withSegments ?? false;
441
+ const withMatches = options.withMatches ?? false;
442
+ if (!compiled) {
443
+ return {
444
+ output: line,
445
+ counts: {},
446
+ matches: [],
447
+ segments: withSegments ? { before: [{ text: line, changed: false }], after: [{ text: line, changed: false }] } : void 0
448
+ };
449
+ }
450
+ compiled.lastIndex = 0;
451
+ const counts = {};
452
+ const matches = [];
453
+ const before = [];
454
+ const after = [];
455
+ let output = "";
456
+ let cursor = 0;
457
+ let match = compiled.exec(line);
458
+ while (match !== null) {
459
+ const ruleId = match.groups ? matchedRuleId(ruleIds, match.groups) : void 0;
460
+ const rule = ruleId === void 0 ? void 0 : rulesById.get(ruleId);
461
+ const original = match[0];
462
+ if (rule === void 0 || ruleId === void 0) {
463
+ compiled.lastIndex = match.index + Math.max(original.length, 1);
464
+ match = compiled.exec(line);
465
+ continue;
466
+ }
467
+ const rejected = rule.validate !== void 0 && !rule.validate(original) || isAllowed(allow, ruleId, original);
468
+ if (rejected) {
469
+ compiled.lastIndex = match.index + Math.max(original.length, 1);
470
+ match = compiled.exec(line);
471
+ continue;
472
+ }
473
+ const replacement = buildReplacement(ctx, ruleId, original);
474
+ if (withSegments) {
475
+ if (match.index > cursor) {
476
+ const gap = line.slice(cursor, match.index);
477
+ before.push({ text: gap, changed: false });
478
+ after.push({ text: gap, changed: false });
479
+ }
480
+ before.push({ text: original, changed: true });
481
+ after.push({ text: replacement, changed: true });
482
+ }
483
+ output += line.slice(cursor, match.index) + replacement;
484
+ cursor = match.index + original.length;
485
+ counts[ruleId] = (counts[ruleId] ?? 0) + 1;
486
+ if (withMatches) {
487
+ matches.push({
488
+ ruleId,
489
+ original,
490
+ replacement,
491
+ ...contextChars > 0 ? sliceContext(line, match.index, original.length, contextChars) : {}
492
+ });
493
+ }
494
+ if (original.length === 0) {
495
+ compiled.lastIndex += 1;
496
+ }
497
+ match = compiled.exec(line);
498
+ }
499
+ output += line.slice(cursor);
500
+ if (withSegments && cursor < line.length) {
501
+ const tail = line.slice(cursor);
502
+ before.push({ text: tail, changed: false });
503
+ after.push({ text: tail, changed: false });
504
+ }
505
+ return {
506
+ output,
507
+ counts,
508
+ matches,
509
+ segments: withSegments ? { before, after } : void 0
510
+ };
511
+ }
512
+
513
+ // src/core/redactJsonLine.ts
514
+ var REPLACEMENT_TOKEN_RE = /<[A-Z][A-Z0-9]*:[0-9a-f]+>/g;
515
+ function looksLikeJson(line) {
516
+ const trimmed = line.trim();
517
+ return trimmed.length > 0 && (trimmed.startsWith("{") || trimmed.startsWith("["));
518
+ }
519
+ function normalizeKey2(key) {
520
+ return key.toLowerCase().replace(/[-_]/g, "");
521
+ }
522
+ function buildJsonKeyIndex(rules) {
523
+ const index = /* @__PURE__ */ new Map();
524
+ for (const rule of rules) {
525
+ for (const key of rule.jsonKeys ?? []) {
526
+ const normalized = normalizeKey2(key);
527
+ if (!index.has(normalized)) {
528
+ index.set(normalized, rule);
529
+ }
530
+ }
531
+ }
532
+ return index;
533
+ }
534
+ var jsonKeyIndexCache = /* @__PURE__ */ new WeakMap();
535
+ function jsonKeyIndex(rules) {
536
+ const cached = jsonKeyIndexCache.get(rules);
537
+ if (cached) {
538
+ return cached;
539
+ }
540
+ const index = buildJsonKeyIndex(rules);
541
+ jsonKeyIndexCache.set(rules, index);
542
+ return index;
543
+ }
544
+ function mergeCounts(target, source) {
545
+ for (const [id, count] of Object.entries(source)) {
546
+ target[id] = (target[id] ?? 0) + count;
547
+ }
548
+ }
549
+ function highlightReplacements(text) {
550
+ const segments = [];
551
+ let cursor = 0;
552
+ REPLACEMENT_TOKEN_RE.lastIndex = 0;
553
+ let match = REPLACEMENT_TOKEN_RE.exec(text);
554
+ while (match !== null) {
555
+ if (match.index > cursor) {
556
+ segments.push({ text: text.slice(cursor, match.index), changed: false });
557
+ }
558
+ segments.push({ text: match[0], changed: true });
559
+ cursor = match.index + match[0].length;
560
+ match = REPLACEMENT_TOKEN_RE.exec(text);
561
+ }
562
+ if (cursor < text.length) {
563
+ segments.push({ text: text.slice(cursor), changed: false });
564
+ }
565
+ return segments.length > 0 ? segments : [{ text, changed: false }];
566
+ }
567
+ function redactJsonLine(line, ctx, options = {}) {
568
+ const terminatorMatch = /(\r\n|\r|\n)$/.exec(line);
569
+ const terminator = terminatorMatch ? terminatorMatch[0] : "";
570
+ const content = terminator ? line.slice(0, -terminator.length) : line;
571
+ let parsed;
572
+ try {
573
+ parsed = JSON.parse(content);
574
+ } catch {
575
+ return null;
576
+ }
577
+ if (typeof parsed !== "object" || parsed === null) {
578
+ return null;
579
+ }
580
+ const withSegments = options.withSegments ?? false;
581
+ const withMatches = options.withMatches ?? false;
582
+ const keyIndex = jsonKeyIndex(ctx.rules);
583
+ const counts = {};
584
+ const matches = [];
585
+ const contextFor = (value) => {
586
+ if (ctx.contextChars <= 0) {
587
+ return {};
588
+ }
589
+ const index = content.indexOf(value);
590
+ if (index < 0) {
591
+ return { contextBefore: "", contextAfter: "" };
592
+ }
593
+ return sliceContext(content, index, value.length, ctx.contextChars);
594
+ };
595
+ const redactValue = (value, key) => {
596
+ if (typeof value === "string") {
597
+ const fieldRule = key === void 0 ? void 0 : keyIndex.get(normalizeKey2(key));
598
+ if (fieldRule) {
599
+ if (isAllowed(ctx.allow, fieldRule.id, value)) {
600
+ return value;
601
+ }
602
+ const replacement = buildReplacement(ctx, fieldRule.id, value);
603
+ counts[fieldRule.id] = (counts[fieldRule.id] ?? 0) + 1;
604
+ if (withMatches) {
605
+ matches.push({
606
+ ruleId: fieldRule.id,
607
+ original: value,
608
+ replacement,
609
+ ...contextFor(value)
610
+ });
611
+ }
612
+ return replacement;
613
+ }
614
+ const result = redactLine(value, ctx, { withMatches });
615
+ mergeCounts(counts, result.counts);
616
+ if (withMatches) {
617
+ for (const match of result.matches) {
618
+ matches.push({ ...match, ...contextFor(match.original) });
619
+ }
620
+ }
621
+ return result.output;
622
+ }
623
+ if (Array.isArray(value)) {
624
+ return value.map((item) => redactValue(item));
625
+ }
626
+ if (value !== null && typeof value === "object") {
627
+ const out = {};
628
+ for (const [entryKey, entryValue] of Object.entries(value)) {
629
+ out[entryKey] = redactValue(entryValue, entryKey);
630
+ }
631
+ return out;
632
+ }
633
+ return value;
634
+ };
635
+ const output = JSON.stringify(redactValue(parsed)) + terminator;
636
+ if (!withSegments) {
637
+ return { output, counts, matches };
638
+ }
639
+ const preview = redactLine(line, ctx, { withSegments: true });
640
+ return {
641
+ output,
642
+ counts,
643
+ matches,
644
+ segments: {
645
+ before: preview.segments?.before ?? [{ text: line, changed: false }],
646
+ after: highlightReplacements(output)
647
+ }
648
+ };
649
+ }
650
+
651
+ // src/core/report.ts
652
+ function createReportCollector(options = {}) {
653
+ const previewBytes = options.previewBytes ?? DEFAULT_PREVIEW_BYTES;
654
+ const collectReplacements = options.replacements ?? true;
655
+ if (previewBytes < 0) {
656
+ throw new chunkVW3VVW6L_cjs.InvalidOptionError("report.previewBytes must not be negative.");
657
+ }
658
+ const counts = {};
659
+ const replacements = /* @__PURE__ */ new Map();
660
+ const before = [];
661
+ const after = [];
662
+ let lineCount = 0;
663
+ let previewBytesUsed = 0;
664
+ let previewClosed = previewBytes === 0;
665
+ function needsSegments() {
666
+ return !previewClosed;
667
+ }
668
+ function needsMatches() {
669
+ return collectReplacements;
670
+ }
671
+ function record(result) {
672
+ lineCount += 1;
673
+ for (const [id, count] of Object.entries(result.counts)) {
674
+ counts[id] = (counts[id] ?? 0) + count;
675
+ }
676
+ if (collectReplacements) {
677
+ for (const match of result.matches) {
678
+ const key = `${match.ruleId}\0${match.original}`;
679
+ const existing = replacements.get(key);
680
+ if (existing) {
681
+ existing.count += 1;
682
+ } else {
683
+ replacements.set(key, {
684
+ ruleId: match.ruleId,
685
+ original: match.original,
686
+ replacement: match.replacement,
687
+ count: 1,
688
+ ...match.contextBefore !== void 0 ? { contextBefore: match.contextBefore, contextAfter: match.contextAfter } : {}
689
+ });
690
+ }
691
+ }
692
+ }
693
+ if (previewClosed) {
694
+ return;
695
+ }
696
+ if (result.segments) {
697
+ before.push(...result.segments.before);
698
+ after.push(...result.segments.after);
699
+ }
700
+ previewBytesUsed += result.output.length;
701
+ if (previewBytesUsed >= previewBytes) {
702
+ previewClosed = true;
703
+ }
704
+ }
705
+ function build(snapshot) {
706
+ const includeReplacements = snapshot?.includeReplacements ?? true;
707
+ let totalMatches = 0;
708
+ for (const count of Object.values(counts)) {
709
+ totalMatches += count;
710
+ }
711
+ return {
712
+ counts: { ...counts },
713
+ totalMatches,
714
+ lineCount,
715
+ replacements: includeReplacements ? [...replacements.values()] : [],
716
+ preview: { before: [...before], after: [...after] }
717
+ };
718
+ }
719
+ return { needsSegments, needsMatches, record, build };
720
+ }
721
+
722
+ // src/core/resolveRules.ts
723
+ function toRule(selector) {
724
+ if (typeof selector === "string") {
725
+ const builtin = chunkVW3VVW6L_cjs.getBuiltinRule(selector);
726
+ if (!builtin) {
727
+ throw new chunkVW3VVW6L_cjs.UnknownRuleError(
728
+ `Unknown rule "${selector}". Pass a rule object for custom rules, or one of the built-in identifiers.`
729
+ );
730
+ }
731
+ return builtin;
732
+ }
733
+ return chunkVW3VVW6L_cjs.validateRule(selector);
734
+ }
735
+ function resolveRules(selectors, extraRules = []) {
736
+ const resolved = [];
737
+ const positions = /* @__PURE__ */ new Map();
738
+ for (const selector of [...selectors, ...extraRules]) {
739
+ const rule = toRule(selector);
740
+ const existing = positions.get(rule.id);
741
+ if (existing === void 0) {
742
+ positions.set(rule.id, resolved.length);
743
+ resolved.push(rule);
744
+ } else {
745
+ resolved[existing] = rule;
746
+ }
747
+ }
748
+ return resolved;
749
+ }
750
+
751
+ // src/core/sanitizer.ts
752
+ async function* toAsyncIterable(source) {
753
+ yield* source;
754
+ }
755
+ function processLine(line, ctx, collector) {
756
+ const options = {
757
+ withSegments: collector.needsSegments(),
758
+ withMatches: collector.needsMatches()
759
+ };
760
+ const parsed = ctx.json && looksLikeJson(line) ? redactJsonLine(line, ctx, options) : null;
761
+ const result = parsed ?? redactLine(line, ctx, options);
762
+ collector.record(result);
763
+ return result.output;
764
+ }
765
+ function createSanitizer(options = {}) {
766
+ const keyEncoding = options.keyEncoding ?? "hex";
767
+ const key = options.key ?? generateKey();
768
+ assertKey(key, keyEncoding);
769
+ const contextChars = options.report?.contextChars ?? 0;
770
+ if (contextChars < 0) {
771
+ throw new chunkVW3VVW6L_cjs.InvalidOptionError("report.contextChars must not be negative.");
772
+ }
773
+ const selected = resolveWithAlwaysRedact(options);
774
+ const ctx = createRuleContext({
775
+ rules: selected,
776
+ aggressive: options.aggressive ?? false,
777
+ pseudonymize: createPseudonymizer(key, keyEncoding),
778
+ allow: createAllowList(options.neverRedact),
779
+ contextChars,
780
+ json: options.json !== false
781
+ });
782
+ const collectorOptions = {
783
+ previewBytes: options.report?.previewBytes,
784
+ replacements: options.report?.replacements
785
+ };
786
+ const rules = selected.map((rule) => ({
787
+ id: rule.id,
788
+ label: rule.label,
789
+ description: rule.description,
790
+ mode: rule.mode
791
+ }));
792
+ function sanitizeText2(text) {
793
+ const collector = createReportCollector(collectorOptions);
794
+ const splitter = createLineSplitter(options.lines);
795
+ let output = "";
796
+ for (const line of splitter.push(text)) {
797
+ output += processLine(line, ctx, collector);
798
+ }
799
+ for (const line of splitter.flush()) {
800
+ output += processLine(line, ctx, collector);
801
+ }
802
+ return { output, report: collector.build() };
803
+ }
804
+ async function sanitizeStream2(source, sink, streamOptions = {}) {
805
+ const collector = createReportCollector(collectorOptions);
806
+ const splitter = createLineSplitter(options.lines);
807
+ const { signal, onProgress } = streamOptions;
808
+ let charsRead = 0;
809
+ const writeLine = async (line) => {
810
+ if (signal?.aborted === true) {
811
+ throw new chunkVW3VVW6L_cjs.SanitizationAbortedError();
812
+ }
813
+ await sink.write(processLine(line, ctx, collector));
814
+ };
815
+ for await (const chunk of toAsyncIterable(source)) {
816
+ charsRead += chunk.length;
817
+ for (const line of splitter.push(chunk)) {
818
+ await writeLine(line);
819
+ }
820
+ onProgress?.({ charsRead, report: collector.build({ includeReplacements: false }) });
821
+ }
822
+ for (const line of splitter.flush()) {
823
+ await writeLine(line);
824
+ }
825
+ await sink.close?.();
826
+ return collector.build();
827
+ }
828
+ return { key, rules, sanitizeText: sanitizeText2, sanitizeStream: sanitizeStream2 };
829
+ }
830
+ function resolveWithAlwaysRedact(options) {
831
+ const selected = resolveRules(options.rules ?? chunkVW3VVW6L_cjs.builtinRuleIds, options.extraRules);
832
+ const alwaysRule = createAlwaysRedactRule(options.alwaysRedact);
833
+ if (!alwaysRule) {
834
+ return selected;
835
+ }
836
+ if (selected.some((rule) => rule.id === alwaysRule.id)) {
837
+ throw new chunkVW3VVW6L_cjs.InvalidOptionError(
838
+ `alwaysRedact.ruleId "${alwaysRule.id}" collides with an active rule. Choose an identifier that is not in use.`
839
+ );
840
+ }
841
+ return [alwaysRule, ...selected];
842
+ }
843
+ function sanitizeText(text, options) {
844
+ return createSanitizer(options).sanitizeText(text);
845
+ }
846
+ function sanitizeStream(source, sink, options) {
847
+ const { onProgress, signal, ...sanitizerOptions } = options ?? {};
848
+ return createSanitizer(sanitizerOptions).sanitizeStream(source, sink, { onProgress, signal });
849
+ }
850
+
851
+ // src/io/sources.ts
852
+ var DEFAULT_CHUNK_BYTES = 4 * 1024 * 1024;
853
+ function fromString(text, chunkChars = DEFAULT_CHUNK_BYTES) {
854
+ return {
855
+ *[Symbol.iterator]() {
856
+ for (let offset = 0; offset < text.length; offset += chunkChars) {
857
+ yield text.slice(offset, offset + chunkChars);
858
+ }
859
+ }
860
+ };
861
+ }
862
+ function fromBlob(blob, chunkBytes = DEFAULT_CHUNK_BYTES) {
863
+ return {
864
+ async *[Symbol.asyncIterator]() {
865
+ const decoder = new TextDecoder("utf-8");
866
+ for (let offset = 0; offset < blob.size; offset += chunkBytes) {
867
+ const slice = blob.slice(offset, Math.min(offset + chunkBytes, blob.size));
868
+ const buffer = await slice.arrayBuffer();
869
+ const text = decoder.decode(new Uint8Array(buffer), { stream: true });
870
+ if (text.length > 0) {
871
+ yield text;
872
+ }
873
+ }
874
+ const tail = decoder.decode();
875
+ if (tail.length > 0) {
876
+ yield tail;
877
+ }
878
+ }
879
+ };
880
+ }
881
+ function fromWebStream(stream) {
882
+ return {
883
+ async *[Symbol.asyncIterator]() {
884
+ const reader = stream.getReader();
885
+ const decoder = new TextDecoder("utf-8");
886
+ try {
887
+ for (; ; ) {
888
+ const { done, value } = await reader.read();
889
+ if (done) {
890
+ break;
891
+ }
892
+ if (value === void 0) {
893
+ continue;
894
+ }
895
+ const text = typeof value === "string" ? value : decoder.decode(value, { stream: true });
896
+ if (text.length > 0) {
897
+ yield text;
898
+ }
899
+ }
900
+ const tail = decoder.decode();
901
+ if (tail.length > 0) {
902
+ yield tail;
903
+ }
904
+ } finally {
905
+ reader.releaseLock();
906
+ }
907
+ }
908
+ };
909
+ }
910
+
911
+ // src/io/sinks.ts
912
+ function toStringSink() {
913
+ const chunks = [];
914
+ return {
915
+ write(chunk) {
916
+ chunks.push(chunk);
917
+ },
918
+ get text() {
919
+ return chunks.join("");
920
+ }
921
+ };
922
+ }
923
+ function toCallbackSink(onChunk, onClose) {
924
+ return {
925
+ write: onChunk,
926
+ ...onClose ? { close: onClose } : {}
927
+ };
928
+ }
929
+ function toNullSink() {
930
+ return { write: () => void 0 };
931
+ }
932
+
933
+ exports.createSanitizer = createSanitizer;
934
+ exports.fromBlob = fromBlob;
935
+ exports.fromString = fromString;
936
+ exports.fromWebStream = fromWebStream;
937
+ exports.generateKey = generateKey;
938
+ exports.sanitizeStream = sanitizeStream;
939
+ exports.sanitizeText = sanitizeText;
940
+ exports.toCallbackSink = toCallbackSink;
941
+ exports.toNullSink = toNullSink;
942
+ exports.toStringSink = toStringSink;
943
+ //# sourceMappingURL=chunk-WISLDJT3.cjs.map
944
+ //# sourceMappingURL=chunk-WISLDJT3.cjs.map