n8n-nodes-redactor 5.0.0 → 5.2.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.
|
@@ -767,7 +767,7 @@ class Redactor {
|
|
|
767
767
|
};
|
|
768
768
|
}
|
|
769
769
|
async execute() {
|
|
770
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
770
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
771
771
|
const items = this.getInputData();
|
|
772
772
|
const allPaired = items.map((_, i) => ({ item: i }));
|
|
773
773
|
const operation = this.getNodeParameter('operation', 0);
|
|
@@ -1041,6 +1041,7 @@ class Redactor {
|
|
|
1041
1041
|
keyOrigins: new WeakMap(),
|
|
1042
1042
|
inputText: (0, engine_1.collectInputText)(items.map((it) => it.json)),
|
|
1043
1043
|
lockedSpans: new WeakMap(),
|
|
1044
|
+
priorTokens: (0, engine_1.priorTokenMatcher)((_e = vault.getSession(sessionId)) === null || _e === void 0 ? void 0 : _e.entries),
|
|
1044
1045
|
};
|
|
1045
1046
|
const allHits = [];
|
|
1046
1047
|
const returnData = [];
|
|
@@ -1091,7 +1092,7 @@ class Redactor {
|
|
|
1091
1092
|
if (includeReport && this.getNodeParameter('includeRopa', 0, false)) {
|
|
1092
1093
|
const d = this.getNodeParameter('ropa', 0, {});
|
|
1093
1094
|
const list = (v) => String(v !== null && v !== void 0 ? v : '').split(',').map((x) => x.trim()).filter(Boolean);
|
|
1094
|
-
report.ropa = (0, ropa_1.generateRopaRecord)(allHits, (0, classification_1.classifyData)(allHits), items.length, enabledPatterns.length, mode, sessionTtl, !!vaultPassphrase, auditLog, { name: String((
|
|
1095
|
+
report.ropa = (0, ropa_1.generateRopaRecord)(allHits, (0, classification_1.classifyData)(allHits), items.length, enabledPatterns.length, mode, sessionTtl, !!vaultPassphrase, auditLog, { name: String((_f = d.controllerName) !== null && _f !== void 0 ? _f : ''), contactEmail: String((_g = d.controllerContact) !== null && _g !== void 0 ? _g : ''), dpo: d.dpo ? String(d.dpo) : undefined }, list(d.purposes), String((_h = d.legalBasis) !== null && _h !== void 0 ? _h : 'legitimate_interest'), list(d.dataSubjects), list(d.recipients), []);
|
|
1095
1096
|
}
|
|
1096
1097
|
return [returnData, [{ json: report, pairedItem: allPaired }]];
|
|
1097
1098
|
}
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.collectTokenLike = collectTokenLike;
|
|
37
37
|
exports.collectInputText = collectInputText;
|
|
38
38
|
exports.redactValue = redactValue;
|
|
39
|
+
exports.priorTokenMatcher = priorTokenMatcher;
|
|
39
40
|
exports.restoreValue = restoreValue;
|
|
40
41
|
exports.enhanceWithPresidio = enhanceWithPresidio;
|
|
41
42
|
exports.buildReport = buildReport;
|
|
@@ -70,7 +71,11 @@ function isAllowListed(value, allowList) {
|
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
73
|
function generateToken(label, index) {
|
|
73
|
-
|
|
74
|
+
const safe = label.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
|
75
|
+
return `[${/^[A-Z]/.test(safe) ? safe : `X${safe}`}_${index}]`;
|
|
76
|
+
}
|
|
77
|
+
function setOwn(obj, key, value) {
|
|
78
|
+
Object.defineProperty(obj, key, { value, enumerable: true, writable: true, configurable: true });
|
|
74
79
|
}
|
|
75
80
|
const TOKEN_RE = /^\[[A-Z][A-Z0-9_]*_\d+\]$/;
|
|
76
81
|
function maskValue(value, label) {
|
|
@@ -460,7 +465,7 @@ function redactNumber(value, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
460
465
|
if (out === null || out === text)
|
|
461
466
|
return value;
|
|
462
467
|
if (ctx.mode !== 'token' && ctx.mode !== 'pseudonymize') {
|
|
463
|
-
ctx.lastLocked =
|
|
468
|
+
ctx.lastLocked = numCtx.lastLocked;
|
|
464
469
|
return out;
|
|
465
470
|
}
|
|
466
471
|
const mapped = numericMapping(out, text, ctx, vault, sessionId);
|
|
@@ -472,7 +477,7 @@ function redactKey(key, taken, sourceKeys, ctx, vault, sessionId, itemIndex, key
|
|
|
472
477
|
const out = redactText(key, ctx, vault, sessionId, keyHits, itemIndex, '');
|
|
473
478
|
if (out === key)
|
|
474
479
|
return key;
|
|
475
|
-
const clashes = (k) => k === '' || k
|
|
480
|
+
const clashes = (k) => k === '' || Object.prototype.hasOwnProperty.call(taken, k) || sourceKeys.has(k);
|
|
476
481
|
if (!clashes(out))
|
|
477
482
|
return out;
|
|
478
483
|
if (ctx.mode === 'token' || ctx.mode === 'pseudonymize') {
|
|
@@ -502,7 +507,7 @@ function redactObject(value, ctx, vault, sessionId, hits, itemIndex, path, displ
|
|
|
502
507
|
const childDisplay = display ? `${display}.${outKey}` : outKey;
|
|
503
508
|
for (const h of keyHits)
|
|
504
509
|
hits.push({ ...h, field: childDisplay });
|
|
505
|
-
obj
|
|
510
|
+
setOwn(obj, outKey, lockChild(ctx, obj, outKey, () => redactValue(v, ctx, vault, sessionId, hits, itemIndex, fieldPath, siblingKeys, childDisplay)));
|
|
506
511
|
}
|
|
507
512
|
(_a = ctx.keyOrigins) === null || _a === void 0 ? void 0 : _a.set(obj, origins);
|
|
508
513
|
return obj;
|
|
@@ -544,35 +549,56 @@ function assertUserRegexInput(text, what) {
|
|
|
544
549
|
throw new Error(`${what} cannot scan a value of ${text.length} characters, the limit is ${USER_REGEX_MAX_INPUT}. ` +
|
|
545
550
|
'Exclude the field with field rules, or remove the regex.');
|
|
546
551
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
552
|
+
class SegmentCursor {
|
|
553
|
+
constructor(segs) {
|
|
554
|
+
this.segs = segs;
|
|
555
|
+
this.next = [];
|
|
556
|
+
this.i = 0;
|
|
557
|
+
this.cur = segs[0];
|
|
558
|
+
}
|
|
559
|
+
freeAt(start, end) {
|
|
560
|
+
while (this.cur && (this.cur.locked || this.cur.src + this.cur.text.length <= start))
|
|
561
|
+
this.take();
|
|
562
|
+
const cur = this.cur;
|
|
563
|
+
return cur && cur.src <= start && end <= cur.src + cur.text.length ? cur : undefined;
|
|
564
|
+
}
|
|
565
|
+
lock(start, end, replacement) {
|
|
566
|
+
const cur = this.cur;
|
|
567
|
+
if (start > cur.src)
|
|
568
|
+
this.next.push({ text: cur.text.slice(0, start - cur.src), locked: false, src: cur.src });
|
|
569
|
+
this.next.push({ text: replacement, locked: true, src: -1 });
|
|
570
|
+
const tail = end - cur.src;
|
|
571
|
+
if (tail < cur.text.length)
|
|
572
|
+
this.cur = { text: cur.text.slice(tail), locked: false, src: end };
|
|
573
|
+
else
|
|
574
|
+
this.cur = this.segs[++this.i];
|
|
575
|
+
}
|
|
576
|
+
finish() {
|
|
577
|
+
while (this.cur)
|
|
578
|
+
this.take();
|
|
579
|
+
return this.next;
|
|
580
|
+
}
|
|
581
|
+
take() {
|
|
582
|
+
this.next.push(this.cur);
|
|
583
|
+
this.cur = this.segs[++this.i];
|
|
561
584
|
}
|
|
562
|
-
if (last < seg.text.length)
|
|
563
|
-
into.push({ text: seg.text.slice(last), locked: false, src: seg.src + last });
|
|
564
585
|
}
|
|
565
586
|
function replaceFree(pass, re, fn) {
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
587
|
+
const global = new RegExp(re.source, re.flags.includes('g') ? re.flags : `${re.flags}g`);
|
|
588
|
+
const cursor = new SegmentCursor(pass.segs);
|
|
589
|
+
let m;
|
|
590
|
+
while ((m = global.exec(pass.source)) !== null) {
|
|
591
|
+
const start = m.index;
|
|
592
|
+
const end = start + m[0].length;
|
|
593
|
+
if (m[0] === '' || !cursor.freeAt(start, end)) {
|
|
594
|
+
global.lastIndex = start + 1;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
const replacement = fn(m[0], start);
|
|
598
|
+
if (replacement !== null)
|
|
599
|
+
cursor.lock(start, end, replacement);
|
|
574
600
|
}
|
|
575
|
-
pass.segs =
|
|
601
|
+
pass.segs = cursor.finish();
|
|
576
602
|
}
|
|
577
603
|
function redactMatch(pass, match, pattern, detail) {
|
|
578
604
|
const replacement = applyRedaction(match, pattern, pass.ctx, pass.vault, pass.sessionId);
|
|
@@ -621,19 +647,18 @@ function builtInStage(pass) {
|
|
|
621
647
|
replaceFree(pass, pattern.regex, (match, offset) => builtInMatch(pass, pattern, match, offset));
|
|
622
648
|
}
|
|
623
649
|
}
|
|
624
|
-
function
|
|
625
|
-
const names = new Set();
|
|
626
|
-
for (const seg of pass.segs)
|
|
650
|
+
function sourceNames(pass) {
|
|
651
|
+
const names = new Set((0, names_1.detectNamesInText)(pass.source).map((nm) => nm.name));
|
|
652
|
+
for (const seg of pass.segs)
|
|
627
653
|
if (!seg.locked)
|
|
628
654
|
for (const nm of (0, names_1.detectNamesInText)(seg.text))
|
|
629
655
|
names.add(nm.name);
|
|
630
|
-
|
|
631
|
-
return names;
|
|
656
|
+
return [...names].sort((x, y) => y.length - x.length);
|
|
632
657
|
}
|
|
633
658
|
function nameStage(pass) {
|
|
634
659
|
if (pass.ctx.skipSemantic)
|
|
635
660
|
return;
|
|
636
|
-
for (const name of
|
|
661
|
+
for (const name of sourceNames(pass)) {
|
|
637
662
|
if ((name.includes('[') && name.includes(']')) || isAllowListed(name, pass.ctx.allowList))
|
|
638
663
|
continue;
|
|
639
664
|
let replacement;
|
|
@@ -689,12 +714,27 @@ function lockChild(ctx, container, key, compute) {
|
|
|
689
714
|
ctx.lastLocked = undefined;
|
|
690
715
|
return out;
|
|
691
716
|
}
|
|
717
|
+
function priorTokenMatcher(entries) {
|
|
718
|
+
const tokens = Object.keys(entries !== null && entries !== void 0 ? entries : {}).filter((t) => t.length > 0).sort((a, b) => b.length - a.length);
|
|
719
|
+
return tokens.length > 0 ? new RegExp(tokens.map(escapeRe).join('|'), 'g') : undefined;
|
|
720
|
+
}
|
|
721
|
+
function literalStage(pass) {
|
|
722
|
+
const { ctx, vault, sessionId } = pass;
|
|
723
|
+
if (!ctx.priorTokens || (ctx.mode !== 'token' && ctx.mode !== 'pseudonymize'))
|
|
724
|
+
return;
|
|
725
|
+
replaceFree(pass, ctx.priorTokens, (match) => {
|
|
726
|
+
const token = freshToken('LITERAL', vault, sessionId, ctx.reservedTokens);
|
|
727
|
+
vault.addEntry(sessionId, { token, original: match, patternLabel: 'LITERAL', category: 'other', createdAt: new Date().toISOString() });
|
|
728
|
+
return token;
|
|
729
|
+
});
|
|
730
|
+
}
|
|
692
731
|
function redactText(text, ctx, vault, sessionId, hits, itemIndex, fieldPath) {
|
|
693
732
|
var _a;
|
|
694
733
|
const pass = {
|
|
695
734
|
source: text, segs: [{ text, locked: false, src: 0 }], threshold: (_a = ctx.confidenceThreshold) !== null && _a !== void 0 ? _a : 0,
|
|
696
735
|
ctx, vault, sessionId, hits, itemIndex, fieldPath,
|
|
697
736
|
};
|
|
737
|
+
literalStage(pass);
|
|
698
738
|
denyListStage(pass);
|
|
699
739
|
builtInStage(pass);
|
|
700
740
|
nameStage(pass);
|
|
@@ -721,7 +761,7 @@ function restoreWith(value, restore, entries) {
|
|
|
721
761
|
if (value !== null && typeof value === 'object') {
|
|
722
762
|
const obj = {};
|
|
723
763
|
for (const [k, v] of Object.entries(value)) {
|
|
724
|
-
obj
|
|
764
|
+
setOwn(obj, restore(k), restoreWith(v, restore, entries));
|
|
725
765
|
}
|
|
726
766
|
return obj;
|
|
727
767
|
}
|
|
@@ -808,7 +848,7 @@ async function presidioWalk(value, ctx, vault, sessionId, hits, itemIndex, path,
|
|
|
808
848
|
const child = path ? `${path}.${k}` : k;
|
|
809
849
|
const childPolicy = policyPath ? `${policyPath}.${original}` : original;
|
|
810
850
|
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
|
|
851
|
+
setOwn(out, k, await presidioWalk(v, ctx, vault, sessionId, hits, itemIndex, child, childPolicy, lock));
|
|
812
852
|
}
|
|
813
853
|
return out;
|
|
814
854
|
}
|
|
@@ -50,6 +50,7 @@ const CLASS_ESCAPES = {
|
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
52
|
const HEX = /^[0-9a-fA-F]+$/;
|
|
53
|
+
const CONTROL = { n: 10, r: 13, t: 9, v: 11, f: 12 };
|
|
53
54
|
function readEscape(src, i) {
|
|
54
55
|
var _a, _b;
|
|
55
56
|
const e = (_a = src[i + 1]) !== null && _a !== void 0 ? _a : '';
|
|
@@ -62,10 +63,18 @@ function readEscape(src, i) {
|
|
|
62
63
|
return { length: 3, code: src.charCodeAt(i + 2) % 32 };
|
|
63
64
|
if (e === '0')
|
|
64
65
|
return { length: 2, code: 0 };
|
|
66
|
+
const control = CONTROL[e];
|
|
67
|
+
if (control !== undefined)
|
|
68
|
+
return { length: 2, code: control };
|
|
65
69
|
if (/[a-zA-Z0-9]/.test(e))
|
|
66
70
|
return { length: 2, code: null };
|
|
67
71
|
return { length: 2, code: e.charCodeAt(0) };
|
|
68
72
|
}
|
|
73
|
+
function codeSet(code) {
|
|
74
|
+
const set = (0, exports.emptySet)();
|
|
75
|
+
addCode(set, code);
|
|
76
|
+
return set;
|
|
77
|
+
}
|
|
69
78
|
function escapeSet(src, i) {
|
|
70
79
|
var _a;
|
|
71
80
|
const { code } = readEscape(src, i);
|
|
@@ -82,6 +91,10 @@ function escapeSet(src, i) {
|
|
|
82
91
|
return letter === letter.toLowerCase() ? set : complement(set);
|
|
83
92
|
}
|
|
84
93
|
function classMember(body, j) {
|
|
94
|
+
if (body[j] === '\\' && body[j + 1] === 'b')
|
|
95
|
+
return { set: codeSet(8), next: j + 2, code: 8 };
|
|
96
|
+
if (body[j] === '\\' && body[j + 1] === 'B')
|
|
97
|
+
return { set: codeSet(66), next: j + 2, code: 66 };
|
|
85
98
|
if (body[j] === '\\') {
|
|
86
99
|
const { length, code } = readEscape(body, j);
|
|
87
100
|
return { set: escapeSet(body, j), next: j + length, code };
|
|
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.checkRegex = checkRegex;
|
|
4
4
|
const regexCharset_1 = require("./regexCharset");
|
|
5
5
|
const MAX_REGEX_LENGTH = 500;
|
|
6
|
-
const newFrame = (zeroWidth = false) => ({
|
|
6
|
+
const newFrame = (zeroWidth = false, inLook = zeroWidth) => ({
|
|
7
7
|
hasVariableQuantifier: false, hasAlternation: false, run: null, chars: (0, regexCharset_1.emptySet)(), zeroWidth,
|
|
8
|
-
branchConsumes: false, allConsume: true, nullable: false,
|
|
8
|
+
branchConsumes: false, allConsume: true, nullable: false, inLook,
|
|
9
9
|
});
|
|
10
10
|
function endBranch(frame) {
|
|
11
11
|
frame.allConsume && (frame.allConsume = frame.branchConsumes);
|
|
@@ -48,7 +48,7 @@ function skipClass(src, i) {
|
|
|
48
48
|
function groupPrefixLength(src, i) {
|
|
49
49
|
if (src[i + 1] !== '?')
|
|
50
50
|
return 1;
|
|
51
|
-
const named = /^\(\?<[
|
|
51
|
+
const named = /^\(\?<[^=!>][^>]*>/.exec(src.slice(i));
|
|
52
52
|
if (named)
|
|
53
53
|
return named[0].length;
|
|
54
54
|
const look = /^\(\?(?::|=|!|<=|<!)/.exec(src.slice(i));
|
|
@@ -79,7 +79,8 @@ function readToken(src, i, stack) {
|
|
|
79
79
|
if (c === '[')
|
|
80
80
|
return { atomEnd: skipClass(src, i), closed: null };
|
|
81
81
|
if (c === '(') {
|
|
82
|
-
|
|
82
|
+
const look = /^\(\?(?:=|!|<=|<!)/.test(src.slice(i));
|
|
83
|
+
stack.push(newFrame(look, look || stack[stack.length - 1].inLook));
|
|
83
84
|
return { atomEnd: i, closed: null, next: i + groupPrefixLength(src, i) };
|
|
84
85
|
}
|
|
85
86
|
if (c === '|')
|
|
@@ -127,8 +128,11 @@ function consumes(q, chars, closed) {
|
|
|
127
128
|
return false;
|
|
128
129
|
return !(closed === null || closed === void 0 ? void 0 : closed.nullable);
|
|
129
130
|
}
|
|
131
|
+
const IN_LOOK = 'repeats a variable part inside a lookahead or lookbehind, which is not supported';
|
|
130
132
|
function stepProblem(src, i, step, q, top) {
|
|
131
133
|
var _a;
|
|
134
|
+
if (top.inLook && isVariable(q))
|
|
135
|
+
return IN_LOOK;
|
|
132
136
|
const problem = applyQuantifier(q, step.closed, top);
|
|
133
137
|
if (problem || (q === null || q === void 0 ? void 0 : q.max) === 0)
|
|
134
138
|
return problem;
|
|
@@ -157,7 +161,45 @@ function structuralProblem(src) {
|
|
|
157
161
|
}
|
|
158
162
|
return null;
|
|
159
163
|
}
|
|
164
|
+
const KNOWN_ESCAPES = /^(?:[dDwWsSbBnrtvf]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|c[A-Za-z]|0(?![0-9])|[^A-Za-z0-9])/;
|
|
165
|
+
const CLASS_ESCAPE_RANGE = /\\[dDwWsS]-[^\]]|[^[^\\]-\\[dDwWsS]/;
|
|
166
|
+
const BAD_ESCAPE = 'uses an escape that is not supported (octal, back-reference or unknown letter)';
|
|
167
|
+
const EMPTY_CLASS = 'uses an empty class [] or [^], which is not supported';
|
|
168
|
+
const ESCAPE_RANGE = 'uses a range with \\d, \\w or \\s as an end, which is not supported';
|
|
169
|
+
function classProblem(src, i) {
|
|
170
|
+
if (/^\[\^?\]/.test(src.slice(i)))
|
|
171
|
+
return { end: i, problem: EMPTY_CLASS };
|
|
172
|
+
const end = skipClass(src, i);
|
|
173
|
+
const body = src.slice(i, end);
|
|
174
|
+
for (let j = 1; j < body.length; j++) {
|
|
175
|
+
if (body[j] === '\\' && !KNOWN_ESCAPES.test(body.slice(j + 1)))
|
|
176
|
+
return { end, problem: BAD_ESCAPE };
|
|
177
|
+
if (body[j] === '\\')
|
|
178
|
+
j++;
|
|
179
|
+
}
|
|
180
|
+
return { end, problem: CLASS_ESCAPE_RANGE.test(body) ? ESCAPE_RANGE : null };
|
|
181
|
+
}
|
|
182
|
+
function unsupportedSyntax(src) {
|
|
183
|
+
let i = 0;
|
|
184
|
+
while (i < src.length) {
|
|
185
|
+
if (src[i] === '[') {
|
|
186
|
+
const { end, problem } = classProblem(src, i);
|
|
187
|
+
if (problem)
|
|
188
|
+
return problem;
|
|
189
|
+
i = end;
|
|
190
|
+
}
|
|
191
|
+
else if (src[i] === '\\') {
|
|
192
|
+
if (!KNOWN_ESCAPES.test(src.slice(i + 1)))
|
|
193
|
+
return BAD_ESCAPE;
|
|
194
|
+
i += 2;
|
|
195
|
+
}
|
|
196
|
+
else
|
|
197
|
+
i++;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
160
201
|
function checkRegex(regexStr) {
|
|
202
|
+
var _a;
|
|
161
203
|
if (typeof regexStr !== 'string' || regexStr.length === 0)
|
|
162
204
|
return 'is empty';
|
|
163
205
|
if (regexStr.length > MAX_REGEX_LENGTH)
|
|
@@ -168,5 +210,5 @@ function checkRegex(regexStr) {
|
|
|
168
210
|
catch {
|
|
169
211
|
return 'is not a valid regular expression';
|
|
170
212
|
}
|
|
171
|
-
return structuralProblem(regexStr);
|
|
213
|
+
return (_a = unsupportedSyntax(regexStr)) !== null && _a !== void 0 ? _a : structuralProblem(regexStr);
|
|
172
214
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-redactor",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.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",
|