n8n-nodes-redactor 4.3.0 → 5.0.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.
|
@@ -1040,7 +1040,7 @@ class Redactor {
|
|
|
1040
1040
|
reservedTokens: (0, engine_1.collectTokenLike)(items.map((it) => it.json)),
|
|
1041
1041
|
keyOrigins: new WeakMap(),
|
|
1042
1042
|
inputText: (0, engine_1.collectInputText)(items.map((it) => it.json)),
|
|
1043
|
-
lockedSpans: new
|
|
1043
|
+
lockedSpans: new WeakMap(),
|
|
1044
1044
|
};
|
|
1045
1045
|
const allHits = [];
|
|
1046
1046
|
const returnData = [];
|
|
@@ -57,7 +57,7 @@ function isAllowListed(value, allowList) {
|
|
|
57
57
|
case 'contains':
|
|
58
58
|
return value.includes(entry.value);
|
|
59
59
|
case 'regex':
|
|
60
|
-
if (!isSafeRegex(entry.value))
|
|
60
|
+
if (!isSafeRegex(entry.value) || value.length > USER_REGEX_MAX_INPUT)
|
|
61
61
|
return false;
|
|
62
62
|
return new RegExp(entry.value, 'i').test(value);
|
|
63
63
|
default:
|
|
@@ -414,7 +414,7 @@ function getSemanticLabel(fieldKey, siblingKeys) {
|
|
|
414
414
|
return null;
|
|
415
415
|
}
|
|
416
416
|
function redactSemantic(raw, ctx, vault, sessionId, hits, itemIndex, path, display, siblingKeys) {
|
|
417
|
-
var _a
|
|
417
|
+
var _a;
|
|
418
418
|
const semantic = getSemanticLabel(path, siblingKeys);
|
|
419
419
|
if (!semantic || !semantic.label || raw.trim().length === 0)
|
|
420
420
|
return null;
|
|
@@ -424,8 +424,7 @@ function redactSemantic(raw, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
424
424
|
if (semConf < ((_a = ctx.confidenceThreshold) !== null && _a !== void 0 ? _a : 0))
|
|
425
425
|
return null;
|
|
426
426
|
const replacement = applyRedaction(raw, { label: semantic.label, category: semantic.category }, ctx, vault, sessionId);
|
|
427
|
-
|
|
428
|
-
(_b = ctx.lockedSpans) === null || _b === void 0 ? void 0 : _b.set(lockKey(itemIndex, display), [[0, replacement.length]]);
|
|
427
|
+
ctx.lastLocked = replacement ? [[0, replacement.length]] : undefined;
|
|
429
428
|
hits.push({
|
|
430
429
|
token: replacement, original: '***',
|
|
431
430
|
patternName: `semantic_${semantic.label}`,
|
|
@@ -444,14 +443,13 @@ function numericMapping(out, text, ctx, vault, sessionId) {
|
|
|
444
443
|
existing.numeric = true;
|
|
445
444
|
return out;
|
|
446
445
|
}
|
|
447
|
-
const token =
|
|
446
|
+
const token = freshToken('NUMBER', vault, sessionId, ctx.reservedTokens);
|
|
448
447
|
vault.addEntry(sessionId, {
|
|
449
448
|
token, original: text, patternLabel: 'NUMBER', category: 'other', createdAt: new Date().toISOString(), numeric: true,
|
|
450
449
|
});
|
|
451
450
|
return token;
|
|
452
451
|
}
|
|
453
452
|
function redactNumber(value, ctx, vault, sessionId, hits, itemIndex, path, display, siblingKeys) {
|
|
454
|
-
var _a;
|
|
455
453
|
const text = String(value);
|
|
456
454
|
const numCtx = { ...ctx, dedup: false, numeric: true };
|
|
457
455
|
let out = null;
|
|
@@ -461,10 +459,12 @@ function redactNumber(value, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
461
459
|
out = redactText(text, numCtx, vault, sessionId, hits, itemIndex, display);
|
|
462
460
|
if (out === null || out === text)
|
|
463
461
|
return value;
|
|
464
|
-
if (ctx.mode !== 'token' && ctx.mode !== 'pseudonymize')
|
|
462
|
+
if (ctx.mode !== 'token' && ctx.mode !== 'pseudonymize') {
|
|
463
|
+
ctx.lastLocked = [[0, out.length]];
|
|
465
464
|
return out;
|
|
465
|
+
}
|
|
466
466
|
const mapped = numericMapping(out, text, ctx, vault, sessionId);
|
|
467
|
-
|
|
467
|
+
ctx.lastLocked = [[0, mapped.length]];
|
|
468
468
|
return mapped;
|
|
469
469
|
}
|
|
470
470
|
function redactKey(key, taken, sourceKeys, ctx, vault, sessionId, itemIndex, keyHits) {
|
|
@@ -502,7 +502,7 @@ function redactObject(value, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
502
502
|
const childDisplay = display ? `${display}.${outKey}` : outKey;
|
|
503
503
|
for (const h of keyHits)
|
|
504
504
|
hits.push({ ...h, field: childDisplay });
|
|
505
|
-
obj[outKey] = redactValue(v, ctx, vault, sessionId, hits, itemIndex, fieldPath, siblingKeys, childDisplay);
|
|
505
|
+
obj[outKey] = lockChild(ctx, obj, outKey, () => redactValue(v, ctx, vault, sessionId, hits, itemIndex, fieldPath, siblingKeys, childDisplay));
|
|
506
506
|
}
|
|
507
507
|
(_a = ctx.keyOrigins) === null || _a === void 0 ? void 0 : _a.set(obj, origins);
|
|
508
508
|
return obj;
|
|
@@ -525,7 +525,11 @@ function redactValue(value, ctx, vault, sessionId, hits, itemIndex, currentPath
|
|
|
525
525
|
return redactNumber(value, ctx, vault, sessionId, hits, itemIndex, currentPath, displayPath, siblingKeys);
|
|
526
526
|
}
|
|
527
527
|
if (Array.isArray(value)) {
|
|
528
|
-
|
|
528
|
+
const out = [];
|
|
529
|
+
value.forEach((v, i) => {
|
|
530
|
+
out.push(lockChild(ctx, out, String(i), () => redactValue(v, ctx, vault, sessionId, hits, itemIndex, `${currentPath}[${i}]`, undefined, `${displayPath}[${i}]`)));
|
|
531
|
+
});
|
|
532
|
+
return out;
|
|
529
533
|
}
|
|
530
534
|
if (value !== null && typeof value === 'object') {
|
|
531
535
|
return redactObject(value, ctx, vault, sessionId, hits, itemIndex, currentPath, displayPath);
|
|
@@ -533,18 +537,12 @@ function redactValue(value, ctx, vault, sessionId, hits, itemIndex, currentPath
|
|
|
533
537
|
return value;
|
|
534
538
|
}
|
|
535
539
|
const escapeRe = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
536
|
-
const
|
|
537
|
-
function
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const end = cut > at + size / 2 ? cut + 1 : at + size;
|
|
543
|
-
out.push({ text: seg.text.slice(at, end), locked: false, src: seg.src + at });
|
|
544
|
-
at = end;
|
|
545
|
-
}
|
|
546
|
-
out.push({ text: seg.text.slice(at), locked: false, src: seg.src + at });
|
|
547
|
-
return out;
|
|
540
|
+
const USER_REGEX_MAX_INPUT = 20000;
|
|
541
|
+
function assertUserRegexInput(text, what) {
|
|
542
|
+
if (text.length <= USER_REGEX_MAX_INPUT)
|
|
543
|
+
return;
|
|
544
|
+
throw new Error(`${what} cannot scan a value of ${text.length} characters, the limit is ${USER_REGEX_MAX_INPUT}. ` +
|
|
545
|
+
'Exclude the field with field rules, or remove the regex.');
|
|
548
546
|
}
|
|
549
547
|
function replaceInFree(seg, re, fn, into) {
|
|
550
548
|
var _a;
|
|
@@ -564,7 +562,7 @@ function replaceInFree(seg, re, fn, into) {
|
|
|
564
562
|
if (last < seg.text.length)
|
|
565
563
|
into.push({ text: seg.text.slice(last), locked: false, src: seg.src + last });
|
|
566
564
|
}
|
|
567
|
-
function replaceFree(pass, re, fn
|
|
565
|
+
function replaceFree(pass, re, fn) {
|
|
568
566
|
const flags = re.flags.includes('g') ? re.flags : `${re.flags}g`;
|
|
569
567
|
const global = new RegExp(re.source, flags);
|
|
570
568
|
const next = [];
|
|
@@ -572,8 +570,7 @@ function replaceFree(pass, re, fn, window) {
|
|
|
572
570
|
if (seg.locked)
|
|
573
571
|
next.push(seg);
|
|
574
572
|
else
|
|
575
|
-
|
|
576
|
-
replaceInFree(piece, global, fn, next);
|
|
573
|
+
replaceInFree(seg, global, fn, next);
|
|
577
574
|
}
|
|
578
575
|
pass.segs = next;
|
|
579
576
|
}
|
|
@@ -590,16 +587,17 @@ function denyListRegex(entry) {
|
|
|
590
587
|
function denyListStage(pass) {
|
|
591
588
|
var _a;
|
|
592
589
|
for (const entry of (_a = pass.ctx.denyList) !== null && _a !== void 0 ? _a : []) {
|
|
590
|
+
if (entry.type === 'regex')
|
|
591
|
+
assertUserRegexInput(pass.source, `Deny list regex "${entry.value}"`);
|
|
593
592
|
try {
|
|
594
593
|
const re = denyListRegex(entry);
|
|
595
594
|
if (!re)
|
|
596
595
|
continue;
|
|
597
|
-
const window = entry.type === 'regex' ? USER_REGEX_WINDOW : undefined;
|
|
598
596
|
replaceFree(pass, re, (match) => TOKEN_RE.test(match)
|
|
599
597
|
? null
|
|
600
598
|
: redactMatch(pass, match, { label: 'DENY_LIST', category: 'other' }, {
|
|
601
599
|
patternName: 'denyList', patternLabel: 'DENY_LIST', category: 'other', confidence: context_1.DENY_LIST_CONFIDENCE,
|
|
602
|
-
})
|
|
600
|
+
}));
|
|
603
601
|
}
|
|
604
602
|
catch { }
|
|
605
603
|
}
|
|
@@ -624,27 +622,25 @@ function builtInStage(pass) {
|
|
|
624
622
|
}
|
|
625
623
|
}
|
|
626
624
|
function freeNames(pass) {
|
|
627
|
-
const names = new
|
|
625
|
+
const names = new Set();
|
|
628
626
|
for (const seg of pass.segs) {
|
|
629
|
-
if (seg.locked)
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (!names.has(nm.name))
|
|
633
|
-
names.set(nm.name, seg.src + nm.start);
|
|
627
|
+
if (!seg.locked)
|
|
628
|
+
for (const nm of (0, names_1.detectNamesInText)(seg.text))
|
|
629
|
+
names.add(nm.name);
|
|
634
630
|
}
|
|
635
631
|
return names;
|
|
636
632
|
}
|
|
637
633
|
function nameStage(pass) {
|
|
638
634
|
if (pass.ctx.skipSemantic)
|
|
639
635
|
return;
|
|
640
|
-
for (const
|
|
636
|
+
for (const name of freeNames(pass)) {
|
|
641
637
|
if ((name.includes('[') && name.includes(']')) || isAllowListed(name, pass.ctx.allowList))
|
|
642
638
|
continue;
|
|
643
|
-
const confidence = (0, context_1.hasContextWords)(pass.source, start, start + name.length, names_1.NAME_CONTEXT_WORDS) ? 0.8 : 0.65;
|
|
644
|
-
if (confidence < pass.threshold)
|
|
645
|
-
continue;
|
|
646
639
|
let replacement;
|
|
647
|
-
replaceFree(pass, new RegExp(escapeRe(name), 'g'), (match) => {
|
|
640
|
+
replaceFree(pass, new RegExp(escapeRe(name), 'g'), (match, start) => {
|
|
641
|
+
const confidence = (0, context_1.hasContextWords)(pass.source, start, start + match.length, names_1.NAME_CONTEXT_WORDS) ? 0.8 : 0.65;
|
|
642
|
+
if (confidence < pass.threshold)
|
|
643
|
+
return null;
|
|
648
644
|
replacement !== null && replacement !== void 0 ? replacement : (replacement = redactMatch(pass, match, { label: 'PERSON_NAME', category: 'identity' }, {
|
|
649
645
|
patternName: 'personNameDict', patternLabel: 'PERSON_NAME', category: 'identity', confidence,
|
|
650
646
|
}));
|
|
@@ -659,20 +655,19 @@ function customStage(pass) {
|
|
|
659
655
|
if (!cp.label || !cp.regex || !isSafeRegex(cp.regex))
|
|
660
656
|
continue;
|
|
661
657
|
const pattern = { label: cp.label.toUpperCase(), category: cp.category || 'identity' };
|
|
658
|
+
assertUserRegexInput(pass.source, `Custom pattern "${cp.label}"`);
|
|
662
659
|
try {
|
|
663
660
|
replaceFree(pass, new RegExp(cp.regex, 'g'), (match) => TOKEN_RE.test(match) || isAllowListed(match, pass.ctx.allowList)
|
|
664
661
|
? null
|
|
665
662
|
: redactMatch(pass, match, pattern, {
|
|
666
663
|
patternName: `custom_${cp.label}`, patternLabel: pattern.label, category: pattern.category,
|
|
667
664
|
confidence: context_1.CUSTOM_PATTERN_CONFIDENCE,
|
|
668
|
-
})
|
|
665
|
+
}));
|
|
669
666
|
}
|
|
670
667
|
catch { }
|
|
671
668
|
}
|
|
672
669
|
}
|
|
673
|
-
function recordLocked(ctx,
|
|
674
|
-
if (!ctx.lockedSpans || fieldPath === '')
|
|
675
|
-
return;
|
|
670
|
+
function recordLocked(ctx, segs) {
|
|
676
671
|
const spans = [];
|
|
677
672
|
let at = 0;
|
|
678
673
|
for (const seg of segs) {
|
|
@@ -680,10 +675,20 @@ function recordLocked(ctx, itemIndex, fieldPath, segs) {
|
|
|
680
675
|
spans.push([at, at + seg.text.length]);
|
|
681
676
|
at += seg.text.length;
|
|
682
677
|
}
|
|
683
|
-
|
|
684
|
-
|
|
678
|
+
ctx.lastLocked = spans.length > 0 ? spans : undefined;
|
|
679
|
+
}
|
|
680
|
+
function lockChild(ctx, container, key, compute) {
|
|
681
|
+
var _a;
|
|
682
|
+
ctx.lastLocked = undefined;
|
|
683
|
+
const out = compute();
|
|
684
|
+
if (ctx.lastLocked && ctx.lockedSpans && typeof out === 'string') {
|
|
685
|
+
const byKey = (_a = ctx.lockedSpans.get(container)) !== null && _a !== void 0 ? _a : new Map();
|
|
686
|
+
byKey.set(key, ctx.lastLocked);
|
|
687
|
+
ctx.lockedSpans.set(container, byKey);
|
|
688
|
+
}
|
|
689
|
+
ctx.lastLocked = undefined;
|
|
690
|
+
return out;
|
|
685
691
|
}
|
|
686
|
-
const lockKey = (itemIndex, fieldPath) => `${itemIndex}\u0000${fieldPath}`;
|
|
687
692
|
function redactText(text, ctx, vault, sessionId, hits, itemIndex, fieldPath) {
|
|
688
693
|
var _a;
|
|
689
694
|
const pass = {
|
|
@@ -694,7 +699,7 @@ function redactText(text, ctx, vault, sessionId, hits, itemIndex, fieldPath) {
|
|
|
694
699
|
builtInStage(pass);
|
|
695
700
|
nameStage(pass);
|
|
696
701
|
customStage(pass);
|
|
697
|
-
recordLocked(ctx,
|
|
702
|
+
recordLocked(ctx, pass.segs);
|
|
698
703
|
return pass.segs.map((s) => s.text).join('');
|
|
699
704
|
}
|
|
700
705
|
function makeRestorer(entries) {
|
|
@@ -745,18 +750,17 @@ function resolveOverlaps(spans) {
|
|
|
745
750
|
}
|
|
746
751
|
return kept.sort((a, b) => b.start - a.start);
|
|
747
752
|
}
|
|
748
|
-
function presidioSpans(value, entities, ctx,
|
|
749
|
-
var _a
|
|
753
|
+
function presidioSpans(value, entities, ctx, locked) {
|
|
754
|
+
var _a;
|
|
750
755
|
const threshold = (_a = ctx.confidenceThreshold) !== null && _a !== void 0 ? _a : 0;
|
|
751
|
-
const locked = (_c = (_b = ctx.lockedSpans) === null || _b === void 0 ? void 0 : _b.get(key)) !== null && _c !== void 0 ? _c : [];
|
|
752
756
|
const free = (s) => !locked.some(([start, end]) => s.start < end && start < s.end);
|
|
753
757
|
return resolveOverlaps(toUtf16Spans(value, entities).filter((s) => s.score >= threshold && free(s)));
|
|
754
758
|
}
|
|
755
|
-
async function presidioRedactString(value, ctx, vault, sessionId, hits, itemIndex, path) {
|
|
759
|
+
async function presidioRedactString(value, ctx, vault, sessionId, hits, itemIndex, path, locked) {
|
|
756
760
|
if (value.trim().length === 0 || TOKEN_RE.test(value))
|
|
757
761
|
return value;
|
|
758
762
|
const entities = await (0, presidio_1.analyzeWithPresidio)(ctx.presidioUrl, value, ctx.presidioLanguage || 'en', 0.4, 10000, ctx.presidioRequest);
|
|
759
|
-
const spans = presidioSpans(value, entities, ctx,
|
|
763
|
+
const spans = presidioSpans(value, entities, ctx, locked);
|
|
760
764
|
let text = value;
|
|
761
765
|
for (const span of spans) {
|
|
762
766
|
const matchedText = value.slice(span.start, span.end);
|
|
@@ -776,13 +780,13 @@ async function presidioRedactString(value, ctx, vault, sessionId, hits, itemInde
|
|
|
776
780
|
}
|
|
777
781
|
return text;
|
|
778
782
|
}
|
|
779
|
-
async function presidioWalk(value, ctx, vault, sessionId, hits, itemIndex, path, policyPath) {
|
|
780
|
-
var _a, _b;
|
|
783
|
+
async function presidioWalk(value, ctx, vault, sessionId, hits, itemIndex, path, policyPath, locked = []) {
|
|
784
|
+
var _a, _b, _c, _d, _e, _f;
|
|
781
785
|
if (typeof value === 'string') {
|
|
782
786
|
if (!shouldProcessField(policyPath, ctx.fieldMode, ctx.fieldRules))
|
|
783
787
|
return value;
|
|
784
788
|
try {
|
|
785
|
-
return await presidioRedactString(value, ctx, vault, sessionId, hits, itemIndex, path);
|
|
789
|
+
return await presidioRedactString(value, ctx, vault, sessionId, hits, itemIndex, path, locked);
|
|
786
790
|
}
|
|
787
791
|
catch {
|
|
788
792
|
return value;
|
|
@@ -791,18 +795,20 @@ async function presidioWalk(value, ctx, vault, sessionId, hits, itemIndex, path,
|
|
|
791
795
|
if (Array.isArray(value)) {
|
|
792
796
|
const out = [];
|
|
793
797
|
for (let i = 0; i < value.length; i++) {
|
|
794
|
-
|
|
798
|
+
const lock = (_b = (_a = ctx.lockedSpans) === null || _a === void 0 ? void 0 : _a.get(value)) === null || _b === void 0 ? void 0 : _b.get(String(i));
|
|
799
|
+
out.push(await presidioWalk(value[i], ctx, vault, sessionId, hits, itemIndex, `${path}[${i}]`, `${policyPath}[${i}]`, lock));
|
|
795
800
|
}
|
|
796
801
|
return out;
|
|
797
802
|
}
|
|
798
803
|
if (value !== null && typeof value === 'object') {
|
|
799
|
-
const origins = (
|
|
804
|
+
const origins = (_c = ctx.keyOrigins) === null || _c === void 0 ? void 0 : _c.get(value);
|
|
800
805
|
const out = {};
|
|
801
806
|
for (const [k, v] of Object.entries(value)) {
|
|
802
|
-
const original = (
|
|
807
|
+
const original = (_d = origins === null || origins === void 0 ? void 0 : origins.get(k)) !== null && _d !== void 0 ? _d : k;
|
|
803
808
|
const child = path ? `${path}.${k}` : k;
|
|
804
809
|
const childPolicy = policyPath ? `${policyPath}.${original}` : original;
|
|
805
|
-
|
|
810
|
+
const lock = (_f = (_e = ctx.lockedSpans) === null || _e === void 0 ? void 0 : _e.get(value)) === null || _f === void 0 ? void 0 : _f.get(k);
|
|
811
|
+
out[k] = await presidioWalk(v, ctx, vault, sessionId, hits, itemIndex, child, childPolicy, lock);
|
|
806
812
|
}
|
|
807
813
|
return out;
|
|
808
814
|
}
|
|
@@ -3,19 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.emptySet = void 0;
|
|
4
4
|
exports.union = union;
|
|
5
5
|
exports.overlaps = overlaps;
|
|
6
|
+
exports.readEscape = readEscape;
|
|
6
7
|
exports.atomCharset = atomCharset;
|
|
7
8
|
const emptySet = () => ({ ascii: new Array(128).fill(false), other: false });
|
|
8
9
|
exports.emptySet = emptySet;
|
|
9
10
|
const fullSet = () => ({ ascii: new Array(128).fill(true), other: true });
|
|
10
11
|
function addCode(set, code) {
|
|
11
|
-
if (code >= 128)
|
|
12
|
+
if (code >= 128)
|
|
12
13
|
set.other = true;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
set.ascii[code] = true;
|
|
16
|
-
const ch = String.fromCharCode(code);
|
|
17
|
-
for (const folded of [ch.toLowerCase(), ch.toUpperCase()])
|
|
18
|
-
set.ascii[folded.charCodeAt(0)] = true;
|
|
14
|
+
else
|
|
15
|
+
set.ascii[code] = true;
|
|
19
16
|
}
|
|
20
17
|
function addRange(set, from, to) {
|
|
21
18
|
for (let c = from; c <= Math.min(to, 127); c++)
|
|
@@ -32,11 +29,18 @@ function overlaps(a, b) {
|
|
|
32
29
|
function complement(set) {
|
|
33
30
|
return { ascii: set.ascii.map((v) => !v), other: true };
|
|
34
31
|
}
|
|
32
|
+
function fold(set) {
|
|
33
|
+
const ascii = [...set.ascii];
|
|
34
|
+
for (let c = 65; c <= 90; c++)
|
|
35
|
+
ascii[c] = ascii[c + 32] = set.ascii[c] || set.ascii[c + 32];
|
|
36
|
+
return { ascii, other: set.other };
|
|
37
|
+
}
|
|
35
38
|
const CLASS_ESCAPES = {
|
|
36
39
|
d: (s) => addRange(s, 48, 57),
|
|
37
40
|
w: (s) => {
|
|
38
41
|
addRange(s, 48, 57);
|
|
39
42
|
addRange(s, 65, 90);
|
|
43
|
+
addRange(s, 97, 122);
|
|
40
44
|
addCode(s, 95);
|
|
41
45
|
},
|
|
42
46
|
s: (s) => {
|
|
@@ -45,26 +49,42 @@ const CLASS_ESCAPES = {
|
|
|
45
49
|
s.other = true;
|
|
46
50
|
},
|
|
47
51
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return
|
|
52
|
+
const HEX = /^[0-9a-fA-F]+$/;
|
|
53
|
+
function readEscape(src, i) {
|
|
54
|
+
var _a, _b;
|
|
55
|
+
const e = (_a = src[i + 1]) !== null && _a !== void 0 ? _a : '';
|
|
56
|
+
const hex = (n) => (HEX.test(src.slice(i + 2, i + 2 + n)) && src.length >= i + 2 + n ? n : 0);
|
|
57
|
+
if (e === 'x' && hex(2))
|
|
58
|
+
return { length: 4, code: parseInt(src.slice(i + 2, i + 4), 16) };
|
|
59
|
+
if (e === 'u' && hex(4))
|
|
60
|
+
return { length: 6, code: parseInt(src.slice(i + 2, i + 6), 16) };
|
|
61
|
+
if (e === 'c' && /[A-Za-z]/.test((_b = src[i + 2]) !== null && _b !== void 0 ? _b : ''))
|
|
62
|
+
return { length: 3, code: src.charCodeAt(i + 2) % 32 };
|
|
63
|
+
if (e === '0')
|
|
64
|
+
return { length: 2, code: 0 };
|
|
65
|
+
if (/[a-zA-Z0-9]/.test(e))
|
|
66
|
+
return { length: 2, code: null };
|
|
67
|
+
return { length: 2, code: e.charCodeAt(0) };
|
|
68
|
+
}
|
|
69
|
+
function escapeSet(src, i) {
|
|
70
|
+
var _a;
|
|
71
|
+
const { code } = readEscape(src, i);
|
|
72
|
+
const set = (0, exports.emptySet)();
|
|
73
|
+
if (code !== null) {
|
|
74
|
+
addCode(set, code);
|
|
75
|
+
return set;
|
|
55
76
|
}
|
|
56
|
-
|
|
77
|
+
const letter = (_a = src[i + 1]) !== null && _a !== void 0 ? _a : '';
|
|
78
|
+
const fill = CLASS_ESCAPES[letter.toLowerCase()];
|
|
79
|
+
if (!fill)
|
|
57
80
|
return fullSet();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return set;
|
|
81
|
+
fill(set);
|
|
82
|
+
return letter === letter.toLowerCase() ? set : complement(set);
|
|
61
83
|
}
|
|
62
84
|
function classMember(body, j) {
|
|
63
|
-
var _a, _b;
|
|
64
85
|
if (body[j] === '\\') {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
return { set, next: j + 2, code: single };
|
|
86
|
+
const { length, code } = readEscape(body, j);
|
|
87
|
+
return { set: escapeSet(body, j), next: j + length, code };
|
|
68
88
|
}
|
|
69
89
|
const set = (0, exports.emptySet)();
|
|
70
90
|
addCode(set, body.charCodeAt(j));
|
|
@@ -90,13 +110,13 @@ function classSet(body) {
|
|
|
90
110
|
}
|
|
91
111
|
return negated ? complement(set) : set;
|
|
92
112
|
}
|
|
93
|
-
function
|
|
94
|
-
var _a
|
|
113
|
+
function rawCharset(src, start, end) {
|
|
114
|
+
var _a;
|
|
95
115
|
const c = src[start];
|
|
96
116
|
if (c === '^' || c === '$')
|
|
97
117
|
return null;
|
|
98
118
|
if (c === '\\')
|
|
99
|
-
return /[bB]/.test((_a = src[start + 1]) !== null && _a !== void 0 ? _a : '') ? null : escapeSet(
|
|
119
|
+
return /[bB]/.test((_a = src[start + 1]) !== null && _a !== void 0 ? _a : '') ? null : escapeSet(src, start);
|
|
100
120
|
if (c === '[')
|
|
101
121
|
return classSet(src.slice(start + 1, end - 1));
|
|
102
122
|
if (c === '.')
|
|
@@ -105,3 +125,7 @@ function atomCharset(src, start, end) {
|
|
|
105
125
|
addCode(set, src.charCodeAt(start));
|
|
106
126
|
return set;
|
|
107
127
|
}
|
|
128
|
+
function atomCharset(src, start, end) {
|
|
129
|
+
const set = rawCharset(src, start, end);
|
|
130
|
+
return set && fold(set);
|
|
131
|
+
}
|
|
@@ -5,7 +5,13 @@ const regexCharset_1 = require("./regexCharset");
|
|
|
5
5
|
const MAX_REGEX_LENGTH = 500;
|
|
6
6
|
const newFrame = (zeroWidth = false) => ({
|
|
7
7
|
hasVariableQuantifier: false, hasAlternation: false, run: null, chars: (0, regexCharset_1.emptySet)(), zeroWidth,
|
|
8
|
+
branchConsumes: false, allConsume: true, nullable: false,
|
|
8
9
|
});
|
|
10
|
+
function endBranch(frame) {
|
|
11
|
+
frame.allConsume && (frame.allConsume = frame.branchConsumes);
|
|
12
|
+
frame.branchConsumes = false;
|
|
13
|
+
frame.nullable = !frame.allConsume;
|
|
14
|
+
}
|
|
9
15
|
function readBraces(src, i) {
|
|
10
16
|
const m = /^\{(\d+)(,(\d*))?\}/.exec(src.slice(i));
|
|
11
17
|
if (!m)
|
|
@@ -48,13 +54,27 @@ function groupPrefixLength(src, i) {
|
|
|
48
54
|
const look = /^\(\?(?::|=|!|<=|<!)/.exec(src.slice(i));
|
|
49
55
|
return look ? look[0].length : 1;
|
|
50
56
|
}
|
|
57
|
+
function alternation(stack, i) {
|
|
58
|
+
const top = stack[stack.length - 1];
|
|
59
|
+
top.hasAlternation = true;
|
|
60
|
+
top.run = null;
|
|
61
|
+
endBranch(top);
|
|
62
|
+
return { atomEnd: i, closed: null, next: i + 1 };
|
|
63
|
+
}
|
|
64
|
+
function closeGroup(c, stack) {
|
|
65
|
+
if (c !== ')' || stack.length <= 1)
|
|
66
|
+
return null;
|
|
67
|
+
const closed = stack.pop();
|
|
68
|
+
endBranch(closed);
|
|
69
|
+
return closed;
|
|
70
|
+
}
|
|
51
71
|
function readToken(src, i, stack) {
|
|
52
72
|
var _a;
|
|
53
73
|
const c = src[i];
|
|
54
74
|
if (c === '\\') {
|
|
55
75
|
if (/[1-9k]/.test((_a = src[i + 1]) !== null && _a !== void 0 ? _a : ''))
|
|
56
76
|
return { atomEnd: i, closed: null, error: 'back-references are not supported' };
|
|
57
|
-
return { atomEnd: i +
|
|
77
|
+
return { atomEnd: i + (0, regexCharset_1.readEscape)(src, i).length, closed: null };
|
|
58
78
|
}
|
|
59
79
|
if (c === '[')
|
|
60
80
|
return { atomEnd: skipClass(src, i), closed: null };
|
|
@@ -62,13 +82,9 @@ function readToken(src, i, stack) {
|
|
|
62
82
|
stack.push(newFrame(/^\(\?(?:=|!|<=|<!)/.test(src.slice(i))));
|
|
63
83
|
return { atomEnd: i, closed: null, next: i + groupPrefixLength(src, i) };
|
|
64
84
|
}
|
|
65
|
-
if (c === '|')
|
|
66
|
-
stack
|
|
67
|
-
|
|
68
|
-
return { atomEnd: i, closed: null, next: i + 1 };
|
|
69
|
-
}
|
|
70
|
-
const closed = c === ')' && stack.length > 1 ? stack.pop() : null;
|
|
71
|
-
return { atomEnd: i + 1, closed };
|
|
85
|
+
if (c === '|')
|
|
86
|
+
return alternation(stack, i);
|
|
87
|
+
return { atomEnd: i + 1, closed: closeGroup(c, stack) };
|
|
72
88
|
}
|
|
73
89
|
const NESTED = 'a repeated group contains a quantifier or alternation (catastrophic backtracking)';
|
|
74
90
|
function isRisky(frame) {
|
|
@@ -106,13 +122,21 @@ function stepChars(src, i, step) {
|
|
|
106
122
|
return (0, regexCharset_1.atomCharset)(src, i, step.atomEnd);
|
|
107
123
|
return closed.zeroWidth ? null : closed.chars;
|
|
108
124
|
}
|
|
125
|
+
function consumes(q, chars, closed) {
|
|
126
|
+
if (chars === null || (q !== null && q.min < 1))
|
|
127
|
+
return false;
|
|
128
|
+
return !(closed === null || closed === void 0 ? void 0 : closed.nullable);
|
|
129
|
+
}
|
|
109
130
|
function stepProblem(src, i, step, q, top) {
|
|
110
|
-
var _a
|
|
131
|
+
var _a;
|
|
111
132
|
const problem = applyQuantifier(q, step.closed, top);
|
|
112
133
|
if (problem || (q === null || q === void 0 ? void 0 : q.max) === 0)
|
|
113
134
|
return problem;
|
|
114
|
-
const
|
|
115
|
-
|
|
135
|
+
const chars = stepChars(src, i, step);
|
|
136
|
+
const required = consumes(q, chars, step.closed);
|
|
137
|
+
if (required)
|
|
138
|
+
top.branchConsumes = true;
|
|
139
|
+
return applySequence(chars, isVariable(q) || !!((_a = step.closed) === null || _a === void 0 ? void 0 : _a.hasVariableQuantifier), required, top);
|
|
116
140
|
}
|
|
117
141
|
function structuralProblem(src) {
|
|
118
142
|
const stack = [newFrame()];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-redactor",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Redact, mask and anonymize PII before it reaches an LLM, then restore it. 216 patterns for emails, phones, addresses, IBAN, cards, names, SSN, passport and tax IDs. Reversible encrypted vault, GDPR Art.30 ROPA record, prompt-injection check. Runs on n8n Cloud and self-hosted n8n 2.x.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|