@socprime/logtotal-sanitizer 0.2.0-beta.3 → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkEUNFMCKE_cjs = require('./chunk-EUNFMCKE.cjs');
3
+ var chunkVELXTD3L_cjs = require('./chunk-VELXTD3L.cjs');
4
4
 
5
5
  // src/core/constants.ts
6
6
  var DEFAULT_PREVIEW_BYTES = 256 * 1024;
@@ -200,11 +200,11 @@ function bytesToHex(bytes) {
200
200
  var HEX_PATTERN = /^(?:[0-9a-fA-F]{2})+$/;
201
201
  function generateKey(byteLength = DEFAULT_KEY_BYTES) {
202
202
  if (!Number.isInteger(byteLength) || byteLength < 16) {
203
- throw new chunkEUNFMCKE_cjs.InvalidKeyError(`Key length must be an integer of at least 16 bytes, got ${byteLength}.`);
203
+ throw new chunkVELXTD3L_cjs.InvalidKeyError(`Key length must be an integer of at least 16 bytes, got ${byteLength}.`);
204
204
  }
205
205
  const source = globalThis.crypto;
206
206
  if (typeof source?.getRandomValues !== "function") {
207
- throw new chunkEUNFMCKE_cjs.InvalidKeyError(
207
+ throw new chunkVELXTD3L_cjs.InvalidKeyError(
208
208
  "No secure random source available. Pass an explicit `key` or provide a Web Crypto implementation on globalThis.crypto."
209
209
  );
210
210
  }
@@ -214,10 +214,10 @@ function generateKey(byteLength = DEFAULT_KEY_BYTES) {
214
214
  }
215
215
  function assertKey(key, encoding) {
216
216
  if (key.length === 0) {
217
- throw new chunkEUNFMCKE_cjs.InvalidKeyError("Key must not be empty.");
217
+ throw new chunkVELXTD3L_cjs.InvalidKeyError("Key must not be empty.");
218
218
  }
219
219
  if (encoding === "hex" && !HEX_PATTERN.test(key)) {
220
- throw new chunkEUNFMCKE_cjs.InvalidKeyError(
220
+ throw new chunkVELXTD3L_cjs.InvalidKeyError(
221
221
  'A hex key must contain an even number of hexadecimal characters. Pass `keyEncoding: "utf8"` for a passphrase.'
222
222
  );
223
223
  }
@@ -237,7 +237,7 @@ function normalizePatterns(patterns) {
237
237
  try {
238
238
  new RegExp(source, "u");
239
239
  } catch (cause) {
240
- throw new chunkEUNFMCKE_cjs.InvalidOptionError(
240
+ throw new chunkVELXTD3L_cjs.InvalidOptionError(
241
241
  `alwaysRedact.patterns contains an invalid regular expression: ${String(cause)}`
242
242
  );
243
243
  }
@@ -278,7 +278,7 @@ function compileAnchored(patterns) {
278
278
  try {
279
279
  return new RegExp(`^(?:${combined})$`, "u");
280
280
  } catch (cause) {
281
- throw new chunkEUNFMCKE_cjs.InvalidOptionError(
281
+ throw new chunkVELXTD3L_cjs.InvalidOptionError(
282
282
  `neverRedact.patterns contains an invalid regular expression: ${String(cause)}`
283
283
  );
284
284
  }
@@ -365,13 +365,13 @@ function compileOne(rule, aggressive) {
365
365
  try {
366
366
  fragments.push({
367
367
  regex: new RegExp(pattern, "gu"),
368
- hasValueGroup: chunkEUNFMCKE_cjs.countCapturingGroups(pattern) === 1
368
+ hasValueGroup: chunkVELXTD3L_cjs.countCapturingGroups(pattern) === 1
369
369
  });
370
370
  } catch (cause) {
371
- throw new chunkEUNFMCKE_cjs.InvalidRuleError(`Failed to compile rule "${rule.id}": ${String(cause)}`);
371
+ throw new chunkVELXTD3L_cjs.InvalidRuleError(`Failed to compile rule "${rule.id}": ${String(cause)}`);
372
372
  }
373
373
  }
374
- const gateSource = patterns.map(chunkEUNFMCKE_cjs.deriveGateSource).join("|");
374
+ const gateSource = patterns.map(chunkVELXTD3L_cjs.deriveGateSource).join("|");
375
375
  let gate = null;
376
376
  if (!SKIP_REGEX_GATE.has(rule.id) && gateSource.length > 0 && gateSource !== source) {
377
377
  try {
@@ -431,10 +431,10 @@ function createLineSplitter(options = {}) {
431
431
  const maxLineChars = options.maxLineChars ?? DEFAULT_MAX_LINE_CHARS;
432
432
  const overlapChars = options.overlapChars ?? DEFAULT_LINE_OVERLAP_CHARS;
433
433
  if (maxLineChars < 1) {
434
- throw new chunkEUNFMCKE_cjs.InvalidOptionError("lines.maxLineChars must be at least 1.");
434
+ throw new chunkVELXTD3L_cjs.InvalidOptionError("lines.maxLineChars must be at least 1.");
435
435
  }
436
436
  if (overlapChars < 0 || overlapChars >= maxLineChars) {
437
- throw new chunkEUNFMCKE_cjs.InvalidOptionError(
437
+ throw new chunkVELXTD3L_cjs.InvalidOptionError(
438
438
  `lines.overlapChars must be between 0 and lines.maxLineChars - 1 (${maxLineChars - 1}), got ${overlapChars}.`
439
439
  );
440
440
  }
@@ -461,6 +461,181 @@ function createLineSplitter(options = {}) {
461
461
  return { push, flush };
462
462
  }
463
463
 
464
+ // src/core/jsonKeyMatcher.ts
465
+ var RESOLVE_CACHE_LIMIT = 1e4;
466
+ function normalizeKey2(key) {
467
+ return key.toLowerCase().replace(/[-_.\s]/g, "");
468
+ }
469
+ function shouldPreserveJsonValue(value) {
470
+ return value.length === 0 || value === "-";
471
+ }
472
+ function buildTables(rules) {
473
+ const exact = /* @__PURE__ */ new Map();
474
+ const contains = [];
475
+ for (const rule of rules) {
476
+ for (const key of rule.jsonKeys ?? []) {
477
+ const normalized = normalizeKey2(key);
478
+ if (!exact.has(normalized)) {
479
+ exact.set(normalized, rule);
480
+ }
481
+ }
482
+ for (const fragment of rule.jsonKeyContains ?? []) {
483
+ contains.push({ fragment: normalizeKey2(fragment), rule });
484
+ }
485
+ }
486
+ return { exact, contains };
487
+ }
488
+ function lastSegment(key) {
489
+ const dot = key.lastIndexOf(".");
490
+ return dot === -1 ? key : key.slice(dot + 1);
491
+ }
492
+ function resolveUncached(tables, key, parent) {
493
+ const dot = key.lastIndexOf(".");
494
+ const leaf = normalizeKey2(dot === -1 ? key : key.slice(dot + 1));
495
+ const parentRaw = dot === -1 ? parent : key.slice(0, dot);
496
+ if (leaf.length === 0) {
497
+ return null;
498
+ }
499
+ if (parentRaw !== void 0) {
500
+ const scoped = tables.exact.get(normalizeKey2(lastSegment(parentRaw)) + leaf);
501
+ if (scoped) {
502
+ return scoped;
503
+ }
504
+ }
505
+ const exact = tables.exact.get(leaf);
506
+ if (exact) {
507
+ return exact;
508
+ }
509
+ for (const entry of tables.contains) {
510
+ if (leaf.includes(entry.fragment)) {
511
+ return entry.rule;
512
+ }
513
+ }
514
+ return null;
515
+ }
516
+ function buildMatcher(rules) {
517
+ const tables = buildTables(rules);
518
+ const cache = /* @__PURE__ */ new Map();
519
+ return {
520
+ resolve(key, parent) {
521
+ const cacheKey = parent === void 0 ? key : `${parent}\0${key}`;
522
+ let rule = cache.get(cacheKey);
523
+ if (rule === void 0) {
524
+ rule = resolveUncached(tables, key, parent);
525
+ if (cache.size >= RESOLVE_CACHE_LIMIT) {
526
+ cache.clear();
527
+ }
528
+ cache.set(cacheKey, rule);
529
+ }
530
+ return rule ?? void 0;
531
+ }
532
+ };
533
+ }
534
+ var matcherCache = /* @__PURE__ */ new WeakMap();
535
+ function jsonKeyMatcher(rules) {
536
+ const cached = matcherCache.get(rules);
537
+ if (cached) {
538
+ return cached;
539
+ }
540
+ const matcher = buildMatcher(rules);
541
+ matcherCache.set(rules, matcher);
542
+ return matcher;
543
+ }
544
+
545
+ // src/core/redactKeyValuePairs.ts
546
+ var NUMBER = "(-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)(?![\\w.:+-])";
547
+ var DOUBLE_QUOTED = new RegExp(
548
+ `(?<![\\\\\\w])"((?:[^"\\\\\\r\\n]|\\\\.){1,128})"\\s*:\\s*(?:"((?:[^"\\\\]|\\\\.)*)"|${NUMBER})`,
549
+ "g"
550
+ );
551
+ var ESCAPED = new RegExp(
552
+ `\\\\"((?:[^"\\\\\\r\\n]|\\\\\\\\){1,128})\\\\"\\s*:\\s*(?:\\\\"((?:\\\\\\\\\\\\"|\\\\\\\\|\\\\[^"\\\\]|[^"\\\\])*)\\\\"|${NUMBER})`,
553
+ "g"
554
+ );
555
+ var SINGLE_QUOTED = new RegExp(
556
+ `(?<![\\\\\\w])'((?:[^'\\\\\\r\\n]|\\\\.){1,128})'\\s*:\\s*(?:'((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|${NUMBER})`,
557
+ "g"
558
+ );
559
+ function decodeJsonString(raw) {
560
+ if (!raw.includes("\\")) {
561
+ return raw;
562
+ }
563
+ try {
564
+ return JSON.parse(`"${raw}"`);
565
+ } catch {
566
+ return raw;
567
+ }
568
+ }
569
+ function decodeEscapedJsonString(raw) {
570
+ return decodeJsonString(decodeJsonString(raw));
571
+ }
572
+ function decodePythonString(raw) {
573
+ return raw.includes("\\") ? raw.replace(/\\(['"\\])/g, "$1") : raw;
574
+ }
575
+ function collect(line, regex, rules, decode, numberQuote, out) {
576
+ const matcher = jsonKeyMatcher(rules);
577
+ regex.lastIndex = 0;
578
+ for (let found = regex.exec(line); found !== null; found = regex.exec(line)) {
579
+ const rawKey = found[1];
580
+ let rawValue;
581
+ let quote = "";
582
+ for (let group = 2; group < found.length; group += 1) {
583
+ if (found[group] !== void 0) {
584
+ rawValue = found[group];
585
+ quote = group === found.length - 1 ? numberQuote : "";
586
+ break;
587
+ }
588
+ }
589
+ if (rawValue === void 0) {
590
+ continue;
591
+ }
592
+ const value = quote === "" ? decode(rawValue) : rawValue;
593
+ if (shouldPreserveJsonValue(value)) {
594
+ continue;
595
+ }
596
+ const rule = matcher.resolve(decode(rawKey));
597
+ if (!rule) {
598
+ continue;
599
+ }
600
+ const end = found.index + found[0].length - (quote === "" ? 1 : 0);
601
+ const valueEnd = quote === "" && regex === ESCAPED ? end - 1 : end;
602
+ out.push({
603
+ keyStart: found.index,
604
+ match: { start: valueEnd - rawValue.length, end: valueEnd, value, rule, quote }
605
+ });
606
+ }
607
+ }
608
+ function findKeyValueMatches(line, rules) {
609
+ const doubleQuoted = line.includes('":') || line.includes('" :');
610
+ const singleQuoted = line.includes("':") || line.includes("' :");
611
+ if (!doubleQuoted && !singleQuoted) {
612
+ return [];
613
+ }
614
+ const candidates = [];
615
+ if (doubleQuoted) {
616
+ collect(line, DOUBLE_QUOTED, rules, decodeJsonString, '"', candidates);
617
+ if (line.includes('\\"')) {
618
+ collect(line, ESCAPED, rules, decodeEscapedJsonString, '\\"', candidates);
619
+ }
620
+ }
621
+ if (singleQuoted) {
622
+ collect(line, SINGLE_QUOTED, rules, decodePythonString, "'", candidates);
623
+ }
624
+ if (candidates.length === 0) {
625
+ return [];
626
+ }
627
+ candidates.sort((left, right) => left.keyStart - right.keyStart);
628
+ const matches = [];
629
+ let floor = 0;
630
+ for (const { keyStart, match } of candidates) {
631
+ if (keyStart >= floor) {
632
+ matches.push(match);
633
+ floor = match.end;
634
+ }
635
+ }
636
+ return matches;
637
+ }
638
+
464
639
  // src/core/redactLine.ts
465
640
  function sliceContext(text, index, length, chars) {
466
641
  return {
@@ -498,7 +673,7 @@ function openCursors(line, compiled) {
498
673
  const cursors = [];
499
674
  for (let ruleIndex = 0; ruleIndex < compiled.length; ruleIndex += 1) {
500
675
  const rule = compiled[ruleIndex];
501
- if (rule.gateChars !== null && !chunkEUNFMCKE_cjs.lineHasAnyChar(line, rule.gateChars)) {
676
+ if (rule.gateChars !== null && !chunkVELXTD3L_cjs.lineHasAnyChar(line, rule.gateChars)) {
502
677
  continue;
503
678
  }
504
679
  if (rule.gate !== null) {
@@ -540,11 +715,21 @@ function pickNext(cursors) {
540
715
  }
541
716
  return best;
542
717
  }
718
+ function blankOut(line, spans) {
719
+ let out = "";
720
+ let cursor = 0;
721
+ for (const span of spans) {
722
+ out += line.slice(cursor, span.start) + " ".repeat(span.end - span.start);
723
+ cursor = span.end;
724
+ }
725
+ return out + line.slice(cursor);
726
+ }
543
727
  function redactLine(line, ctx, options = {}) {
544
728
  const { compiled, rulesById, allow, contextChars } = ctx;
545
729
  const withSegments = options.withSegments ?? false;
546
730
  const withMatches = options.withMatches ?? false;
547
- if (compiled.length === 0) {
731
+ const keyed = ctx.json ? findKeyValueMatches(line, ctx.rules) : [];
732
+ if (compiled.length === 0 && keyed.length === 0) {
548
733
  return {
549
734
  output: line,
550
735
  counts: {},
@@ -552,18 +737,65 @@ function redactLine(line, ctx, options = {}) {
552
737
  segments: withSegments ? { before: [{ text: line, changed: false }], after: [{ text: line, changed: false }] } : void 0
553
738
  };
554
739
  }
555
- const cursors = openCursors(line, compiled);
740
+ const scan = keyed.length === 0 ? line : blankOut(line, keyed);
741
+ const cursors = openCursors(scan, compiled);
556
742
  const counts = {};
557
743
  const matches = [];
558
744
  const before = [];
559
745
  const after = [];
560
746
  let output = "";
561
747
  let cursor = 0;
748
+ let keyedIndex = 0;
562
749
  for (; ; ) {
563
750
  const next = pickNext(cursors);
751
+ const pending = keyed[keyedIndex];
752
+ if (pending !== void 0 && (next === null || pending.start <= next.start)) {
753
+ keyedIndex += 1;
754
+ if (!isAllowed(allow, pending.rule.id, pending.value)) {
755
+ const token = buildReplacement(ctx, pending.rule.id, pending.value);
756
+ const raw = line.slice(pending.start, pending.end);
757
+ if (withSegments) {
758
+ if (pending.start > cursor) {
759
+ const gap = line.slice(cursor, pending.start);
760
+ before.push({ text: gap, changed: false });
761
+ after.push({ text: gap, changed: false });
762
+ }
763
+ before.push({ text: raw, changed: true });
764
+ if (pending.quote) {
765
+ after.push({ text: pending.quote, changed: false });
766
+ }
767
+ after.push({ text: token, changed: true });
768
+ if (pending.quote) {
769
+ after.push({ text: pending.quote, changed: false });
770
+ }
771
+ }
772
+ output += line.slice(cursor, pending.start) + pending.quote + token + pending.quote;
773
+ cursor = pending.end;
774
+ counts[pending.rule.id] = (counts[pending.rule.id] ?? 0) + 1;
775
+ if (withMatches) {
776
+ matches.push({
777
+ ruleId: pending.rule.id,
778
+ original: pending.value,
779
+ replacement: token,
780
+ index: pending.start,
781
+ ...contextChars > 0 ? sliceContext(line, pending.start, raw.length, contextChars) : {}
782
+ });
783
+ }
784
+ }
785
+ for (const candidate of cursors) {
786
+ if (candidate.active && candidate.start < pending.end) {
787
+ advance(candidate, scan, pending.end);
788
+ }
789
+ }
790
+ continue;
791
+ }
564
792
  if (next === null) {
565
793
  break;
566
794
  }
795
+ if (pending !== void 0 && next.start + next.original.length > pending.start) {
796
+ advance(next, scan, next.start + 1);
797
+ continue;
798
+ }
567
799
  const { start, original, ruleId } = next;
568
800
  const spanEnd = start + Math.max(original.length, 1);
569
801
  const rule = rulesById.get(ruleId);
@@ -594,7 +826,7 @@ function redactLine(line, ctx, options = {}) {
594
826
  }
595
827
  for (const candidate of cursors) {
596
828
  if (candidate.active && candidate.start < spanEnd) {
597
- advance(candidate, line, spanEnd);
829
+ advance(candidate, scan, spanEnd);
598
830
  }
599
831
  }
600
832
  }
@@ -618,51 +850,6 @@ function looksLikeJson(line) {
618
850
  const trimmed = line.trim();
619
851
  return trimmed.length > 0 && (trimmed.startsWith("{") || trimmed.startsWith("["));
620
852
  }
621
- function normalizeKey2(key) {
622
- return key.toLowerCase().replace(/[-_]/g, "");
623
- }
624
- function shouldPreserveJsonValue(value) {
625
- return value.length === 0 || value === "-";
626
- }
627
- function buildJsonKeyMatcher(rules) {
628
- const exact = /* @__PURE__ */ new Map();
629
- const contains = [];
630
- for (const rule of rules) {
631
- for (const key of rule.jsonKeys ?? []) {
632
- const normalized = normalizeKey2(key);
633
- if (!exact.has(normalized)) {
634
- exact.set(normalized, rule);
635
- }
636
- }
637
- for (const fragment of rule.jsonKeyContains ?? []) {
638
- contains.push({ fragment: normalizeKey2(fragment), rule });
639
- }
640
- }
641
- return { exact, contains };
642
- }
643
- function resolveJsonKeyRule(matcher, key) {
644
- const normalized = normalizeKey2(key);
645
- const exact = matcher.exact.get(normalized);
646
- if (exact) {
647
- return exact;
648
- }
649
- for (const entry of matcher.contains) {
650
- if (normalized.includes(entry.fragment)) {
651
- return entry.rule;
652
- }
653
- }
654
- return void 0;
655
- }
656
- var jsonKeyMatcherCache = /* @__PURE__ */ new WeakMap();
657
- function jsonKeyMatcher(rules) {
658
- const cached = jsonKeyMatcherCache.get(rules);
659
- if (cached) {
660
- return cached;
661
- }
662
- const matcher = buildJsonKeyMatcher(rules);
663
- jsonKeyMatcherCache.set(rules, matcher);
664
- return matcher;
665
- }
666
853
  function mergeCounts(target, source) {
667
854
  for (const [id, count] of Object.entries(source)) {
668
855
  target[id] = (target[id] ?? 0) + count;
@@ -689,6 +876,15 @@ function highlightReplacements(text) {
689
876
  function jsonStringContent(value) {
690
877
  return JSON.stringify(value).slice(1, -1);
691
878
  }
879
+ function locateJsonNumber(text, value, from = 0) {
880
+ const re = new RegExp(`:\\s*(${escapeRegExp(value)})(?![\\d.eE])`, "g");
881
+ re.lastIndex = from;
882
+ const match = re.exec(text);
883
+ if (match === null) {
884
+ return null;
885
+ }
886
+ return { index: match.index + match[0].length - value.length, length: value.length };
887
+ }
692
888
  function locateQuotedJsonValue(text, value, from = 0) {
693
889
  const encoded = jsonStringContent(value);
694
890
  const quotedAt = text.indexOf(`"${encoded}"`, from);
@@ -698,6 +894,9 @@ function locateQuotedJsonValue(text, value, from = 0) {
698
894
  return { index: quotedAt + 1, length: encoded.length };
699
895
  }
700
896
  function findOriginal(text, original, from) {
897
+ if (original.number === true) {
898
+ return locateJsonNumber(text, original.value, from);
899
+ }
701
900
  const encoded = jsonStringContent(original.value);
702
901
  if (original.wholeValue) {
703
902
  const quoted = `"${encoded}"`;
@@ -780,32 +979,39 @@ function redactJsonLine(line, ctx, options = {}) {
780
979
  }
781
980
  return { contextBefore: "", contextAfter: "" };
782
981
  };
783
- const redactValue = (value, key) => {
982
+ const redactField = (rule, value, number) => {
983
+ if (isAllowed(ctx.allow, rule.id, value)) {
984
+ return null;
985
+ }
986
+ const replacement = buildReplacement(ctx, rule.id, value);
987
+ counts[rule.id] = (counts[rule.id] ?? 0) + 1;
988
+ if (withSegments) {
989
+ originals.push({ value, wholeValue: true, number });
990
+ }
991
+ if (withMatches) {
992
+ const located = number ? locateJsonNumber(content, value) : locateQuotedJsonValue(content, value);
993
+ matches.push({
994
+ ruleId: rule.id,
995
+ original: value,
996
+ replacement,
997
+ index: located?.index ?? 0,
998
+ ...located && ctx.contextChars > 0 ? sliceContext(content, located.index, located.length, ctx.contextChars) : ctx.contextChars > 0 ? { contextBefore: "", contextAfter: "" } : {}
999
+ });
1000
+ }
1001
+ return replacement;
1002
+ };
1003
+ const redactValue = (value, key, parent) => {
1004
+ if (typeof value === "number") {
1005
+ const fieldRule = key === void 0 || !Number.isFinite(value) ? void 0 : keyMatcher.resolve(key, parent);
1006
+ return fieldRule ? redactField(fieldRule, String(value), true) ?? value : value;
1007
+ }
784
1008
  if (typeof value === "string") {
785
1009
  if (key !== void 0 && shouldPreserveJsonValue(value)) {
786
1010
  return value;
787
1011
  }
788
- const fieldRule = key === void 0 ? void 0 : resolveJsonKeyRule(keyMatcher, key);
1012
+ const fieldRule = key === void 0 ? void 0 : keyMatcher.resolve(key, parent);
789
1013
  if (fieldRule) {
790
- if (isAllowed(ctx.allow, fieldRule.id, value)) {
791
- return value;
792
- }
793
- const replacement = buildReplacement(ctx, fieldRule.id, value);
794
- counts[fieldRule.id] = (counts[fieldRule.id] ?? 0) + 1;
795
- if (withSegments) {
796
- originals.push({ value, wholeValue: true });
797
- }
798
- if (withMatches) {
799
- const located = locateQuotedJsonValue(content, value);
800
- matches.push({
801
- ruleId: fieldRule.id,
802
- original: value,
803
- replacement,
804
- index: located?.index ?? 0,
805
- ...located && ctx.contextChars > 0 ? sliceContext(content, located.index, located.length, ctx.contextChars) : ctx.contextChars > 0 ? { contextBefore: "", contextAfter: "" } : {}
806
- });
807
- }
808
- return replacement;
1014
+ return redactField(fieldRule, value, false) ?? value;
809
1015
  }
810
1016
  const result = redactLine(value, ctx, { withMatches: collectOriginals });
811
1017
  mergeCounts(counts, result.counts);
@@ -822,12 +1028,12 @@ function redactJsonLine(line, ctx, options = {}) {
822
1028
  return result.output;
823
1029
  }
824
1030
  if (Array.isArray(value)) {
825
- return value.map((item) => redactValue(item));
1031
+ return value.map((item) => redactValue(item, key, parent));
826
1032
  }
827
1033
  if (value !== null && typeof value === "object") {
828
1034
  const out = {};
829
1035
  for (const [entryKey, entryValue] of Object.entries(value)) {
830
- out[entryKey] = redactValue(entryValue, entryKey);
1036
+ out[entryKey] = redactValue(entryValue, entryKey, key);
831
1037
  }
832
1038
  return out;
833
1039
  }
@@ -855,10 +1061,10 @@ function createReportCollector(options = {}) {
855
1061
  const maxPerRule = options.maxReplacementsPerRule;
856
1062
  const limited = maxPerRule !== void 0;
857
1063
  if (previewBytes < 0) {
858
- throw new chunkEUNFMCKE_cjs.InvalidOptionError("report.previewBytes must not be negative.");
1064
+ throw new chunkVELXTD3L_cjs.InvalidOptionError("report.previewBytes must not be negative.");
859
1065
  }
860
1066
  if (limited && (maxPerRule < 0 || !Number.isInteger(maxPerRule))) {
861
- throw new chunkEUNFMCKE_cjs.InvalidOptionError("report.maxReplacementsPerRule must be a non-negative integer.");
1067
+ throw new chunkVELXTD3L_cjs.InvalidOptionError("report.maxReplacementsPerRule must be a non-negative integer.");
862
1068
  }
863
1069
  const counts = {};
864
1070
  const replacements = /* @__PURE__ */ new Map();
@@ -938,15 +1144,15 @@ function createReportCollector(options = {}) {
938
1144
  // src/core/resolveRules.ts
939
1145
  function toRule(selector) {
940
1146
  if (typeof selector === "string") {
941
- const builtin = chunkEUNFMCKE_cjs.getBuiltinRule(selector);
1147
+ const builtin = chunkVELXTD3L_cjs.getBuiltinRule(selector);
942
1148
  if (!builtin) {
943
- throw new chunkEUNFMCKE_cjs.UnknownRuleError(
1149
+ throw new chunkVELXTD3L_cjs.UnknownRuleError(
944
1150
  `Unknown rule "${selector}". Pass a rule object for custom rules, or one of the built-in identifiers.`
945
1151
  );
946
1152
  }
947
1153
  return builtin;
948
1154
  }
949
- return chunkEUNFMCKE_cjs.validateRule(selector);
1155
+ return chunkVELXTD3L_cjs.validateRule(selector);
950
1156
  }
951
1157
  function resolveRules(selectors, extraRules = []) {
952
1158
  const resolved = [];
@@ -984,7 +1190,7 @@ function createSanitizer(options = {}) {
984
1190
  assertKey(key, keyEncoding);
985
1191
  const contextChars = options.report?.contextChars ?? 0;
986
1192
  if (contextChars < 0) {
987
- throw new chunkEUNFMCKE_cjs.InvalidOptionError("report.contextChars must not be negative.");
1193
+ throw new chunkVELXTD3L_cjs.InvalidOptionError("report.contextChars must not be negative.");
988
1194
  }
989
1195
  const selected = resolveWithAlwaysRedact(options);
990
1196
  const ctx = createRuleContext({
@@ -1028,7 +1234,7 @@ function createSanitizer(options = {}) {
1028
1234
  let charsRead = 0;
1029
1235
  const writeLine = async (line) => {
1030
1236
  if (signal?.aborted === true) {
1031
- throw new chunkEUNFMCKE_cjs.SanitizationAbortedError();
1237
+ throw new chunkVELXTD3L_cjs.SanitizationAbortedError();
1032
1238
  }
1033
1239
  await sink.write(processLine(line, ctx, collector));
1034
1240
  };
@@ -1048,13 +1254,13 @@ function createSanitizer(options = {}) {
1048
1254
  return { key, rules, sanitizeText: sanitizeText2, sanitizeStream: sanitizeStream2 };
1049
1255
  }
1050
1256
  function resolveWithAlwaysRedact(options) {
1051
- const selected = resolveRules(options.rules ?? chunkEUNFMCKE_cjs.builtinRuleIds, options.extraRules);
1257
+ const selected = resolveRules(options.rules ?? chunkVELXTD3L_cjs.builtinRuleIds, options.extraRules);
1052
1258
  const alwaysRule = createAlwaysRedactRule(options.alwaysRedact);
1053
1259
  if (!alwaysRule) {
1054
1260
  return selected;
1055
1261
  }
1056
1262
  if (selected.some((rule) => rule.id === alwaysRule.id)) {
1057
- throw new chunkEUNFMCKE_cjs.InvalidOptionError(
1263
+ throw new chunkVELXTD3L_cjs.InvalidOptionError(
1058
1264
  `alwaysRedact.ruleId "${alwaysRule.id}" collides with an active rule. Choose an identifier that is not in use.`
1059
1265
  );
1060
1266
  }
@@ -1247,15 +1453,15 @@ async function snapToNewline(blob, from, probeBytes, maxProbeBytes) {
1247
1453
  }
1248
1454
  async function planLineAlignedRanges(blob, partCount, options = {}) {
1249
1455
  if (!Number.isInteger(partCount) || partCount < 1) {
1250
- throw new chunkEUNFMCKE_cjs.InvalidOptionError("partCount must be an integer >= 1.");
1456
+ throw new chunkVELXTD3L_cjs.InvalidOptionError("partCount must be an integer >= 1.");
1251
1457
  }
1252
1458
  const probeBytes = options.probeBytes ?? DEFAULT_RANGE_PROBE_BYTES;
1253
1459
  const maxProbeBytes = options.maxProbeBytes ?? DEFAULT_RANGE_MAX_PROBE_BYTES;
1254
1460
  if (!Number.isInteger(probeBytes) || probeBytes < 1) {
1255
- throw new chunkEUNFMCKE_cjs.InvalidOptionError("probeBytes must be an integer >= 1.");
1461
+ throw new chunkVELXTD3L_cjs.InvalidOptionError("probeBytes must be an integer >= 1.");
1256
1462
  }
1257
1463
  if (!Number.isInteger(maxProbeBytes) || maxProbeBytes < probeBytes) {
1258
- throw new chunkEUNFMCKE_cjs.InvalidOptionError("maxProbeBytes must be an integer >= probeBytes.");
1464
+ throw new chunkVELXTD3L_cjs.InvalidOptionError("maxProbeBytes must be an integer >= probeBytes.");
1259
1465
  }
1260
1466
  if (blob.size <= 0) {
1261
1467
  return [];
@@ -1317,5 +1523,5 @@ exports.sanitizeText = sanitizeText;
1317
1523
  exports.toCallbackSink = toCallbackSink;
1318
1524
  exports.toNullSink = toNullSink;
1319
1525
  exports.toStringSink = toStringSink;
1320
- //# sourceMappingURL=chunk-YPHS5NO7.cjs.map
1321
- //# sourceMappingURL=chunk-YPHS5NO7.cjs.map
1526
+ //# sourceMappingURL=chunk-ZCWN2P2R.cjs.map
1527
+ //# sourceMappingURL=chunk-ZCWN2P2R.cjs.map