@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.
- package/CHANGELOG.md +31 -0
- package/README.md +3 -1
- package/{chunk-SGF4CYUV.js → chunk-5UDGYWP3.js} +280 -74
- package/chunk-5UDGYWP3.js.map +1 -0
- package/{chunk-EUNFMCKE.cjs → chunk-VELXTD3L.cjs} +432 -18
- package/chunk-VELXTD3L.cjs.map +1 -0
- package/{chunk-VMPXBCSL.js → chunk-X2QYRBY6.js} +432 -18
- package/chunk-X2QYRBY6.js.map +1 -0
- package/{chunk-YPHS5NO7.cjs → chunk-ZCWN2P2R.cjs} +304 -98
- package/chunk-ZCWN2P2R.cjs.map +1 -0
- package/cli.js +707 -87
- package/cli.js.map +1 -1
- package/{index-BYhIh1ru.d.cts → index-BYEGdnwh.d.cts} +8 -4
- package/{index-BYhIh1ru.d.ts → index-BYEGdnwh.d.ts} +8 -4
- package/index.cjs +25 -25
- package/index.d.cts +2 -2
- package/index.d.ts +2 -2
- package/index.js +2 -2
- package/node.cjs +27 -27
- package/node.d.cts +2 -2
- package/node.d.ts +2 -2
- package/node.js +3 -3
- package/package.json +1 -1
- package/rules.cjs +5 -5
- package/rules.d.cts +1 -1
- package/rules.d.ts +1 -1
- package/rules.js +1 -1
- package/chunk-EUNFMCKE.cjs.map +0 -1
- package/chunk-SGF4CYUV.js.map +0 -1
- package/chunk-VMPXBCSL.js.map +0 -1
- package/chunk-YPHS5NO7.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.1-beta.0] - 2026-10-14
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- JSON field redaction now applies to lines that are not a whole JSON document: pretty-printed
|
|
13
|
+
records, JSON embedded in a text line (`body="{\"password\":\"…\"}"`) and Python dict reprs.
|
|
14
|
+
Values of `"key": value` pairs whose key belongs to a rule are replaced by name, as in
|
|
15
|
+
single-line JSON. Previously, a pretty-printed record leaked `ssn`, `passport_number`, `iban`,
|
|
16
|
+
`cvv`, `diagnosis` and similar fields whose values fail the plain-text checksums.
|
|
17
|
+
- Numeric values of sensitive keys (`"latitude": 48.1486`, `"pan": 4111111111111111`) are replaced
|
|
18
|
+
with a string token instead of being left as is.
|
|
19
|
+
- Array items inherit the key of their array, so `"emails": [...]` and GeoJSON
|
|
20
|
+
`"coordinates": [...]` are redacted.
|
|
21
|
+
- `hosts` rule: the reserved `.test`, `.invalid` and `.localhost` TLDs are recognized.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- JSON key matching also ignores `.` and spaces, and tries a nested field together with its
|
|
26
|
+
parent's name (`user` + `name`, `client` + `ip`, `source` + `address`) before the field's own
|
|
27
|
+
name. Dotted flat keys (`"source.ip"`) resolve the same way.
|
|
28
|
+
- In a pretty-printed record, a field named in `jsonKeys` is now replaced as a whole, the same
|
|
29
|
+
way the single-line form already was. For example, `"User": "CORP\\jdoe"` becomes one `USER`
|
|
30
|
+
token instead of a `HOST` and a `USER` token.
|
|
31
|
+
- Key resolution is memoized per rule set, with a bounded cache.
|
|
32
|
+
- Expanded `jsonKeys` and `jsonKeyContains` for every built-in rule with field names used by ECS,
|
|
33
|
+
AWS CloudTrail, Azure sign-in logs, Windows events, CEF, Datadog and common application logs:
|
|
34
|
+
cloud and API credentials, session and CSRF cookies, card, bank and wallet fields, national
|
|
35
|
+
identifiers (PESEL, rodné číslo, CPF, Aadhaar, РНОКПП and others), clinical fields, phone and
|
|
36
|
+
telephony fields, source, destination and forwarded IPs, workstation, SNI and DNS query hosts,
|
|
37
|
+
mail and actor identities, and street addresses.
|
|
38
|
+
|
|
8
39
|
## [0.2.0-beta.3] - 2026-09-23
|
|
9
40
|
|
|
10
41
|
### Fixed
|
package/README.md
CHANGED
|
@@ -98,7 +98,9 @@ Enabled in this order when you omit `rules`:
|
|
|
98
98
|
|
|
99
99
|
`mask` mode (`<R:…>`) hides the kind of secret. `pseudo` mode keeps a type prefix so you can still read the timeline.
|
|
100
100
|
|
|
101
|
-
JSON lines: if a line parses as a JSON object or array, fields listed in a rule's `jsonKeys` are redacted by name
|
|
101
|
+
JSON lines: if a line parses as a JSON object or array, fields listed in a rule's `jsonKeys` (or whose name contains one of its `jsonKeyContains` fragments) are redacted by name. Matching ignores case, `-`, `_`, `.` and spaces. A nested field is also tried with its parent's name, so `{"user": {"name": …}}` and a flat `"user.name"` both resolve as `userName`, and `source.address` resolves as an IP rather than a street address. String and numeric values are replaced (a number becomes a string token); booleans, `null`, `""` and `"-"` are kept. Array items inherit the key of their array. Other string values still go through the regex pass.
|
|
102
|
+
|
|
103
|
+
Lines that are not a whole JSON document (pretty-printed records, JSON embedded in a text line, JSON escaped inside another JSON string, Python dict reprs) get the same treatment for `"key": value`, `\"key\": \"value\"` and `'key': 'value'` pairs, without the parent-name lookup. A bare number replaced there is wrapped in quotes, so the line stays valid JSON. Set `json: false` to scan everything as plain text.
|
|
102
104
|
|
|
103
105
|
## Custom rules
|
|
104
106
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InvalidKeyError, InvalidOptionError, builtinRuleIds, SanitizationAbortedError, getBuiltinRule, UnknownRuleError, validateRule, countCapturingGroups, InvalidRuleError, deriveGateSource, lineHasAnyChar } from './chunk-
|
|
1
|
+
import { InvalidKeyError, InvalidOptionError, builtinRuleIds, SanitizationAbortedError, getBuiltinRule, UnknownRuleError, validateRule, countCapturingGroups, InvalidRuleError, deriveGateSource, lineHasAnyChar } from './chunk-X2QYRBY6.js';
|
|
2
2
|
|
|
3
3
|
// src/core/constants.ts
|
|
4
4
|
var DEFAULT_PREVIEW_BYTES = 256 * 1024;
|
|
@@ -459,6 +459,181 @@ function createLineSplitter(options = {}) {
|
|
|
459
459
|
return { push, flush };
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
// src/core/jsonKeyMatcher.ts
|
|
463
|
+
var RESOLVE_CACHE_LIMIT = 1e4;
|
|
464
|
+
function normalizeKey2(key) {
|
|
465
|
+
return key.toLowerCase().replace(/[-_.\s]/g, "");
|
|
466
|
+
}
|
|
467
|
+
function shouldPreserveJsonValue(value) {
|
|
468
|
+
return value.length === 0 || value === "-";
|
|
469
|
+
}
|
|
470
|
+
function buildTables(rules) {
|
|
471
|
+
const exact = /* @__PURE__ */ new Map();
|
|
472
|
+
const contains = [];
|
|
473
|
+
for (const rule of rules) {
|
|
474
|
+
for (const key of rule.jsonKeys ?? []) {
|
|
475
|
+
const normalized = normalizeKey2(key);
|
|
476
|
+
if (!exact.has(normalized)) {
|
|
477
|
+
exact.set(normalized, rule);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
for (const fragment of rule.jsonKeyContains ?? []) {
|
|
481
|
+
contains.push({ fragment: normalizeKey2(fragment), rule });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return { exact, contains };
|
|
485
|
+
}
|
|
486
|
+
function lastSegment(key) {
|
|
487
|
+
const dot = key.lastIndexOf(".");
|
|
488
|
+
return dot === -1 ? key : key.slice(dot + 1);
|
|
489
|
+
}
|
|
490
|
+
function resolveUncached(tables, key, parent) {
|
|
491
|
+
const dot = key.lastIndexOf(".");
|
|
492
|
+
const leaf = normalizeKey2(dot === -1 ? key : key.slice(dot + 1));
|
|
493
|
+
const parentRaw = dot === -1 ? parent : key.slice(0, dot);
|
|
494
|
+
if (leaf.length === 0) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
if (parentRaw !== void 0) {
|
|
498
|
+
const scoped = tables.exact.get(normalizeKey2(lastSegment(parentRaw)) + leaf);
|
|
499
|
+
if (scoped) {
|
|
500
|
+
return scoped;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const exact = tables.exact.get(leaf);
|
|
504
|
+
if (exact) {
|
|
505
|
+
return exact;
|
|
506
|
+
}
|
|
507
|
+
for (const entry of tables.contains) {
|
|
508
|
+
if (leaf.includes(entry.fragment)) {
|
|
509
|
+
return entry.rule;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
function buildMatcher(rules) {
|
|
515
|
+
const tables = buildTables(rules);
|
|
516
|
+
const cache = /* @__PURE__ */ new Map();
|
|
517
|
+
return {
|
|
518
|
+
resolve(key, parent) {
|
|
519
|
+
const cacheKey = parent === void 0 ? key : `${parent}\0${key}`;
|
|
520
|
+
let rule = cache.get(cacheKey);
|
|
521
|
+
if (rule === void 0) {
|
|
522
|
+
rule = resolveUncached(tables, key, parent);
|
|
523
|
+
if (cache.size >= RESOLVE_CACHE_LIMIT) {
|
|
524
|
+
cache.clear();
|
|
525
|
+
}
|
|
526
|
+
cache.set(cacheKey, rule);
|
|
527
|
+
}
|
|
528
|
+
return rule ?? void 0;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
var matcherCache = /* @__PURE__ */ new WeakMap();
|
|
533
|
+
function jsonKeyMatcher(rules) {
|
|
534
|
+
const cached = matcherCache.get(rules);
|
|
535
|
+
if (cached) {
|
|
536
|
+
return cached;
|
|
537
|
+
}
|
|
538
|
+
const matcher = buildMatcher(rules);
|
|
539
|
+
matcherCache.set(rules, matcher);
|
|
540
|
+
return matcher;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/core/redactKeyValuePairs.ts
|
|
544
|
+
var NUMBER = "(-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)(?![\\w.:+-])";
|
|
545
|
+
var DOUBLE_QUOTED = new RegExp(
|
|
546
|
+
`(?<![\\\\\\w])"((?:[^"\\\\\\r\\n]|\\\\.){1,128})"\\s*:\\s*(?:"((?:[^"\\\\]|\\\\.)*)"|${NUMBER})`,
|
|
547
|
+
"g"
|
|
548
|
+
);
|
|
549
|
+
var ESCAPED = new RegExp(
|
|
550
|
+
`\\\\"((?:[^"\\\\\\r\\n]|\\\\\\\\){1,128})\\\\"\\s*:\\s*(?:\\\\"((?:\\\\\\\\\\\\"|\\\\\\\\|\\\\[^"\\\\]|[^"\\\\])*)\\\\"|${NUMBER})`,
|
|
551
|
+
"g"
|
|
552
|
+
);
|
|
553
|
+
var SINGLE_QUOTED = new RegExp(
|
|
554
|
+
`(?<![\\\\\\w])'((?:[^'\\\\\\r\\n]|\\\\.){1,128})'\\s*:\\s*(?:'((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|${NUMBER})`,
|
|
555
|
+
"g"
|
|
556
|
+
);
|
|
557
|
+
function decodeJsonString(raw) {
|
|
558
|
+
if (!raw.includes("\\")) {
|
|
559
|
+
return raw;
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
return JSON.parse(`"${raw}"`);
|
|
563
|
+
} catch {
|
|
564
|
+
return raw;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function decodeEscapedJsonString(raw) {
|
|
568
|
+
return decodeJsonString(decodeJsonString(raw));
|
|
569
|
+
}
|
|
570
|
+
function decodePythonString(raw) {
|
|
571
|
+
return raw.includes("\\") ? raw.replace(/\\(['"\\])/g, "$1") : raw;
|
|
572
|
+
}
|
|
573
|
+
function collect(line, regex, rules, decode, numberQuote, out) {
|
|
574
|
+
const matcher = jsonKeyMatcher(rules);
|
|
575
|
+
regex.lastIndex = 0;
|
|
576
|
+
for (let found = regex.exec(line); found !== null; found = regex.exec(line)) {
|
|
577
|
+
const rawKey = found[1];
|
|
578
|
+
let rawValue;
|
|
579
|
+
let quote = "";
|
|
580
|
+
for (let group = 2; group < found.length; group += 1) {
|
|
581
|
+
if (found[group] !== void 0) {
|
|
582
|
+
rawValue = found[group];
|
|
583
|
+
quote = group === found.length - 1 ? numberQuote : "";
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (rawValue === void 0) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
const value = quote === "" ? decode(rawValue) : rawValue;
|
|
591
|
+
if (shouldPreserveJsonValue(value)) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
const rule = matcher.resolve(decode(rawKey));
|
|
595
|
+
if (!rule) {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
const end = found.index + found[0].length - (quote === "" ? 1 : 0);
|
|
599
|
+
const valueEnd = quote === "" && regex === ESCAPED ? end - 1 : end;
|
|
600
|
+
out.push({
|
|
601
|
+
keyStart: found.index,
|
|
602
|
+
match: { start: valueEnd - rawValue.length, end: valueEnd, value, rule, quote }
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function findKeyValueMatches(line, rules) {
|
|
607
|
+
const doubleQuoted = line.includes('":') || line.includes('" :');
|
|
608
|
+
const singleQuoted = line.includes("':") || line.includes("' :");
|
|
609
|
+
if (!doubleQuoted && !singleQuoted) {
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
const candidates = [];
|
|
613
|
+
if (doubleQuoted) {
|
|
614
|
+
collect(line, DOUBLE_QUOTED, rules, decodeJsonString, '"', candidates);
|
|
615
|
+
if (line.includes('\\"')) {
|
|
616
|
+
collect(line, ESCAPED, rules, decodeEscapedJsonString, '\\"', candidates);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (singleQuoted) {
|
|
620
|
+
collect(line, SINGLE_QUOTED, rules, decodePythonString, "'", candidates);
|
|
621
|
+
}
|
|
622
|
+
if (candidates.length === 0) {
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
candidates.sort((left, right) => left.keyStart - right.keyStart);
|
|
626
|
+
const matches = [];
|
|
627
|
+
let floor = 0;
|
|
628
|
+
for (const { keyStart, match } of candidates) {
|
|
629
|
+
if (keyStart >= floor) {
|
|
630
|
+
matches.push(match);
|
|
631
|
+
floor = match.end;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return matches;
|
|
635
|
+
}
|
|
636
|
+
|
|
462
637
|
// src/core/redactLine.ts
|
|
463
638
|
function sliceContext(text, index, length, chars) {
|
|
464
639
|
return {
|
|
@@ -538,11 +713,21 @@ function pickNext(cursors) {
|
|
|
538
713
|
}
|
|
539
714
|
return best;
|
|
540
715
|
}
|
|
716
|
+
function blankOut(line, spans) {
|
|
717
|
+
let out = "";
|
|
718
|
+
let cursor = 0;
|
|
719
|
+
for (const span of spans) {
|
|
720
|
+
out += line.slice(cursor, span.start) + " ".repeat(span.end - span.start);
|
|
721
|
+
cursor = span.end;
|
|
722
|
+
}
|
|
723
|
+
return out + line.slice(cursor);
|
|
724
|
+
}
|
|
541
725
|
function redactLine(line, ctx, options = {}) {
|
|
542
726
|
const { compiled, rulesById, allow, contextChars } = ctx;
|
|
543
727
|
const withSegments = options.withSegments ?? false;
|
|
544
728
|
const withMatches = options.withMatches ?? false;
|
|
545
|
-
|
|
729
|
+
const keyed = ctx.json ? findKeyValueMatches(line, ctx.rules) : [];
|
|
730
|
+
if (compiled.length === 0 && keyed.length === 0) {
|
|
546
731
|
return {
|
|
547
732
|
output: line,
|
|
548
733
|
counts: {},
|
|
@@ -550,18 +735,65 @@ function redactLine(line, ctx, options = {}) {
|
|
|
550
735
|
segments: withSegments ? { before: [{ text: line, changed: false }], after: [{ text: line, changed: false }] } : void 0
|
|
551
736
|
};
|
|
552
737
|
}
|
|
553
|
-
const
|
|
738
|
+
const scan = keyed.length === 0 ? line : blankOut(line, keyed);
|
|
739
|
+
const cursors = openCursors(scan, compiled);
|
|
554
740
|
const counts = {};
|
|
555
741
|
const matches = [];
|
|
556
742
|
const before = [];
|
|
557
743
|
const after = [];
|
|
558
744
|
let output = "";
|
|
559
745
|
let cursor = 0;
|
|
746
|
+
let keyedIndex = 0;
|
|
560
747
|
for (; ; ) {
|
|
561
748
|
const next = pickNext(cursors);
|
|
749
|
+
const pending = keyed[keyedIndex];
|
|
750
|
+
if (pending !== void 0 && (next === null || pending.start <= next.start)) {
|
|
751
|
+
keyedIndex += 1;
|
|
752
|
+
if (!isAllowed(allow, pending.rule.id, pending.value)) {
|
|
753
|
+
const token = buildReplacement(ctx, pending.rule.id, pending.value);
|
|
754
|
+
const raw = line.slice(pending.start, pending.end);
|
|
755
|
+
if (withSegments) {
|
|
756
|
+
if (pending.start > cursor) {
|
|
757
|
+
const gap = line.slice(cursor, pending.start);
|
|
758
|
+
before.push({ text: gap, changed: false });
|
|
759
|
+
after.push({ text: gap, changed: false });
|
|
760
|
+
}
|
|
761
|
+
before.push({ text: raw, changed: true });
|
|
762
|
+
if (pending.quote) {
|
|
763
|
+
after.push({ text: pending.quote, changed: false });
|
|
764
|
+
}
|
|
765
|
+
after.push({ text: token, changed: true });
|
|
766
|
+
if (pending.quote) {
|
|
767
|
+
after.push({ text: pending.quote, changed: false });
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
output += line.slice(cursor, pending.start) + pending.quote + token + pending.quote;
|
|
771
|
+
cursor = pending.end;
|
|
772
|
+
counts[pending.rule.id] = (counts[pending.rule.id] ?? 0) + 1;
|
|
773
|
+
if (withMatches) {
|
|
774
|
+
matches.push({
|
|
775
|
+
ruleId: pending.rule.id,
|
|
776
|
+
original: pending.value,
|
|
777
|
+
replacement: token,
|
|
778
|
+
index: pending.start,
|
|
779
|
+
...contextChars > 0 ? sliceContext(line, pending.start, raw.length, contextChars) : {}
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
for (const candidate of cursors) {
|
|
784
|
+
if (candidate.active && candidate.start < pending.end) {
|
|
785
|
+
advance(candidate, scan, pending.end);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
562
790
|
if (next === null) {
|
|
563
791
|
break;
|
|
564
792
|
}
|
|
793
|
+
if (pending !== void 0 && next.start + next.original.length > pending.start) {
|
|
794
|
+
advance(next, scan, next.start + 1);
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
565
797
|
const { start, original, ruleId } = next;
|
|
566
798
|
const spanEnd = start + Math.max(original.length, 1);
|
|
567
799
|
const rule = rulesById.get(ruleId);
|
|
@@ -592,7 +824,7 @@ function redactLine(line, ctx, options = {}) {
|
|
|
592
824
|
}
|
|
593
825
|
for (const candidate of cursors) {
|
|
594
826
|
if (candidate.active && candidate.start < spanEnd) {
|
|
595
|
-
advance(candidate,
|
|
827
|
+
advance(candidate, scan, spanEnd);
|
|
596
828
|
}
|
|
597
829
|
}
|
|
598
830
|
}
|
|
@@ -616,51 +848,6 @@ function looksLikeJson(line) {
|
|
|
616
848
|
const trimmed = line.trim();
|
|
617
849
|
return trimmed.length > 0 && (trimmed.startsWith("{") || trimmed.startsWith("["));
|
|
618
850
|
}
|
|
619
|
-
function normalizeKey2(key) {
|
|
620
|
-
return key.toLowerCase().replace(/[-_]/g, "");
|
|
621
|
-
}
|
|
622
|
-
function shouldPreserveJsonValue(value) {
|
|
623
|
-
return value.length === 0 || value === "-";
|
|
624
|
-
}
|
|
625
|
-
function buildJsonKeyMatcher(rules) {
|
|
626
|
-
const exact = /* @__PURE__ */ new Map();
|
|
627
|
-
const contains = [];
|
|
628
|
-
for (const rule of rules) {
|
|
629
|
-
for (const key of rule.jsonKeys ?? []) {
|
|
630
|
-
const normalized = normalizeKey2(key);
|
|
631
|
-
if (!exact.has(normalized)) {
|
|
632
|
-
exact.set(normalized, rule);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
for (const fragment of rule.jsonKeyContains ?? []) {
|
|
636
|
-
contains.push({ fragment: normalizeKey2(fragment), rule });
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return { exact, contains };
|
|
640
|
-
}
|
|
641
|
-
function resolveJsonKeyRule(matcher, key) {
|
|
642
|
-
const normalized = normalizeKey2(key);
|
|
643
|
-
const exact = matcher.exact.get(normalized);
|
|
644
|
-
if (exact) {
|
|
645
|
-
return exact;
|
|
646
|
-
}
|
|
647
|
-
for (const entry of matcher.contains) {
|
|
648
|
-
if (normalized.includes(entry.fragment)) {
|
|
649
|
-
return entry.rule;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return void 0;
|
|
653
|
-
}
|
|
654
|
-
var jsonKeyMatcherCache = /* @__PURE__ */ new WeakMap();
|
|
655
|
-
function jsonKeyMatcher(rules) {
|
|
656
|
-
const cached = jsonKeyMatcherCache.get(rules);
|
|
657
|
-
if (cached) {
|
|
658
|
-
return cached;
|
|
659
|
-
}
|
|
660
|
-
const matcher = buildJsonKeyMatcher(rules);
|
|
661
|
-
jsonKeyMatcherCache.set(rules, matcher);
|
|
662
|
-
return matcher;
|
|
663
|
-
}
|
|
664
851
|
function mergeCounts(target, source) {
|
|
665
852
|
for (const [id, count] of Object.entries(source)) {
|
|
666
853
|
target[id] = (target[id] ?? 0) + count;
|
|
@@ -687,6 +874,15 @@ function highlightReplacements(text) {
|
|
|
687
874
|
function jsonStringContent(value) {
|
|
688
875
|
return JSON.stringify(value).slice(1, -1);
|
|
689
876
|
}
|
|
877
|
+
function locateJsonNumber(text, value, from = 0) {
|
|
878
|
+
const re = new RegExp(`:\\s*(${escapeRegExp(value)})(?![\\d.eE])`, "g");
|
|
879
|
+
re.lastIndex = from;
|
|
880
|
+
const match = re.exec(text);
|
|
881
|
+
if (match === null) {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
return { index: match.index + match[0].length - value.length, length: value.length };
|
|
885
|
+
}
|
|
690
886
|
function locateQuotedJsonValue(text, value, from = 0) {
|
|
691
887
|
const encoded = jsonStringContent(value);
|
|
692
888
|
const quotedAt = text.indexOf(`"${encoded}"`, from);
|
|
@@ -696,6 +892,9 @@ function locateQuotedJsonValue(text, value, from = 0) {
|
|
|
696
892
|
return { index: quotedAt + 1, length: encoded.length };
|
|
697
893
|
}
|
|
698
894
|
function findOriginal(text, original, from) {
|
|
895
|
+
if (original.number === true) {
|
|
896
|
+
return locateJsonNumber(text, original.value, from);
|
|
897
|
+
}
|
|
699
898
|
const encoded = jsonStringContent(original.value);
|
|
700
899
|
if (original.wholeValue) {
|
|
701
900
|
const quoted = `"${encoded}"`;
|
|
@@ -778,32 +977,39 @@ function redactJsonLine(line, ctx, options = {}) {
|
|
|
778
977
|
}
|
|
779
978
|
return { contextBefore: "", contextAfter: "" };
|
|
780
979
|
};
|
|
781
|
-
const
|
|
980
|
+
const redactField = (rule, value, number) => {
|
|
981
|
+
if (isAllowed(ctx.allow, rule.id, value)) {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
const replacement = buildReplacement(ctx, rule.id, value);
|
|
985
|
+
counts[rule.id] = (counts[rule.id] ?? 0) + 1;
|
|
986
|
+
if (withSegments) {
|
|
987
|
+
originals.push({ value, wholeValue: true, number });
|
|
988
|
+
}
|
|
989
|
+
if (withMatches) {
|
|
990
|
+
const located = number ? locateJsonNumber(content, value) : locateQuotedJsonValue(content, value);
|
|
991
|
+
matches.push({
|
|
992
|
+
ruleId: rule.id,
|
|
993
|
+
original: value,
|
|
994
|
+
replacement,
|
|
995
|
+
index: located?.index ?? 0,
|
|
996
|
+
...located && ctx.contextChars > 0 ? sliceContext(content, located.index, located.length, ctx.contextChars) : ctx.contextChars > 0 ? { contextBefore: "", contextAfter: "" } : {}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
return replacement;
|
|
1000
|
+
};
|
|
1001
|
+
const redactValue = (value, key, parent) => {
|
|
1002
|
+
if (typeof value === "number") {
|
|
1003
|
+
const fieldRule = key === void 0 || !Number.isFinite(value) ? void 0 : keyMatcher.resolve(key, parent);
|
|
1004
|
+
return fieldRule ? redactField(fieldRule, String(value), true) ?? value : value;
|
|
1005
|
+
}
|
|
782
1006
|
if (typeof value === "string") {
|
|
783
1007
|
if (key !== void 0 && shouldPreserveJsonValue(value)) {
|
|
784
1008
|
return value;
|
|
785
1009
|
}
|
|
786
|
-
const fieldRule = key === void 0 ? void 0 :
|
|
1010
|
+
const fieldRule = key === void 0 ? void 0 : keyMatcher.resolve(key, parent);
|
|
787
1011
|
if (fieldRule) {
|
|
788
|
-
|
|
789
|
-
return value;
|
|
790
|
-
}
|
|
791
|
-
const replacement = buildReplacement(ctx, fieldRule.id, value);
|
|
792
|
-
counts[fieldRule.id] = (counts[fieldRule.id] ?? 0) + 1;
|
|
793
|
-
if (withSegments) {
|
|
794
|
-
originals.push({ value, wholeValue: true });
|
|
795
|
-
}
|
|
796
|
-
if (withMatches) {
|
|
797
|
-
const located = locateQuotedJsonValue(content, value);
|
|
798
|
-
matches.push({
|
|
799
|
-
ruleId: fieldRule.id,
|
|
800
|
-
original: value,
|
|
801
|
-
replacement,
|
|
802
|
-
index: located?.index ?? 0,
|
|
803
|
-
...located && ctx.contextChars > 0 ? sliceContext(content, located.index, located.length, ctx.contextChars) : ctx.contextChars > 0 ? { contextBefore: "", contextAfter: "" } : {}
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
return replacement;
|
|
1012
|
+
return redactField(fieldRule, value, false) ?? value;
|
|
807
1013
|
}
|
|
808
1014
|
const result = redactLine(value, ctx, { withMatches: collectOriginals });
|
|
809
1015
|
mergeCounts(counts, result.counts);
|
|
@@ -820,12 +1026,12 @@ function redactJsonLine(line, ctx, options = {}) {
|
|
|
820
1026
|
return result.output;
|
|
821
1027
|
}
|
|
822
1028
|
if (Array.isArray(value)) {
|
|
823
|
-
return value.map((item) => redactValue(item));
|
|
1029
|
+
return value.map((item) => redactValue(item, key, parent));
|
|
824
1030
|
}
|
|
825
1031
|
if (value !== null && typeof value === "object") {
|
|
826
1032
|
const out = {};
|
|
827
1033
|
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
828
|
-
out[entryKey] = redactValue(entryValue, entryKey);
|
|
1034
|
+
out[entryKey] = redactValue(entryValue, entryKey, key);
|
|
829
1035
|
}
|
|
830
1036
|
return out;
|
|
831
1037
|
}
|
|
@@ -1304,5 +1510,5 @@ function toNullSink() {
|
|
|
1304
1510
|
}
|
|
1305
1511
|
|
|
1306
1512
|
export { createSanitizer, fromBlob, fromString, fromWebStream, generateKey, mergeReports, planLineAlignedRanges, sanitizeStream, sanitizeText, toCallbackSink, toNullSink, toStringSink };
|
|
1307
|
-
//# sourceMappingURL=chunk-
|
|
1308
|
-
//# sourceMappingURL=chunk-
|
|
1513
|
+
//# sourceMappingURL=chunk-5UDGYWP3.js.map
|
|
1514
|
+
//# sourceMappingURL=chunk-5UDGYWP3.js.map
|