n8n-nodes-redactor 4.2.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.
|
@@ -1039,6 +1039,8 @@ class Redactor {
|
|
|
1039
1039
|
scanNumbers: true,
|
|
1040
1040
|
reservedTokens: (0, engine_1.collectTokenLike)(items.map((it) => it.json)),
|
|
1041
1041
|
keyOrigins: new WeakMap(),
|
|
1042
|
+
inputText: (0, engine_1.collectInputText)(items.map((it) => it.json)),
|
|
1043
|
+
lockedSpans: new WeakMap(),
|
|
1042
1044
|
};
|
|
1043
1045
|
const allHits = [];
|
|
1044
1046
|
const returnData = [];
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.collectTokenLike = collectTokenLike;
|
|
37
|
+
exports.collectInputText = collectInputText;
|
|
37
38
|
exports.redactValue = redactValue;
|
|
38
39
|
exports.restoreValue = restoreValue;
|
|
39
40
|
exports.enhanceWithPresidio = enhanceWithPresidio;
|
|
@@ -56,7 +57,7 @@ function isAllowListed(value, allowList) {
|
|
|
56
57
|
case 'contains':
|
|
57
58
|
return value.includes(entry.value);
|
|
58
59
|
case 'regex':
|
|
59
|
-
if (!isSafeRegex(entry.value))
|
|
60
|
+
if (!isSafeRegex(entry.value) || value.length > USER_REGEX_MAX_INPUT)
|
|
60
61
|
return false;
|
|
61
62
|
return new RegExp(entry.value, 'i').test(value);
|
|
62
63
|
default:
|
|
@@ -97,8 +98,8 @@ function maskValue(value, label) {
|
|
|
97
98
|
function hashValue(value) {
|
|
98
99
|
return crypto.createHash('sha256').update(value).digest('hex').slice(0, 12);
|
|
99
100
|
}
|
|
100
|
-
function storeReversible(match, pattern, vault, sessionId,
|
|
101
|
-
if (dedup) {
|
|
101
|
+
function storeReversible(match, pattern, ctx, vault, sessionId, makeToken) {
|
|
102
|
+
if (ctx.dedup) {
|
|
102
103
|
const existing = vault.findByOriginal(sessionId, match);
|
|
103
104
|
if (existing && !existing.numeric)
|
|
104
105
|
return existing.token;
|
|
@@ -120,14 +121,14 @@ const IRREVERSIBLE = {
|
|
|
120
121
|
blackout: (match) => '\u2588'.repeat(match.length),
|
|
121
122
|
remove: () => '',
|
|
122
123
|
};
|
|
123
|
-
function applyRedaction(match, pattern,
|
|
124
|
-
if (mode === 'token') {
|
|
125
|
-
return storeReversible(match, pattern, vault, sessionId,
|
|
124
|
+
function applyRedaction(match, pattern, ctx, vault, sessionId) {
|
|
125
|
+
if (ctx.mode === 'token') {
|
|
126
|
+
return storeReversible(match, pattern, ctx, vault, sessionId, () => freshToken(pattern.label, vault, sessionId, ctx.reservedTokens));
|
|
126
127
|
}
|
|
127
|
-
if (mode === 'pseudonymize') {
|
|
128
|
-
return storeReversible(match, pattern, vault, sessionId,
|
|
128
|
+
if (ctx.mode === 'pseudonymize') {
|
|
129
|
+
return storeReversible(match, pattern, ctx, vault, sessionId, () => uniquePseudonym(vault, sessionId, match, pattern.label, ctx));
|
|
129
130
|
}
|
|
130
|
-
const irreversible = IRREVERSIBLE[mode];
|
|
131
|
+
const irreversible = IRREVERSIBLE[ctx.mode];
|
|
131
132
|
return irreversible ? irreversible(match, pattern.label) : match;
|
|
132
133
|
}
|
|
133
134
|
function freshToken(label, vault, sessionId, reserved) {
|
|
@@ -140,36 +141,46 @@ function freshToken(label, vault, sessionId, reserved) {
|
|
|
140
141
|
return token;
|
|
141
142
|
}
|
|
142
143
|
const TOKEN_LIKE_RE = /\[[A-Z][A-Z0-9_]*_\d+\]/g;
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
into.add(m[0]);
|
|
147
|
-
};
|
|
148
|
-
if (typeof value === 'string')
|
|
149
|
-
scan(value);
|
|
144
|
+
function walkText(value, visit) {
|
|
145
|
+
if (typeof value === 'string' || typeof value === 'number')
|
|
146
|
+
visit(String(value));
|
|
150
147
|
else if (Array.isArray(value))
|
|
151
|
-
value.forEach((v) =>
|
|
148
|
+
value.forEach((v) => walkText(v, visit));
|
|
152
149
|
else if (value !== null && typeof value === 'object') {
|
|
153
150
|
for (const [k, v] of Object.entries(value)) {
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
visit(k);
|
|
152
|
+
walkText(v, visit);
|
|
156
153
|
}
|
|
157
154
|
}
|
|
155
|
+
}
|
|
156
|
+
function collectTokenLike(value, into = new Set()) {
|
|
157
|
+
walkText(value, (text) => {
|
|
158
|
+
for (const m of text.matchAll(TOKEN_LIKE_RE))
|
|
159
|
+
into.add(m[0]);
|
|
160
|
+
});
|
|
158
161
|
return into;
|
|
159
162
|
}
|
|
160
|
-
function
|
|
161
|
-
|
|
163
|
+
function collectInputText(value) {
|
|
164
|
+
const parts = [];
|
|
165
|
+
walkText(value, (text) => parts.push(text));
|
|
166
|
+
return parts.join('\u0000');
|
|
167
|
+
}
|
|
168
|
+
function reusable(taken, original, ctx) {
|
|
169
|
+
return !!taken && taken.original === original && !!taken.numeric === !!ctx.numeric;
|
|
170
|
+
}
|
|
171
|
+
function uniquePseudonym(vault, sessionId, original, label, ctx) {
|
|
172
|
+
var _a, _b, _c;
|
|
162
173
|
const entries = (_b = (_a = vault.getSession(sessionId)) === null || _a === void 0 ? void 0 : _a.entries) !== null && _b !== void 0 ? _b : {};
|
|
163
174
|
for (let attempt = 0; attempt < 32; attempt++) {
|
|
164
175
|
const seed = attempt === 0 ? original : `${original}\u0000${attempt}`;
|
|
165
176
|
const candidate = (0, pseudonymize_1.generatePseudonym)(sessionId, seed, label);
|
|
166
|
-
|
|
167
|
-
if (candidate === original)
|
|
177
|
+
if (candidate === original || ((_c = ctx.inputText) === null || _c === void 0 ? void 0 : _c.includes(candidate)))
|
|
168
178
|
continue;
|
|
169
|
-
|
|
179
|
+
const taken = entries[candidate];
|
|
180
|
+
if (!taken || reusable(taken, original, ctx))
|
|
170
181
|
return candidate;
|
|
171
182
|
}
|
|
172
|
-
return
|
|
183
|
+
return freshToken(label, vault, sessionId, ctx.reservedTokens);
|
|
173
184
|
}
|
|
174
185
|
function shouldProcessField(fieldPath, fieldMode, fieldRules) {
|
|
175
186
|
if (fieldMode === 'all')
|
|
@@ -412,7 +423,8 @@ function redactSemantic(raw, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
412
423
|
const semConf = semantic.label === 'IDENTIFIER' ? context_1.AMBIGUOUS_FIELD_CONFIDENCE : context_1.SEMANTIC_CONFIDENCE;
|
|
413
424
|
if (semConf < ((_a = ctx.confidenceThreshold) !== null && _a !== void 0 ? _a : 0))
|
|
414
425
|
return null;
|
|
415
|
-
const replacement = applyRedaction(raw, { label: semantic.label, category: semantic.category }, ctx
|
|
426
|
+
const replacement = applyRedaction(raw, { label: semantic.label, category: semantic.category }, ctx, vault, sessionId);
|
|
427
|
+
ctx.lastLocked = replacement ? [[0, replacement.length]] : undefined;
|
|
416
428
|
hits.push({
|
|
417
429
|
token: replacement, original: '***',
|
|
418
430
|
patternName: `semantic_${semantic.label}`,
|
|
@@ -423,10 +435,23 @@ function redactSemantic(raw, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
423
435
|
});
|
|
424
436
|
return replacement;
|
|
425
437
|
}
|
|
438
|
+
function numericMapping(out, text, ctx, vault, sessionId) {
|
|
439
|
+
var _a, _b;
|
|
440
|
+
const entries = (_b = (_a = vault.getSession(sessionId)) === null || _a === void 0 ? void 0 : _a.entries) !== null && _b !== void 0 ? _b : {};
|
|
441
|
+
const existing = entries[out];
|
|
442
|
+
if ((existing === null || existing === void 0 ? void 0 : existing.original) === text) {
|
|
443
|
+
existing.numeric = true;
|
|
444
|
+
return out;
|
|
445
|
+
}
|
|
446
|
+
const token = freshToken('NUMBER', vault, sessionId, ctx.reservedTokens);
|
|
447
|
+
vault.addEntry(sessionId, {
|
|
448
|
+
token, original: text, patternLabel: 'NUMBER', category: 'other', createdAt: new Date().toISOString(), numeric: true,
|
|
449
|
+
});
|
|
450
|
+
return token;
|
|
451
|
+
}
|
|
426
452
|
function redactNumber(value, ctx, vault, sessionId, hits, itemIndex, path, display, siblingKeys) {
|
|
427
|
-
var _a;
|
|
428
453
|
const text = String(value);
|
|
429
|
-
const numCtx = { ...ctx, dedup: false };
|
|
454
|
+
const numCtx = { ...ctx, dedup: false, numeric: true };
|
|
430
455
|
let out = null;
|
|
431
456
|
if (!ctx.skipSemantic)
|
|
432
457
|
out = redactSemantic(text, numCtx, vault, sessionId, hits, itemIndex, path, display, siblingKeys);
|
|
@@ -434,10 +459,13 @@ function redactNumber(value, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
434
459
|
out = redactText(text, numCtx, vault, sessionId, hits, itemIndex, display);
|
|
435
460
|
if (out === null || out === text)
|
|
436
461
|
return value;
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
462
|
+
if (ctx.mode !== 'token' && ctx.mode !== 'pseudonymize') {
|
|
463
|
+
ctx.lastLocked = [[0, out.length]];
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
const mapped = numericMapping(out, text, ctx, vault, sessionId);
|
|
467
|
+
ctx.lastLocked = [[0, mapped.length]];
|
|
468
|
+
return mapped;
|
|
441
469
|
}
|
|
442
470
|
function redactKey(key, taken, sourceKeys, ctx, vault, sessionId, itemIndex, keyHits) {
|
|
443
471
|
var _a;
|
|
@@ -474,7 +502,7 @@ function redactObject(value, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
474
502
|
const childDisplay = display ? `${display}.${outKey}` : outKey;
|
|
475
503
|
for (const h of keyHits)
|
|
476
504
|
hits.push({ ...h, field: childDisplay });
|
|
477
|
-
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));
|
|
478
506
|
}
|
|
479
507
|
(_a = ctx.keyOrigins) === null || _a === void 0 ? void 0 : _a.set(obj, origins);
|
|
480
508
|
return obj;
|
|
@@ -497,7 +525,11 @@ function redactValue(value, ctx, vault, sessionId, hits, itemIndex, currentPath
|
|
|
497
525
|
return redactNumber(value, ctx, vault, sessionId, hits, itemIndex, currentPath, displayPath, siblingKeys);
|
|
498
526
|
}
|
|
499
527
|
if (Array.isArray(value)) {
|
|
500
|
-
|
|
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;
|
|
501
533
|
}
|
|
502
534
|
if (value !== null && typeof value === 'object') {
|
|
503
535
|
return redactObject(value, ctx, vault, sessionId, hits, itemIndex, currentPath, displayPath);
|
|
@@ -505,38 +537,46 @@ function redactValue(value, ctx, vault, sessionId, hits, itemIndex, currentPath
|
|
|
505
537
|
return value;
|
|
506
538
|
}
|
|
507
539
|
const escapeRe = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
508
|
-
|
|
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.');
|
|
546
|
+
}
|
|
547
|
+
function replaceInFree(seg, re, fn, into) {
|
|
509
548
|
var _a;
|
|
510
|
-
if (produced.size === 0)
|
|
511
|
-
return [{ text, locked: false }];
|
|
512
|
-
const guard = new RegExp([...produced].sort((a, b) => b.length - a.length).map(escapeRe).join('|'), 'g');
|
|
513
|
-
const out = [];
|
|
514
549
|
let last = 0;
|
|
515
|
-
for (const m of text.matchAll(
|
|
550
|
+
for (const m of seg.text.matchAll(re)) {
|
|
516
551
|
const at = (_a = m.index) !== null && _a !== void 0 ? _a : 0;
|
|
552
|
+
if (m[0] === '')
|
|
553
|
+
continue;
|
|
554
|
+
const replacement = fn(m[0], seg.src + at);
|
|
555
|
+
if (replacement === null)
|
|
556
|
+
continue;
|
|
517
557
|
if (at > last)
|
|
518
|
-
|
|
519
|
-
|
|
558
|
+
into.push({ text: seg.text.slice(last, at), locked: false, src: seg.src + last });
|
|
559
|
+
into.push({ text: replacement, locked: true, src: -1 });
|
|
520
560
|
last = at + m[0].length;
|
|
521
561
|
}
|
|
522
|
-
if (last < text.length)
|
|
523
|
-
|
|
524
|
-
return out;
|
|
525
|
-
}
|
|
526
|
-
function replaceOutside(text, produced, re, fn) {
|
|
527
|
-
return segments(text, produced)
|
|
528
|
-
.map((seg) => (seg.locked ? seg.text : seg.text.replace(new RegExp(re.source, re.flags), fn)))
|
|
529
|
-
.join('');
|
|
562
|
+
if (last < seg.text.length)
|
|
563
|
+
into.push({ text: seg.text.slice(last), locked: false, src: seg.src + last });
|
|
530
564
|
}
|
|
531
|
-
function
|
|
532
|
-
|
|
565
|
+
function replaceFree(pass, re, fn) {
|
|
566
|
+
const flags = re.flags.includes('g') ? re.flags : `${re.flags}g`;
|
|
567
|
+
const global = new RegExp(re.source, flags);
|
|
568
|
+
const next = [];
|
|
569
|
+
for (const seg of pass.segs) {
|
|
570
|
+
if (seg.locked)
|
|
571
|
+
next.push(seg);
|
|
572
|
+
else
|
|
573
|
+
replaceInFree(seg, global, fn, next);
|
|
574
|
+
}
|
|
575
|
+
pass.segs = next;
|
|
533
576
|
}
|
|
534
577
|
function redactMatch(pass, match, pattern, detail) {
|
|
535
|
-
const
|
|
536
|
-
const replacement = applyRedaction(match, pattern, ctx.mode, vault, sessionId, ctx.dedup, ctx.reservedTokens);
|
|
578
|
+
const replacement = applyRedaction(match, pattern, pass.ctx, pass.vault, pass.sessionId);
|
|
537
579
|
pass.hits.push({ token: replacement, original: '***', field: pass.fieldPath, itemIndex: pass.itemIndex, ...detail });
|
|
538
|
-
if (replacement)
|
|
539
|
-
pass.produced.add(replacement);
|
|
540
580
|
return replacement;
|
|
541
581
|
}
|
|
542
582
|
function denyListRegex(entry) {
|
|
@@ -547,12 +587,14 @@ function denyListRegex(entry) {
|
|
|
547
587
|
function denyListStage(pass) {
|
|
548
588
|
var _a;
|
|
549
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}"`);
|
|
550
592
|
try {
|
|
551
593
|
const re = denyListRegex(entry);
|
|
552
594
|
if (!re)
|
|
553
595
|
continue;
|
|
554
|
-
|
|
555
|
-
?
|
|
596
|
+
replaceFree(pass, re, (match) => TOKEN_RE.test(match)
|
|
597
|
+
? null
|
|
556
598
|
: redactMatch(pass, match, { label: 'DENY_LIST', category: 'other' }, {
|
|
557
599
|
patternName: 'denyList', patternLabel: 'DENY_LIST', category: 'other', confidence: context_1.DENY_LIST_CONFIDENCE,
|
|
558
600
|
}));
|
|
@@ -560,17 +602,15 @@ function denyListStage(pass) {
|
|
|
560
602
|
catch { }
|
|
561
603
|
}
|
|
562
604
|
}
|
|
563
|
-
function builtInMatch(pass, pattern, match,
|
|
605
|
+
function builtInMatch(pass, pattern, match, offset) {
|
|
564
606
|
if (TOKEN_RE.test(match) || (match.startsWith('[') && match.endsWith(']')))
|
|
565
|
-
return
|
|
607
|
+
return null;
|
|
566
608
|
const validatorPassed = pattern.validate ? pattern.validate(match) : true;
|
|
567
609
|
if (!validatorPassed || isAllowListed(match, pass.ctx.allowList))
|
|
568
|
-
return
|
|
569
|
-
const offsetArg = args[args.length - 2];
|
|
570
|
-
const offset = typeof offsetArg === 'number' ? offsetArg : 0;
|
|
610
|
+
return null;
|
|
571
611
|
const confidence = (0, context_1.calculateConfidence)(pattern.name, !!pattern.validate, validatorPassed, pass.source, offset, offset + match.length);
|
|
572
612
|
if (confidence < pass.threshold)
|
|
573
|
-
return
|
|
613
|
+
return null;
|
|
574
614
|
return redactMatch(pass, match, pattern, {
|
|
575
615
|
patternName: pattern.name, patternLabel: pattern.label, category: pattern.category, confidence,
|
|
576
616
|
});
|
|
@@ -578,24 +618,34 @@ function builtInMatch(pass, pattern, match, args) {
|
|
|
578
618
|
function builtInStage(pass) {
|
|
579
619
|
const enabled = (0, patterns_1.getPatternsByNames)(pass.ctx.enabledPatterns);
|
|
580
620
|
for (const pattern of [...enabled.filter((p) => p.validate), ...enabled.filter((p) => !p.validate)]) {
|
|
581
|
-
|
|
582
|
-
pass.result = replaceOutside(pass.result, pass.produced, re, (match, ...args) => builtInMatch(pass, pattern, match, args));
|
|
621
|
+
replaceFree(pass, pattern.regex, (match, offset) => builtInMatch(pass, pattern, match, offset));
|
|
583
622
|
}
|
|
584
623
|
}
|
|
624
|
+
function freeNames(pass) {
|
|
625
|
+
const names = new Set();
|
|
626
|
+
for (const seg of pass.segs) {
|
|
627
|
+
if (!seg.locked)
|
|
628
|
+
for (const nm of (0, names_1.detectNamesInText)(seg.text))
|
|
629
|
+
names.add(nm.name);
|
|
630
|
+
}
|
|
631
|
+
return names;
|
|
632
|
+
}
|
|
585
633
|
function nameStage(pass) {
|
|
586
634
|
if (pass.ctx.skipSemantic)
|
|
587
635
|
return;
|
|
588
|
-
for (const
|
|
589
|
-
if ((
|
|
590
|
-
continue;
|
|
591
|
-
const confidence = (0, context_1.hasContextWords)(pass.source, nm.start, nm.end, names_1.NAME_CONTEXT_WORDS) ? 0.8 : 0.65;
|
|
592
|
-
if (confidence < pass.threshold || !occursOutside(pass.result, pass.produced, nm.name))
|
|
636
|
+
for (const name of freeNames(pass)) {
|
|
637
|
+
if ((name.includes('[') && name.includes(']')) || isAllowListed(name, pass.ctx.allowList))
|
|
593
638
|
continue;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
639
|
+
let replacement;
|
|
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;
|
|
644
|
+
replacement !== null && replacement !== void 0 ? replacement : (replacement = redactMatch(pass, match, { label: 'PERSON_NAME', category: 'identity' }, {
|
|
645
|
+
patternName: 'personNameDict', patternLabel: 'PERSON_NAME', category: 'identity', confidence,
|
|
646
|
+
}));
|
|
647
|
+
return replacement;
|
|
597
648
|
});
|
|
598
|
-
pass.result = replaceOutside(pass.result, before, new RegExp(escapeRe(nm.name), 'g'), () => replacement);
|
|
599
649
|
}
|
|
600
650
|
}
|
|
601
651
|
function customStage(pass) {
|
|
@@ -605,9 +655,10 @@ function customStage(pass) {
|
|
|
605
655
|
if (!cp.label || !cp.regex || !isSafeRegex(cp.regex))
|
|
606
656
|
continue;
|
|
607
657
|
const pattern = { label: cp.label.toUpperCase(), category: cp.category || 'identity' };
|
|
658
|
+
assertUserRegexInput(pass.source, `Custom pattern "${cp.label}"`);
|
|
608
659
|
try {
|
|
609
|
-
|
|
610
|
-
?
|
|
660
|
+
replaceFree(pass, new RegExp(cp.regex, 'g'), (match) => TOKEN_RE.test(match) || isAllowListed(match, pass.ctx.allowList)
|
|
661
|
+
? null
|
|
611
662
|
: redactMatch(pass, match, pattern, {
|
|
612
663
|
patternName: `custom_${cp.label}`, patternLabel: pattern.label, category: pattern.category,
|
|
613
664
|
confidence: context_1.CUSTOM_PATTERN_CONFIDENCE,
|
|
@@ -616,17 +667,40 @@ function customStage(pass) {
|
|
|
616
667
|
catch { }
|
|
617
668
|
}
|
|
618
669
|
}
|
|
670
|
+
function recordLocked(ctx, segs) {
|
|
671
|
+
const spans = [];
|
|
672
|
+
let at = 0;
|
|
673
|
+
for (const seg of segs) {
|
|
674
|
+
if (seg.locked)
|
|
675
|
+
spans.push([at, at + seg.text.length]);
|
|
676
|
+
at += seg.text.length;
|
|
677
|
+
}
|
|
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;
|
|
691
|
+
}
|
|
619
692
|
function redactText(text, ctx, vault, sessionId, hits, itemIndex, fieldPath) {
|
|
620
693
|
var _a;
|
|
621
694
|
const pass = {
|
|
622
|
-
source: text,
|
|
695
|
+
source: text, segs: [{ text, locked: false, src: 0 }], threshold: (_a = ctx.confidenceThreshold) !== null && _a !== void 0 ? _a : 0,
|
|
623
696
|
ctx, vault, sessionId, hits, itemIndex, fieldPath,
|
|
624
697
|
};
|
|
625
698
|
denyListStage(pass);
|
|
626
699
|
builtInStage(pass);
|
|
627
700
|
nameStage(pass);
|
|
628
701
|
customStage(pass);
|
|
629
|
-
|
|
702
|
+
recordLocked(ctx, pass.segs);
|
|
703
|
+
return pass.segs.map((s) => s.text).join('');
|
|
630
704
|
}
|
|
631
705
|
function makeRestorer(entries) {
|
|
632
706
|
const tokens = Object.keys(entries).filter((t) => t.length > 0).sort((a, b) => b.length - a.length);
|
|
@@ -676,13 +750,17 @@ function resolveOverlaps(spans) {
|
|
|
676
750
|
}
|
|
677
751
|
return kept.sort((a, b) => b.start - a.start);
|
|
678
752
|
}
|
|
679
|
-
|
|
753
|
+
function presidioSpans(value, entities, ctx, locked) {
|
|
680
754
|
var _a;
|
|
755
|
+
const threshold = (_a = ctx.confidenceThreshold) !== null && _a !== void 0 ? _a : 0;
|
|
756
|
+
const free = (s) => !locked.some(([start, end]) => s.start < end && start < s.end);
|
|
757
|
+
return resolveOverlaps(toUtf16Spans(value, entities).filter((s) => s.score >= threshold && free(s)));
|
|
758
|
+
}
|
|
759
|
+
async function presidioRedactString(value, ctx, vault, sessionId, hits, itemIndex, path, locked) {
|
|
681
760
|
if (value.trim().length === 0 || TOKEN_RE.test(value))
|
|
682
761
|
return value;
|
|
683
762
|
const entities = await (0, presidio_1.analyzeWithPresidio)(ctx.presidioUrl, value, ctx.presidioLanguage || 'en', 0.4, 10000, ctx.presidioRequest);
|
|
684
|
-
const
|
|
685
|
-
const spans = resolveOverlaps(toUtf16Spans(value, entities).filter((s) => s.score >= threshold));
|
|
763
|
+
const spans = presidioSpans(value, entities, ctx, locked);
|
|
686
764
|
let text = value;
|
|
687
765
|
for (const span of spans) {
|
|
688
766
|
const matchedText = value.slice(span.start, span.end);
|
|
@@ -692,7 +770,7 @@ async function presidioRedactString(value, ctx, vault, sessionId, hits, itemInde
|
|
|
692
770
|
continue;
|
|
693
771
|
const label = (0, presidio_1.getRedactorLabel)(span.entityType);
|
|
694
772
|
const category = (0, presidio_1.getRedactorCategory)(span.entityType);
|
|
695
|
-
const replacement = applyRedaction(matchedText, { label, category }, ctx
|
|
773
|
+
const replacement = applyRedaction(matchedText, { label, category }, ctx, vault, sessionId);
|
|
696
774
|
text = text.slice(0, span.start) + replacement + text.slice(span.end);
|
|
697
775
|
hits.push({
|
|
698
776
|
token: replacement, original: '***',
|
|
@@ -702,13 +780,13 @@ async function presidioRedactString(value, ctx, vault, sessionId, hits, itemInde
|
|
|
702
780
|
}
|
|
703
781
|
return text;
|
|
704
782
|
}
|
|
705
|
-
async function presidioWalk(value, ctx, vault, sessionId, hits, itemIndex, path, policyPath) {
|
|
706
|
-
var _a, _b;
|
|
783
|
+
async function presidioWalk(value, ctx, vault, sessionId, hits, itemIndex, path, policyPath, locked = []) {
|
|
784
|
+
var _a, _b, _c, _d, _e, _f;
|
|
707
785
|
if (typeof value === 'string') {
|
|
708
786
|
if (!shouldProcessField(policyPath, ctx.fieldMode, ctx.fieldRules))
|
|
709
787
|
return value;
|
|
710
788
|
try {
|
|
711
|
-
return await presidioRedactString(value, ctx, vault, sessionId, hits, itemIndex, path);
|
|
789
|
+
return await presidioRedactString(value, ctx, vault, sessionId, hits, itemIndex, path, locked);
|
|
712
790
|
}
|
|
713
791
|
catch {
|
|
714
792
|
return value;
|
|
@@ -717,18 +795,20 @@ async function presidioWalk(value, ctx, vault, sessionId, hits, itemIndex, path,
|
|
|
717
795
|
if (Array.isArray(value)) {
|
|
718
796
|
const out = [];
|
|
719
797
|
for (let i = 0; i < value.length; i++) {
|
|
720
|
-
|
|
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));
|
|
721
800
|
}
|
|
722
801
|
return out;
|
|
723
802
|
}
|
|
724
803
|
if (value !== null && typeof value === 'object') {
|
|
725
|
-
const origins = (
|
|
804
|
+
const origins = (_c = ctx.keyOrigins) === null || _c === void 0 ? void 0 : _c.get(value);
|
|
726
805
|
const out = {};
|
|
727
806
|
for (const [k, v] of Object.entries(value)) {
|
|
728
|
-
const original = (
|
|
807
|
+
const original = (_d = origins === null || origins === void 0 ? void 0 : origins.get(k)) !== null && _d !== void 0 ? _d : k;
|
|
729
808
|
const child = path ? `${path}.${k}` : k;
|
|
730
809
|
const childPolicy = policyPath ? `${policyPath}.${original}` : original;
|
|
731
|
-
|
|
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);
|
|
732
812
|
}
|
|
733
813
|
return out;
|
|
734
814
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emptySet = void 0;
|
|
4
|
+
exports.union = union;
|
|
5
|
+
exports.overlaps = overlaps;
|
|
6
|
+
exports.readEscape = readEscape;
|
|
7
|
+
exports.atomCharset = atomCharset;
|
|
8
|
+
const emptySet = () => ({ ascii: new Array(128).fill(false), other: false });
|
|
9
|
+
exports.emptySet = emptySet;
|
|
10
|
+
const fullSet = () => ({ ascii: new Array(128).fill(true), other: true });
|
|
11
|
+
function addCode(set, code) {
|
|
12
|
+
if (code >= 128)
|
|
13
|
+
set.other = true;
|
|
14
|
+
else
|
|
15
|
+
set.ascii[code] = true;
|
|
16
|
+
}
|
|
17
|
+
function addRange(set, from, to) {
|
|
18
|
+
for (let c = from; c <= Math.min(to, 127); c++)
|
|
19
|
+
addCode(set, c);
|
|
20
|
+
if (to >= 128)
|
|
21
|
+
set.other = true;
|
|
22
|
+
}
|
|
23
|
+
function union(a, b) {
|
|
24
|
+
return { ascii: a.ascii.map((v, i) => v || b.ascii[i]), other: a.other || b.other };
|
|
25
|
+
}
|
|
26
|
+
function overlaps(a, b) {
|
|
27
|
+
return (a.other && b.other) || a.ascii.some((v, i) => v && b.ascii[i]);
|
|
28
|
+
}
|
|
29
|
+
function complement(set) {
|
|
30
|
+
return { ascii: set.ascii.map((v) => !v), other: true };
|
|
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
|
+
}
|
|
38
|
+
const CLASS_ESCAPES = {
|
|
39
|
+
d: (s) => addRange(s, 48, 57),
|
|
40
|
+
w: (s) => {
|
|
41
|
+
addRange(s, 48, 57);
|
|
42
|
+
addRange(s, 65, 90);
|
|
43
|
+
addRange(s, 97, 122);
|
|
44
|
+
addCode(s, 95);
|
|
45
|
+
},
|
|
46
|
+
s: (s) => {
|
|
47
|
+
for (const c of [9, 10, 11, 12, 13, 32])
|
|
48
|
+
addCode(s, c);
|
|
49
|
+
s.other = true;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
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;
|
|
76
|
+
}
|
|
77
|
+
const letter = (_a = src[i + 1]) !== null && _a !== void 0 ? _a : '';
|
|
78
|
+
const fill = CLASS_ESCAPES[letter.toLowerCase()];
|
|
79
|
+
if (!fill)
|
|
80
|
+
return fullSet();
|
|
81
|
+
fill(set);
|
|
82
|
+
return letter === letter.toLowerCase() ? set : complement(set);
|
|
83
|
+
}
|
|
84
|
+
function classMember(body, j) {
|
|
85
|
+
if (body[j] === '\\') {
|
|
86
|
+
const { length, code } = readEscape(body, j);
|
|
87
|
+
return { set: escapeSet(body, j), next: j + length, code };
|
|
88
|
+
}
|
|
89
|
+
const set = (0, exports.emptySet)();
|
|
90
|
+
addCode(set, body.charCodeAt(j));
|
|
91
|
+
return { set, next: j + 1, code: body.charCodeAt(j) };
|
|
92
|
+
}
|
|
93
|
+
function classSet(body) {
|
|
94
|
+
const negated = body.startsWith('^');
|
|
95
|
+
let set = (0, exports.emptySet)();
|
|
96
|
+
let j = negated ? 1 : 0;
|
|
97
|
+
while (j < body.length) {
|
|
98
|
+
const first = classMember(body, j);
|
|
99
|
+
j = first.next;
|
|
100
|
+
if (body[j] === '-' && j + 1 < body.length && first.code !== null) {
|
|
101
|
+
const last = classMember(body, j + 1);
|
|
102
|
+
if (last.code !== null)
|
|
103
|
+
addRange(set, first.code, last.code);
|
|
104
|
+
else
|
|
105
|
+
set = union(union(set, first.set), last.set);
|
|
106
|
+
j = last.next;
|
|
107
|
+
}
|
|
108
|
+
else
|
|
109
|
+
set = union(set, first.set);
|
|
110
|
+
}
|
|
111
|
+
return negated ? complement(set) : set;
|
|
112
|
+
}
|
|
113
|
+
function rawCharset(src, start, end) {
|
|
114
|
+
var _a;
|
|
115
|
+
const c = src[start];
|
|
116
|
+
if (c === '^' || c === '$')
|
|
117
|
+
return null;
|
|
118
|
+
if (c === '\\')
|
|
119
|
+
return /[bB]/.test((_a = src[start + 1]) !== null && _a !== void 0 ? _a : '') ? null : escapeSet(src, start);
|
|
120
|
+
if (c === '[')
|
|
121
|
+
return classSet(src.slice(start + 1, end - 1));
|
|
122
|
+
if (c === '.')
|
|
123
|
+
return fullSet();
|
|
124
|
+
const set = (0, exports.emptySet)();
|
|
125
|
+
addCode(set, src.charCodeAt(start));
|
|
126
|
+
return set;
|
|
127
|
+
}
|
|
128
|
+
function atomCharset(src, start, end) {
|
|
129
|
+
const set = rawCharset(src, start, end);
|
|
130
|
+
return set && fold(set);
|
|
131
|
+
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.checkRegex = checkRegex;
|
|
4
|
+
const regexCharset_1 = require("./regexCharset");
|
|
4
5
|
const MAX_REGEX_LENGTH = 500;
|
|
6
|
+
const newFrame = (zeroWidth = false) => ({
|
|
7
|
+
hasVariableQuantifier: false, hasAlternation: false, run: null, chars: (0, regexCharset_1.emptySet)(), zeroWidth,
|
|
8
|
+
branchConsumes: false, allConsume: true, nullable: false,
|
|
9
|
+
});
|
|
10
|
+
function endBranch(frame) {
|
|
11
|
+
frame.allConsume && (frame.allConsume = frame.branchConsumes);
|
|
12
|
+
frame.branchConsumes = false;
|
|
13
|
+
frame.nullable = !frame.allConsume;
|
|
14
|
+
}
|
|
5
15
|
function readBraces(src, i) {
|
|
6
16
|
const m = /^\{(\d+)(,(\d*))?\}/.exec(src.slice(i));
|
|
7
17
|
if (!m)
|
|
@@ -44,32 +54,43 @@ function groupPrefixLength(src, i) {
|
|
|
44
54
|
const look = /^\(\?(?::|=|!|<=|<!)/.exec(src.slice(i));
|
|
45
55
|
return look ? look[0].length : 1;
|
|
46
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
|
+
}
|
|
47
71
|
function readToken(src, i, stack) {
|
|
48
72
|
var _a;
|
|
49
73
|
const c = src[i];
|
|
50
74
|
if (c === '\\') {
|
|
51
75
|
if (/[1-9k]/.test((_a = src[i + 1]) !== null && _a !== void 0 ? _a : ''))
|
|
52
76
|
return { atomEnd: i, closed: null, error: 'back-references are not supported' };
|
|
53
|
-
return { atomEnd: i +
|
|
77
|
+
return { atomEnd: i + (0, regexCharset_1.readEscape)(src, i).length, closed: null };
|
|
54
78
|
}
|
|
55
79
|
if (c === '[')
|
|
56
80
|
return { atomEnd: skipClass(src, i), closed: null };
|
|
57
81
|
if (c === '(') {
|
|
58
|
-
stack.push(
|
|
82
|
+
stack.push(newFrame(/^\(\?(?:=|!|<=|<!)/.test(src.slice(i))));
|
|
59
83
|
return { atomEnd: i, closed: null, next: i + groupPrefixLength(src, i) };
|
|
60
84
|
}
|
|
61
|
-
if (c === '|')
|
|
62
|
-
stack
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
const closed = c === ')' && stack.length > 1 ? stack.pop() : null;
|
|
66
|
-
return { atomEnd: i + 1, closed };
|
|
85
|
+
if (c === '|')
|
|
86
|
+
return alternation(stack, i);
|
|
87
|
+
return { atomEnd: i + 1, closed: closeGroup(c, stack) };
|
|
67
88
|
}
|
|
68
89
|
const NESTED = 'a repeated group contains a quantifier or alternation (catastrophic backtracking)';
|
|
69
90
|
function isRisky(frame) {
|
|
70
91
|
return !!frame && (frame.hasVariableQuantifier || frame.hasAlternation);
|
|
71
92
|
}
|
|
72
|
-
const NO_GROUP =
|
|
93
|
+
const NO_GROUP = newFrame();
|
|
73
94
|
const repeats = (q) => q !== null && q.max > 1;
|
|
74
95
|
const isVariable = (q) => q !== null && q.min !== q.max;
|
|
75
96
|
function applyQuantifier(q, closed, top) {
|
|
@@ -80,8 +101,45 @@ function applyQuantifier(q, closed, top) {
|
|
|
80
101
|
top.hasAlternation || (top.hasAlternation = inner.hasAlternation);
|
|
81
102
|
return null;
|
|
82
103
|
}
|
|
104
|
+
const SEQUENTIAL = 'two repetitions in a row can match the same characters (catastrophic backtracking)';
|
|
105
|
+
function applySequence(chars, variable, required, top) {
|
|
106
|
+
if (chars === null)
|
|
107
|
+
return null;
|
|
108
|
+
top.chars = (0, regexCharset_1.union)(top.chars, chars);
|
|
109
|
+
const shared = top.run !== null && (0, regexCharset_1.overlaps)(top.run, chars);
|
|
110
|
+
if (variable) {
|
|
111
|
+
if (shared)
|
|
112
|
+
return SEQUENTIAL;
|
|
113
|
+
top.run = top.run ? (0, regexCharset_1.union)(top.run, chars) : chars;
|
|
114
|
+
}
|
|
115
|
+
else if (required && !shared)
|
|
116
|
+
top.run = null;
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function stepChars(src, i, step) {
|
|
120
|
+
const closed = step.closed;
|
|
121
|
+
if (!closed)
|
|
122
|
+
return (0, regexCharset_1.atomCharset)(src, i, step.atomEnd);
|
|
123
|
+
return closed.zeroWidth ? null : closed.chars;
|
|
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
|
+
}
|
|
130
|
+
function stepProblem(src, i, step, q, top) {
|
|
131
|
+
var _a;
|
|
132
|
+
const problem = applyQuantifier(q, step.closed, top);
|
|
133
|
+
if (problem || (q === null || q === void 0 ? void 0 : q.max) === 0)
|
|
134
|
+
return problem;
|
|
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);
|
|
140
|
+
}
|
|
83
141
|
function structuralProblem(src) {
|
|
84
|
-
const stack = [
|
|
142
|
+
const stack = [newFrame()];
|
|
85
143
|
let i = 0;
|
|
86
144
|
while (i < src.length) {
|
|
87
145
|
const step = readToken(src, i, stack);
|
|
@@ -92,7 +150,7 @@ function structuralProblem(src) {
|
|
|
92
150
|
continue;
|
|
93
151
|
}
|
|
94
152
|
const q = readQuantifier(src, step.atomEnd);
|
|
95
|
-
const problem =
|
|
153
|
+
const problem = stepProblem(src, i, step, q, stack[stack.length - 1]);
|
|
96
154
|
if (problem)
|
|
97
155
|
return problem;
|
|
98
156
|
i = step.atomEnd + (q ? q.length : 0);
|
|
@@ -27,12 +27,26 @@ async function resolvePassphrase(fns) {
|
|
|
27
27
|
}
|
|
28
28
|
return cred.passphrase;
|
|
29
29
|
}
|
|
30
|
+
return legacyPassphrase(fns);
|
|
31
|
+
}
|
|
32
|
+
function legacyPassphrase(fns) {
|
|
33
|
+
let raw;
|
|
30
34
|
try {
|
|
31
|
-
|
|
35
|
+
raw = fns.getNodeParameter('vaultPassphrase', 0, '');
|
|
32
36
|
}
|
|
33
|
-
catch {
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new n8n_workflow_1.NodeOperationError(fns.getNode(), 'The legacy vault passphrase could not be evaluated, so the vault stays locked.', {
|
|
39
|
+
description: `Nothing was stored. Fix the expression, or move the passphrase into a Redactor Vault credential. Cause: ${error.message}`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (raw === undefined || raw === null || raw === '')
|
|
34
43
|
return '';
|
|
44
|
+
if (typeof raw !== 'string') {
|
|
45
|
+
throw new n8n_workflow_1.NodeOperationError(fns.getNode(), 'The legacy vault passphrase is not text, so the vault stays locked.', {
|
|
46
|
+
description: 'Nothing was stored. Set the passphrase to a text value, or use a Redactor Vault credential.',
|
|
47
|
+
});
|
|
35
48
|
}
|
|
49
|
+
return raw;
|
|
36
50
|
}
|
|
37
51
|
function workflowNamespace(fns) {
|
|
38
52
|
try {
|
|
@@ -47,7 +61,7 @@ function workflowNamespace(fns) {
|
|
|
47
61
|
function assertUserRegexes(fns, customPatterns, lists) {
|
|
48
62
|
const refuse = (what, problem) => {
|
|
49
63
|
throw new n8n_workflow_1.NodeOperationError(fns.getNode(), `${what} regex ${problem}`, {
|
|
50
|
-
description: 'Rewrite the pattern without nested or alternated repetition, for example [ab]+ instead of (a|b)+.',
|
|
64
|
+
description: 'Rewrite the pattern without nested or alternated repetition, for example [ab]+ instead of (a|b)+, and without two repetitions in a row that can match the same characters, for example \\d+ instead of \\d+\\d+. Put a fixed separator such as - or @ between repeated parts.',
|
|
51
65
|
});
|
|
52
66
|
};
|
|
53
67
|
for (const cp of customPatterns) {
|
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",
|