n8n-nodes-redactor 3.0.1

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.
Files changed (61) hide show
  1. package/LICENSE +42 -0
  2. package/README.dev.md +153 -0
  3. package/README.md +443 -0
  4. package/README.npm.md +443 -0
  5. package/dist/nodes/PiiRedactor/PiiRedactor.node.d.ts +5 -0
  6. package/dist/nodes/PiiRedactor/PiiRedactor.node.js +1093 -0
  7. package/dist/nodes/PiiRedactor/__tests__/encryption.test.d.ts +1 -0
  8. package/dist/nodes/PiiRedactor/__tests__/encryption.test.js +200 -0
  9. package/dist/nodes/PiiRedactor/__tests__/engine.test.d.ts +1 -0
  10. package/dist/nodes/PiiRedactor/__tests__/engine.test.js +524 -0
  11. package/dist/nodes/PiiRedactor/__tests__/operations.test.d.ts +1 -0
  12. package/dist/nodes/PiiRedactor/__tests__/operations.test.js +316 -0
  13. package/dist/nodes/PiiRedactor/__tests__/patterns-global.test.d.ts +1 -0
  14. package/dist/nodes/PiiRedactor/__tests__/patterns-global.test.js +427 -0
  15. package/dist/nodes/PiiRedactor/__tests__/patterns.test.d.ts +1 -0
  16. package/dist/nodes/PiiRedactor/__tests__/patterns.test.js +481 -0
  17. package/dist/nodes/PiiRedactor/__tests__/phase1.test.d.ts +1 -0
  18. package/dist/nodes/PiiRedactor/__tests__/phase1.test.js +343 -0
  19. package/dist/nodes/PiiRedactor/__tests__/phase3.test.d.ts +1 -0
  20. package/dist/nodes/PiiRedactor/__tests__/phase3.test.js +275 -0
  21. package/dist/nodes/PiiRedactor/__tests__/phase4.test.d.ts +1 -0
  22. package/dist/nodes/PiiRedactor/__tests__/phase4.test.js +184 -0
  23. package/dist/nodes/PiiRedactor/__tests__/presidio.test.d.ts +1 -0
  24. package/dist/nodes/PiiRedactor/__tests__/presidio.test.js +170 -0
  25. package/dist/nodes/PiiRedactor/__tests__/security.test.d.ts +1 -0
  26. package/dist/nodes/PiiRedactor/__tests__/security.test.js +178 -0
  27. package/dist/nodes/PiiRedactor/__tests__/semantic.test.d.ts +1 -0
  28. package/dist/nodes/PiiRedactor/__tests__/semantic.test.js +319 -0
  29. package/dist/nodes/PiiRedactor/__tests__/vault.test.d.ts +1 -0
  30. package/dist/nodes/PiiRedactor/__tests__/vault.test.js +247 -0
  31. package/dist/nodes/PiiRedactor/audit.d.ts +48 -0
  32. package/dist/nodes/PiiRedactor/audit.js +192 -0
  33. package/dist/nodes/PiiRedactor/classification.d.ts +33 -0
  34. package/dist/nodes/PiiRedactor/classification.js +118 -0
  35. package/dist/nodes/PiiRedactor/context.d.ts +57 -0
  36. package/dist/nodes/PiiRedactor/context.js +260 -0
  37. package/dist/nodes/PiiRedactor/encryption.d.ts +45 -0
  38. package/dist/nodes/PiiRedactor/encryption.js +158 -0
  39. package/dist/nodes/PiiRedactor/engine.d.ts +23 -0
  40. package/dist/nodes/PiiRedactor/engine.js +888 -0
  41. package/dist/nodes/PiiRedactor/injection.d.ts +46 -0
  42. package/dist/nodes/PiiRedactor/injection.js +425 -0
  43. package/dist/nodes/PiiRedactor/names.d.ts +25 -0
  44. package/dist/nodes/PiiRedactor/names.js +188 -0
  45. package/dist/nodes/PiiRedactor/patterns.d.ts +17 -0
  46. package/dist/nodes/PiiRedactor/patterns.js +1742 -0
  47. package/dist/nodes/PiiRedactor/presidio.d.ts +77 -0
  48. package/dist/nodes/PiiRedactor/presidio.js +264 -0
  49. package/dist/nodes/PiiRedactor/profiles.d.ts +47 -0
  50. package/dist/nodes/PiiRedactor/profiles.js +139 -0
  51. package/dist/nodes/PiiRedactor/pseudonymize.d.ts +20 -0
  52. package/dist/nodes/PiiRedactor/pseudonymize.js +203 -0
  53. package/dist/nodes/PiiRedactor/redact.png +0 -0
  54. package/dist/nodes/PiiRedactor/redact.svg +3 -0
  55. package/dist/nodes/PiiRedactor/ropa.d.ts +63 -0
  56. package/dist/nodes/PiiRedactor/ropa.js +70 -0
  57. package/dist/nodes/PiiRedactor/types.d.ts +82 -0
  58. package/dist/nodes/PiiRedactor/types.js +3 -0
  59. package/dist/nodes/PiiRedactor/vault.d.ts +61 -0
  60. package/dist/nodes/PiiRedactor/vault.js +352 -0
  61. package/package.json +87 -0
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FileVault = exports.MemoryVault = void 0;
37
+ exports.createVault = createVault;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const crypto = __importStar(require("crypto"));
41
+ const encryption_1 = require("./encryption");
42
+ // ═══════════════════════════════════════════════════════════
43
+ // In-memory vault
44
+ // ═══════════════════════════════════════════════════════════
45
+ const memoryStore = new Map();
46
+ const reverseIndex = new Map();
47
+ const MAX_SESSIONS = 1000; // Prevent unbounded memory growth
48
+ class MemoryVault {
49
+ getOrCreateSession(sessionId, ttl) {
50
+ // Auto-cleanup expired sessions on every create to prevent memory leak
51
+ this.cleanup();
52
+ // Enforce max session limit
53
+ if (memoryStore.size >= MAX_SESSIONS && !memoryStore.has(sessionId)) {
54
+ // Evict oldest session
55
+ const oldest = memoryStore.keys().next().value;
56
+ if (oldest !== undefined) {
57
+ memoryStore.delete(oldest);
58
+ reverseIndex.delete(oldest);
59
+ }
60
+ }
61
+ if (!memoryStore.has(sessionId)) {
62
+ memoryStore.set(sessionId, {
63
+ sessionId,
64
+ entries: {},
65
+ createdAt: new Date().toISOString(),
66
+ ttl,
67
+ });
68
+ }
69
+ return memoryStore.get(sessionId);
70
+ }
71
+ getSession(sessionId) {
72
+ const session = memoryStore.get(sessionId);
73
+ if (!session)
74
+ return null;
75
+ // Check expiry
76
+ if (session.ttl > 0) {
77
+ const created = new Date(session.createdAt).getTime();
78
+ if (Date.now() - created > session.ttl) {
79
+ memoryStore.delete(sessionId);
80
+ return null;
81
+ }
82
+ }
83
+ return session;
84
+ }
85
+ addEntry(sessionId, entry) {
86
+ const session = memoryStore.get(sessionId);
87
+ if (session) {
88
+ session.entries[entry.token] = entry;
89
+ // Maintain reverse lookup index for O(1) dedup
90
+ if (!reverseIndex.has(sessionId))
91
+ reverseIndex.set(sessionId, new Map());
92
+ reverseIndex.get(sessionId).set(entry.original, entry);
93
+ }
94
+ }
95
+ findByOriginal(sessionId, original) {
96
+ // O(1) lookup via reverse index instead of O(n) scan
97
+ const index = reverseIndex.get(sessionId);
98
+ if (index) {
99
+ const entry = index.get(original);
100
+ if (entry)
101
+ return entry;
102
+ }
103
+ // Fallback to linear scan (for sessions loaded without index)
104
+ const session = memoryStore.get(sessionId);
105
+ if (!session)
106
+ return undefined;
107
+ return Object.values(session.entries).find((e) => e.original === original);
108
+ }
109
+ deleteSession(sessionId) {
110
+ memoryStore.delete(sessionId);
111
+ reverseIndex.delete(sessionId);
112
+ }
113
+ listSessions() {
114
+ return Array.from(memoryStore.values()).map((s) => ({
115
+ sessionId: s.sessionId,
116
+ entryCount: Object.keys(s.entries).length,
117
+ createdAt: s.createdAt,
118
+ ttl: s.ttl,
119
+ }));
120
+ }
121
+ save() {
122
+ // No-op for memory vault
123
+ }
124
+ cleanup() {
125
+ let removed = 0;
126
+ const now = Date.now();
127
+ for (const [id, session] of memoryStore.entries()) {
128
+ if (session.ttl > 0) {
129
+ const created = new Date(session.createdAt).getTime();
130
+ if (now - created > session.ttl) {
131
+ memoryStore.delete(id);
132
+ reverseIndex.delete(id);
133
+ removed++;
134
+ }
135
+ }
136
+ }
137
+ return removed;
138
+ }
139
+ }
140
+ exports.MemoryVault = MemoryVault;
141
+ // ═══════════════════════════════════════════════════════════
142
+ // File-based vault (JSON files in a directory)
143
+ // ═══════════════════════════════════════════════════════════
144
+ class FileVault {
145
+ constructor(dir, passphrase) {
146
+ this.cache = new Map();
147
+ this.passphrase = passphrase || '';
148
+ this.dir = dir || path.join(process.env.HOME || '/tmp', '.n8n', 'pii-vault');
149
+ if (!fs.existsSync(this.dir)) {
150
+ fs.mkdirSync(this.dir, { recursive: true });
151
+ }
152
+ // Auto-cleanup expired files on every instantiation
153
+ this.cleanup();
154
+ }
155
+ filePath(sessionId) {
156
+ // Hash session ID for safe filenames — use 32 hex chars (128 bits) to avoid collisions
157
+ const hash = crypto.createHash('sha256').update(sessionId).digest('hex').slice(0, 32);
158
+ return path.join(this.dir, `vault_${hash}.json`);
159
+ }
160
+ load(sessionId) {
161
+ if (this.cache.has(sessionId)) {
162
+ return this.cache.get(sessionId);
163
+ }
164
+ const fp = this.filePath(sessionId);
165
+ if (!fs.existsSync(fp))
166
+ return null;
167
+ try {
168
+ const raw = fs.readFileSync(fp);
169
+ let jsonStr;
170
+ if ((0, encryption_1.isEncryptedVault)(raw)) {
171
+ // Encrypted file — decrypt with passphrase
172
+ if (!this.passphrase) {
173
+ // Encrypted but no passphrase — cannot read
174
+ return null;
175
+ }
176
+ jsonStr = (0, encryption_1.decryptVaultData)(raw, this.passphrase);
177
+ }
178
+ else if ((0, encryption_1.isPlaintextVault)(raw)) {
179
+ // Plaintext JSON — read directly
180
+ jsonStr = raw.toString('utf-8');
181
+ }
182
+ else {
183
+ // Unknown format
184
+ return null;
185
+ }
186
+ const data = JSON.parse(jsonStr);
187
+ // Check expiry
188
+ if (data.ttl > 0) {
189
+ const created = new Date(data.createdAt).getTime();
190
+ if (Date.now() - created > data.ttl) {
191
+ try {
192
+ fs.unlinkSync(fp);
193
+ }
194
+ catch { /* ignore */ }
195
+ return null;
196
+ }
197
+ }
198
+ this.cache.set(sessionId, data);
199
+ return data;
200
+ }
201
+ catch {
202
+ return null;
203
+ }
204
+ }
205
+ getOrCreateSession(sessionId, ttl) {
206
+ // Auto-cleanup expired files before creating new sessions
207
+ this.cleanup();
208
+ const existing = this.load(sessionId);
209
+ if (existing)
210
+ return existing;
211
+ const session = {
212
+ sessionId,
213
+ entries: {},
214
+ createdAt: new Date().toISOString(),
215
+ ttl,
216
+ };
217
+ this.cache.set(sessionId, session);
218
+ return session;
219
+ }
220
+ getSession(sessionId) {
221
+ return this.load(sessionId);
222
+ }
223
+ addEntry(sessionId, entry) {
224
+ const session = this.cache.get(sessionId);
225
+ if (session) {
226
+ session.entries[entry.token] = entry;
227
+ }
228
+ }
229
+ findByOriginal(sessionId, original) {
230
+ const session = this.cache.get(sessionId);
231
+ if (!session)
232
+ return undefined;
233
+ return Object.values(session.entries).find((e) => e.original === original);
234
+ }
235
+ deleteSession(sessionId) {
236
+ this.cache.delete(sessionId);
237
+ try {
238
+ const fp = this.filePath(sessionId);
239
+ if (fs.existsSync(fp)) {
240
+ fs.unlinkSync(fp);
241
+ }
242
+ }
243
+ catch {
244
+ // Ignore file deletion errors (read-only FS, already deleted, etc.)
245
+ }
246
+ }
247
+ listSessions() {
248
+ // List all vault files
249
+ const files = fs.readdirSync(this.dir).filter((f) => f.startsWith('vault_') && f.endsWith('.json'));
250
+ const sessions = [];
251
+ for (const file of files) {
252
+ try {
253
+ const fp = path.join(this.dir, file);
254
+ const raw = fs.readFileSync(fp);
255
+ let jsonStr;
256
+ if ((0, encryption_1.isEncryptedVault)(raw)) {
257
+ if (!this.passphrase)
258
+ continue; // Can't read without passphrase
259
+ try {
260
+ jsonStr = (0, encryption_1.decryptVaultData)(raw, this.passphrase);
261
+ }
262
+ catch {
263
+ continue; // Wrong passphrase or corrupted
264
+ }
265
+ }
266
+ else if ((0, encryption_1.isPlaintextVault)(raw)) {
267
+ jsonStr = raw.toString('utf-8');
268
+ }
269
+ else {
270
+ continue;
271
+ }
272
+ const data = JSON.parse(jsonStr);
273
+ sessions.push({
274
+ sessionId: data.sessionId,
275
+ entryCount: Object.keys(data.entries).length,
276
+ createdAt: data.createdAt,
277
+ ttl: data.ttl,
278
+ });
279
+ }
280
+ catch {
281
+ // Skip corrupt files
282
+ }
283
+ }
284
+ return sessions;
285
+ }
286
+ save(sessionId) {
287
+ const session = this.cache.get(sessionId);
288
+ if (session) {
289
+ try {
290
+ const fp = this.filePath(sessionId);
291
+ const tmpFp = fp + '.tmp';
292
+ const jsonData = JSON.stringify(session, null, 2);
293
+ if (this.passphrase) {
294
+ // Encrypt vault data before writing
295
+ const encrypted = (0, encryption_1.encryptVaultData)(jsonData, this.passphrase);
296
+ fs.writeFileSync(tmpFp, encrypted);
297
+ }
298
+ else {
299
+ // Write plaintext JSON
300
+ fs.writeFileSync(tmpFp, jsonData, 'utf-8');
301
+ }
302
+ fs.renameSync(tmpFp, fp);
303
+ }
304
+ catch {
305
+ // Write failed (read-only FS, permissions, disk full).
306
+ // Session stays in memory cache — restore will still work
307
+ // for this execution, just won't persist across restarts.
308
+ }
309
+ }
310
+ }
311
+ cleanup() {
312
+ let removed = 0;
313
+ const now = Date.now();
314
+ const files = fs.readdirSync(this.dir).filter((f) => f.startsWith('vault_') && f.endsWith('.json'));
315
+ for (const file of files) {
316
+ try {
317
+ const fp = path.join(this.dir, file);
318
+ const data = JSON.parse(fs.readFileSync(fp, 'utf-8'));
319
+ if (data.ttl > 0) {
320
+ const created = new Date(data.createdAt).getTime();
321
+ if (now - created > data.ttl) {
322
+ fs.unlinkSync(fp);
323
+ this.cache.delete(data.sessionId);
324
+ removed++;
325
+ }
326
+ }
327
+ }
328
+ catch {
329
+ // Skip
330
+ }
331
+ }
332
+ return removed;
333
+ }
334
+ }
335
+ exports.FileVault = FileVault;
336
+ /**
337
+ * Factory to get the right vault implementation.
338
+ * If file vault fails (read-only filesystem, no permissions, Docker, cloud),
339
+ * automatically falls back to memory vault so the node never crashes.
340
+ */
341
+ function createVault(storage, vaultDir, passphrase) {
342
+ if (storage === 'file') {
343
+ try {
344
+ return new FileVault(vaultDir, passphrase);
345
+ }
346
+ catch {
347
+ // Filesystem not writable — fall back to memory vault silently
348
+ return new MemoryVault();
349
+ }
350
+ }
351
+ return new MemoryVault();
352
+ }
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "n8n-nodes-redactor",
3
+ "version": "3.0.1",
4
+ "description": "Redact, remove, mask and anonymize sensitive data before LLMs. Detect PII, personal information, emails, phones, addresses, IBAN, credit cards, names, SSN, passport, tax ID. Reversible vault restore. GDPR HIPAA CCPA DACH EU compliant. 50+ patterns. 100% local. Built by next8n.",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "n8n",
8
+ "redactor",
9
+ "redact",
10
+ "pii",
11
+ "remove-data",
12
+ "remove-pii",
13
+ "data-masking",
14
+ "data-privacy",
15
+ "data-protection",
16
+ "anonymize",
17
+ "anonymization",
18
+ "sanitize",
19
+ "scrub",
20
+ "mask",
21
+ "gdpr",
22
+ "hipaa",
23
+ "ccpa",
24
+ "dsgvo",
25
+ "compliance",
26
+ "privacy",
27
+ "personal-data",
28
+ "sensitive-data",
29
+ "iban",
30
+ "credit-card",
31
+ "ssn",
32
+ "email",
33
+ "phone",
34
+ "address",
35
+ "passport",
36
+ "tax-id",
37
+ "eu",
38
+ "dach",
39
+ "european",
40
+ "llm",
41
+ "openai",
42
+ "claude",
43
+ "ai",
44
+ "next8n",
45
+ "workflow",
46
+ "automation"
47
+ ],
48
+ "author": "Mirza Iqbal / next8n (https://next8n.com)",
49
+ "license": "FUCL",
50
+ "homepage": "https://next8n.com",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/mjmirza/n8n-nodes-redactor.git"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/mjmirza/n8n-nodes-redactor/issues"
57
+ },
58
+ "main": "dist/nodes/PiiRedactor/PiiRedactor.node.js",
59
+ "types": "dist/nodes/PiiRedactor/PiiRedactor.node.d.ts",
60
+ "scripts": {
61
+ "build": "tsc -p tsconfig.json && cp src/nodes/PiiRedactor/redact.png dist/nodes/PiiRedactor/redact.png",
62
+ "dev": "tsc -w -p tsconfig.json",
63
+ "test": "jest --no-cache",
64
+ "lint": "eslint src/ --ext .ts",
65
+ "prepublishOnly": "cp README.md README.dev.md && cp README.npm.md README.md && npm run build",
66
+ "postpublish": "mv README.dev.md README.md"
67
+ },
68
+ "files": [
69
+ "dist"
70
+ ],
71
+ "n8n": {
72
+ "n8nNodesApiVersion": 1,
73
+ "nodes": [
74
+ "dist/nodes/PiiRedactor/PiiRedactor.node.js"
75
+ ]
76
+ },
77
+ "devDependencies": {
78
+ "@types/jest": "^30.0.0",
79
+ "@types/node": "^20.0.0",
80
+ "jest": "^29.7.0",
81
+ "ts-jest": "^29.4.6",
82
+ "typescript": "^5.4.0"
83
+ },
84
+ "peerDependencies": {
85
+ "n8n-workflow": "*"
86
+ }
87
+ }