n8n-nodes-redactor 2.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.
Potentially problematic release.
This version of n8n-nodes-redactor might be problematic. Click here for more details.
- package/LICENSE +42 -0
- package/README.dev.md +134 -0
- package/README.md +376 -0
- package/README.npm.md +376 -0
- package/dist/nodes/PiiRedactor/PiiRedactor.node.d.ts +5 -0
- package/dist/nodes/PiiRedactor/PiiRedactor.node.js +872 -0
- package/dist/nodes/PiiRedactor/__tests__/engine.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/engine.test.js +524 -0
- package/dist/nodes/PiiRedactor/__tests__/operations.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/operations.test.js +316 -0
- package/dist/nodes/PiiRedactor/__tests__/patterns-global.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/patterns-global.test.js +427 -0
- package/dist/nodes/PiiRedactor/__tests__/patterns.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/patterns.test.js +481 -0
- package/dist/nodes/PiiRedactor/__tests__/phase1.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/phase1.test.js +343 -0
- package/dist/nodes/PiiRedactor/__tests__/security.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/security.test.js +178 -0
- package/dist/nodes/PiiRedactor/__tests__/semantic.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/semantic.test.js +319 -0
- package/dist/nodes/PiiRedactor/__tests__/vault.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/vault.test.js +247 -0
- package/dist/nodes/PiiRedactor/context.d.ts +57 -0
- package/dist/nodes/PiiRedactor/context.js +260 -0
- package/dist/nodes/PiiRedactor/engine.d.ts +17 -0
- package/dist/nodes/PiiRedactor/engine.js +813 -0
- package/dist/nodes/PiiRedactor/names.d.ts +25 -0
- package/dist/nodes/PiiRedactor/names.js +188 -0
- package/dist/nodes/PiiRedactor/patterns.d.ts +17 -0
- package/dist/nodes/PiiRedactor/patterns.js +1741 -0
- package/dist/nodes/PiiRedactor/redact.png +0 -0
- package/dist/nodes/PiiRedactor/redact.svg +3 -0
- package/dist/nodes/PiiRedactor/types.d.ts +78 -0
- package/dist/nodes/PiiRedactor/types.js +3 -0
- package/dist/nodes/PiiRedactor/vault.d.ts +60 -0
- package/dist/nodes/PiiRedactor/vault.js +299 -0
- package/package.json +87 -0
|
Binary file
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="60" height="60" viewBox="0 0 60 60">
|
|
2
|
+
<image width="60" height="60" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAAtGVYSWZJSSoACAAAAAYAEgEDAAEAAAABAAAAGgEFAAEAAABWAAAAGwEFAAEAAABeAAAAKAEDAAEAAAACAAAAEwIDAAEAAAABAAAAaYcEAAEAAABmAAAAAAAAAGAAAAABAAAAYAAAAAEAAAAGAACQBwAEAAAAMDIxMAGRBwAEAAAAAQIDAACgBwAEAAAAMDEwMAGgAwABAAAA//8AAAKgBAABAAAAkAEAAAOgBAABAAAAkAEAAAAAAAA1qXZBAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFwGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSfvu78nIGlkPSdXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQnPz4KPHg6eG1wbWV0YSB4bWxuczp4PSdhZG9iZTpuczptZXRhLyc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyc+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczpBdHRyaWI9J2h0dHA6Ly9ucy5hdHRyaWJ1dGlvbi5jb20vYWRzLzEuMC8nPgogIDxBdHRyaWI6QWRzPgogICA8cmRmOlNlcT4KICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0nUmVzb3VyY2UnPgogICAgIDxBdHRyaWI6Q3JlYXRlZD4yMDI2LTAzLTE0PC9BdHRyaWI6Q3JlYXRlZD4KICAgICA8QXR0cmliOkRhdGE+eyZxdW90O2RvYyZxdW90OzomcXVvdDtEQUc2anFFdGhHMCZxdW90OywmcXVvdDt1c2VyJnF1b3Q7OiZxdW90O1VBRWt2VDU2eXVJJnF1b3Q7LCZxdW90O2JyYW5kJnF1b3Q7OiZxdW90O01JICZhbXA7IEFaJnF1b3Q7LCZxdW90O3RlbXBsYXRlJnF1b3Q7OiZxdW90O09yYW5nZSBCbHVlIE1pbmltYWxpc3QgTW9kZXJuIFByb3RlY3QgTG9nbyAmcXVvdDt9PC9BdHRyaWI6RGF0YT4KICAgICA8QXR0cmliOkV4dElkPjMxYTE3OWFiLWFhZDUtNDM1My05MjM5LWZmNzMxOWUwMGY0NTwvQXR0cmliOkV4dElkPgogICAgIDxBdHRyaWI6RmJJZD41MjUyNjU5MTQxNzk1ODA8L0F0dHJpYjpGYklkPgogICAgIDxBdHRyaWI6VG91Y2hUeXBlPjI8L0F0dHJpYjpUb3VjaFR5cGU+CiAgICA8L3JkZjpsaT4KICAgPC9yZGY6U2VxPgogIDwvQXR0cmliOkFkcz4KIDwvcmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PScnCiAgeG1sbnM6ZGM9J2h0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvJz4KICA8ZGM6dGl0bGU+CiAgIDxyZGY6QWx0PgogICAgPHJkZjpsaSB4bWw6bGFuZz0neC1kZWZhdWx0Jz5mYXZpY29uIC0gcmVkYWN0PC9yZGY6bGk+CiAgIDwvcmRmOkFsdD4KICA8L2RjOnRpdGxlPgogPC9yZGY6RGVzY3JpcHRpb24+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczpwZGY9J2h0dHA6Ly9ucy5hZG9iZS5jb20vcGRmLzEuMy8nPgogIDxwZGY6QXV0aG9yPk1JICZhbXA7IEE8L3BkZjpBdXRob3I+CiA8L3JkZjpEZXNjcmlwdGlvbj4KCiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0nJwogIHhtbG5zOnhtcD0naHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyc+CiAgPHhtcDpDcmVhdG9yVG9vbD5DYW52YSBkb2M9REFHNmpxRXRoRzAgdXNlcj1VQUVrdlQ1Nnl1SSBicmFuZD1NSSAmYW1wOyBBWiB0ZW1wbGF0ZT1PcmFuZ2UgQmx1ZSBNaW5pbWFsaXN0IE1vZGVybiBQcm90ZWN0IExvZ28gPC94bXA6Q3JlYXRvclRvb2w+CiA8L3JkZjpEZXNjcmlwdGlvbj4KPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0ncic/PlPvXF4AACAASURBVHic7Z0JuBXVle+ZRQTJ5WpM0oma+KXzuu0kL+l0vu5093vpdL8kGpUZ04IIEUdwiHMcouLQJnBn7mVGJkWQ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"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Shared type definitions for the PII Redactor node */
|
|
2
|
+
export interface PiiPattern {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
category: PiiCategory;
|
|
6
|
+
regex: RegExp;
|
|
7
|
+
/** Optional validator to reduce false positives */
|
|
8
|
+
validate?: (match: string) => boolean;
|
|
9
|
+
/** Description shown in the UI */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Context words that boost confidence when found near a match */
|
|
12
|
+
contextWords?: string[];
|
|
13
|
+
/** Base confidence score (0.0 to 1.0) before context boosting */
|
|
14
|
+
baseConfidence?: number;
|
|
15
|
+
}
|
|
16
|
+
export type PiiCategory = 'identity' | 'financial' | 'contact' | 'network' | 'location' | 'temporal' | 'medical' | 'crypto' | 'enterprise' | 'vehicle' | 'biometric' | 'other';
|
|
17
|
+
export type RedactionMode = 'token' | 'mask' | 'hash' | 'redact';
|
|
18
|
+
export type VaultStorage = 'memory' | 'file';
|
|
19
|
+
export interface RedactionHit {
|
|
20
|
+
token: string;
|
|
21
|
+
original: string;
|
|
22
|
+
patternName: string;
|
|
23
|
+
patternLabel: string;
|
|
24
|
+
category: PiiCategory;
|
|
25
|
+
field: string;
|
|
26
|
+
itemIndex: number;
|
|
27
|
+
/** Confidence score 0.0 to 1.0 */
|
|
28
|
+
confidence: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RedactionReport {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
totalHits: number;
|
|
34
|
+
hitsByCategory: Record<string, number>;
|
|
35
|
+
hitsByPattern: Record<string, number>;
|
|
36
|
+
hits: RedactionHit[];
|
|
37
|
+
}
|
|
38
|
+
export interface VaultEntry {
|
|
39
|
+
token: string;
|
|
40
|
+
original: string;
|
|
41
|
+
patternLabel: string;
|
|
42
|
+
category: PiiCategory;
|
|
43
|
+
createdAt: string;
|
|
44
|
+
}
|
|
45
|
+
export interface VaultSession {
|
|
46
|
+
sessionId: string;
|
|
47
|
+
entries: Record<string, VaultEntry>;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
ttl: number;
|
|
50
|
+
}
|
|
51
|
+
export interface CustomPattern {
|
|
52
|
+
label: string;
|
|
53
|
+
regex: string;
|
|
54
|
+
category?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface FieldRule {
|
|
57
|
+
field: string;
|
|
58
|
+
mode: 'include' | 'exclude';
|
|
59
|
+
}
|
|
60
|
+
export interface AllowDenyEntry {
|
|
61
|
+
value: string;
|
|
62
|
+
type: 'exact' | 'contains' | 'regex';
|
|
63
|
+
}
|
|
64
|
+
export interface RedactionContext {
|
|
65
|
+
enabledPatterns: string[];
|
|
66
|
+
customPatterns: CustomPattern[];
|
|
67
|
+
mode: RedactionMode;
|
|
68
|
+
dedup: boolean;
|
|
69
|
+
fieldRules: FieldRule[];
|
|
70
|
+
fieldMode: 'all' | 'allowlist' | 'denylist';
|
|
71
|
+
skipSemantic?: boolean;
|
|
72
|
+
/** Values to NEVER redact (bypass) */
|
|
73
|
+
allowList?: AllowDenyEntry[];
|
|
74
|
+
/** Values to ALWAYS redact even if no pattern matches */
|
|
75
|
+
denyList?: AllowDenyEntry[];
|
|
76
|
+
/** Minimum confidence score to trigger redaction (0.0 to 1.0) */
|
|
77
|
+
confidenceThreshold?: number;
|
|
78
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { VaultEntry, VaultSession } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Abstract vault interface — storage backend for token ↔ original mappings.
|
|
4
|
+
*/
|
|
5
|
+
export interface IVault {
|
|
6
|
+
getOrCreateSession(sessionId: string, ttl: number): VaultSession;
|
|
7
|
+
getSession(sessionId: string): VaultSession | null;
|
|
8
|
+
addEntry(sessionId: string, entry: VaultEntry): void;
|
|
9
|
+
findByOriginal(sessionId: string, original: string): VaultEntry | undefined;
|
|
10
|
+
deleteSession(sessionId: string): void;
|
|
11
|
+
listSessions(): Array<{
|
|
12
|
+
sessionId: string;
|
|
13
|
+
entryCount: number;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
ttl: number;
|
|
16
|
+
}>;
|
|
17
|
+
save(sessionId: string): void;
|
|
18
|
+
cleanup(): number;
|
|
19
|
+
}
|
|
20
|
+
export declare class MemoryVault implements IVault {
|
|
21
|
+
getOrCreateSession(sessionId: string, ttl: number): VaultSession;
|
|
22
|
+
getSession(sessionId: string): VaultSession | null;
|
|
23
|
+
addEntry(sessionId: string, entry: VaultEntry): void;
|
|
24
|
+
findByOriginal(sessionId: string, original: string): VaultEntry | undefined;
|
|
25
|
+
deleteSession(sessionId: string): void;
|
|
26
|
+
listSessions(): Array<{
|
|
27
|
+
sessionId: string;
|
|
28
|
+
entryCount: number;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
ttl: number;
|
|
31
|
+
}>;
|
|
32
|
+
save(): void;
|
|
33
|
+
cleanup(): number;
|
|
34
|
+
}
|
|
35
|
+
export declare class FileVault implements IVault {
|
|
36
|
+
private dir;
|
|
37
|
+
private cache;
|
|
38
|
+
constructor(dir?: string);
|
|
39
|
+
private filePath;
|
|
40
|
+
private load;
|
|
41
|
+
getOrCreateSession(sessionId: string, ttl: number): VaultSession;
|
|
42
|
+
getSession(sessionId: string): VaultSession | null;
|
|
43
|
+
addEntry(sessionId: string, entry: VaultEntry): void;
|
|
44
|
+
findByOriginal(sessionId: string, original: string): VaultEntry | undefined;
|
|
45
|
+
deleteSession(sessionId: string): void;
|
|
46
|
+
listSessions(): Array<{
|
|
47
|
+
sessionId: string;
|
|
48
|
+
entryCount: number;
|
|
49
|
+
createdAt: string;
|
|
50
|
+
ttl: number;
|
|
51
|
+
}>;
|
|
52
|
+
save(sessionId: string): void;
|
|
53
|
+
cleanup(): number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Factory to get the right vault implementation.
|
|
57
|
+
* If file vault fails (read-only filesystem, no permissions, Docker, cloud),
|
|
58
|
+
* automatically falls back to memory vault so the node never crashes.
|
|
59
|
+
*/
|
|
60
|
+
export declare function createVault(storage: string, vaultDir?: string): IVault;
|
|
@@ -0,0 +1,299 @@
|
|
|
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
|
+
// ═══════════════════════════════════════════════════════════
|
|
42
|
+
// In-memory vault
|
|
43
|
+
// ═══════════════════════════════════════════════════════════
|
|
44
|
+
const memoryStore = new Map();
|
|
45
|
+
const reverseIndex = new Map();
|
|
46
|
+
const MAX_SESSIONS = 1000; // Prevent unbounded memory growth
|
|
47
|
+
class MemoryVault {
|
|
48
|
+
getOrCreateSession(sessionId, ttl) {
|
|
49
|
+
// Auto-cleanup expired sessions on every create to prevent memory leak
|
|
50
|
+
this.cleanup();
|
|
51
|
+
// Enforce max session limit
|
|
52
|
+
if (memoryStore.size >= MAX_SESSIONS && !memoryStore.has(sessionId)) {
|
|
53
|
+
// Evict oldest session
|
|
54
|
+
const oldest = memoryStore.keys().next().value;
|
|
55
|
+
if (oldest !== undefined)
|
|
56
|
+
memoryStore.delete(oldest);
|
|
57
|
+
}
|
|
58
|
+
if (!memoryStore.has(sessionId)) {
|
|
59
|
+
memoryStore.set(sessionId, {
|
|
60
|
+
sessionId,
|
|
61
|
+
entries: {},
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
ttl,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return memoryStore.get(sessionId);
|
|
67
|
+
}
|
|
68
|
+
getSession(sessionId) {
|
|
69
|
+
const session = memoryStore.get(sessionId);
|
|
70
|
+
if (!session)
|
|
71
|
+
return null;
|
|
72
|
+
// Check expiry
|
|
73
|
+
if (session.ttl > 0) {
|
|
74
|
+
const created = new Date(session.createdAt).getTime();
|
|
75
|
+
if (Date.now() - created > session.ttl) {
|
|
76
|
+
memoryStore.delete(sessionId);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return session;
|
|
81
|
+
}
|
|
82
|
+
addEntry(sessionId, entry) {
|
|
83
|
+
const session = memoryStore.get(sessionId);
|
|
84
|
+
if (session) {
|
|
85
|
+
session.entries[entry.token] = entry;
|
|
86
|
+
// Maintain reverse lookup index for O(1) dedup
|
|
87
|
+
if (!reverseIndex.has(sessionId))
|
|
88
|
+
reverseIndex.set(sessionId, new Map());
|
|
89
|
+
reverseIndex.get(sessionId).set(entry.original, entry);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
findByOriginal(sessionId, original) {
|
|
93
|
+
// O(1) lookup via reverse index instead of O(n) scan
|
|
94
|
+
const index = reverseIndex.get(sessionId);
|
|
95
|
+
if (index) {
|
|
96
|
+
const entry = index.get(original);
|
|
97
|
+
if (entry)
|
|
98
|
+
return entry;
|
|
99
|
+
}
|
|
100
|
+
// Fallback to linear scan (for sessions loaded without index)
|
|
101
|
+
const session = memoryStore.get(sessionId);
|
|
102
|
+
if (!session)
|
|
103
|
+
return undefined;
|
|
104
|
+
return Object.values(session.entries).find((e) => e.original === original);
|
|
105
|
+
}
|
|
106
|
+
deleteSession(sessionId) {
|
|
107
|
+
memoryStore.delete(sessionId);
|
|
108
|
+
reverseIndex.delete(sessionId);
|
|
109
|
+
}
|
|
110
|
+
listSessions() {
|
|
111
|
+
return Array.from(memoryStore.values()).map((s) => ({
|
|
112
|
+
sessionId: s.sessionId,
|
|
113
|
+
entryCount: Object.keys(s.entries).length,
|
|
114
|
+
createdAt: s.createdAt,
|
|
115
|
+
ttl: s.ttl,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
save() {
|
|
119
|
+
// No-op for memory vault
|
|
120
|
+
}
|
|
121
|
+
cleanup() {
|
|
122
|
+
let removed = 0;
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
for (const [id, session] of memoryStore.entries()) {
|
|
125
|
+
if (session.ttl > 0) {
|
|
126
|
+
const created = new Date(session.createdAt).getTime();
|
|
127
|
+
if (now - created > session.ttl) {
|
|
128
|
+
memoryStore.delete(id);
|
|
129
|
+
removed++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return removed;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.MemoryVault = MemoryVault;
|
|
137
|
+
// ═══════════════════════════════════════════════════════════
|
|
138
|
+
// File-based vault (JSON files in a directory)
|
|
139
|
+
// ═══════════════════════════════════════════════════════════
|
|
140
|
+
class FileVault {
|
|
141
|
+
constructor(dir) {
|
|
142
|
+
this.cache = new Map();
|
|
143
|
+
this.dir = dir || path.join(process.env.HOME || '/tmp', '.n8n', 'pii-vault');
|
|
144
|
+
if (!fs.existsSync(this.dir)) {
|
|
145
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
// Auto-cleanup expired files on every instantiation
|
|
148
|
+
this.cleanup();
|
|
149
|
+
}
|
|
150
|
+
filePath(sessionId) {
|
|
151
|
+
// Hash session ID for safe filenames — use 32 hex chars (128 bits) to avoid collisions
|
|
152
|
+
const hash = crypto.createHash('sha256').update(sessionId).digest('hex').slice(0, 32);
|
|
153
|
+
return path.join(this.dir, `vault_${hash}.json`);
|
|
154
|
+
}
|
|
155
|
+
load(sessionId) {
|
|
156
|
+
if (this.cache.has(sessionId)) {
|
|
157
|
+
return this.cache.get(sessionId);
|
|
158
|
+
}
|
|
159
|
+
const fp = this.filePath(sessionId);
|
|
160
|
+
if (!fs.existsSync(fp))
|
|
161
|
+
return null;
|
|
162
|
+
try {
|
|
163
|
+
const data = JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
164
|
+
// Check expiry
|
|
165
|
+
if (data.ttl > 0) {
|
|
166
|
+
const created = new Date(data.createdAt).getTime();
|
|
167
|
+
if (Date.now() - created > data.ttl) {
|
|
168
|
+
fs.unlinkSync(fp);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.cache.set(sessionId, data);
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
getOrCreateSession(sessionId, ttl) {
|
|
180
|
+
// Auto-cleanup expired files before creating new sessions
|
|
181
|
+
this.cleanup();
|
|
182
|
+
const existing = this.load(sessionId);
|
|
183
|
+
if (existing)
|
|
184
|
+
return existing;
|
|
185
|
+
const session = {
|
|
186
|
+
sessionId,
|
|
187
|
+
entries: {},
|
|
188
|
+
createdAt: new Date().toISOString(),
|
|
189
|
+
ttl,
|
|
190
|
+
};
|
|
191
|
+
this.cache.set(sessionId, session);
|
|
192
|
+
return session;
|
|
193
|
+
}
|
|
194
|
+
getSession(sessionId) {
|
|
195
|
+
return this.load(sessionId);
|
|
196
|
+
}
|
|
197
|
+
addEntry(sessionId, entry) {
|
|
198
|
+
const session = this.cache.get(sessionId);
|
|
199
|
+
if (session) {
|
|
200
|
+
session.entries[entry.token] = entry;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
findByOriginal(sessionId, original) {
|
|
204
|
+
const session = this.cache.get(sessionId);
|
|
205
|
+
if (!session)
|
|
206
|
+
return undefined;
|
|
207
|
+
return Object.values(session.entries).find((e) => e.original === original);
|
|
208
|
+
}
|
|
209
|
+
deleteSession(sessionId) {
|
|
210
|
+
this.cache.delete(sessionId);
|
|
211
|
+
try {
|
|
212
|
+
const fp = this.filePath(sessionId);
|
|
213
|
+
if (fs.existsSync(fp)) {
|
|
214
|
+
fs.unlinkSync(fp);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Ignore file deletion errors (read-only FS, already deleted, etc.)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
listSessions() {
|
|
222
|
+
// List all vault files
|
|
223
|
+
const files = fs.readdirSync(this.dir).filter((f) => f.startsWith('vault_') && f.endsWith('.json'));
|
|
224
|
+
const sessions = [];
|
|
225
|
+
for (const file of files) {
|
|
226
|
+
try {
|
|
227
|
+
const data = JSON.parse(fs.readFileSync(path.join(this.dir, file), 'utf-8'));
|
|
228
|
+
sessions.push({
|
|
229
|
+
sessionId: data.sessionId,
|
|
230
|
+
entryCount: Object.keys(data.entries).length,
|
|
231
|
+
createdAt: data.createdAt,
|
|
232
|
+
ttl: data.ttl,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Skip corrupt files
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return sessions;
|
|
240
|
+
}
|
|
241
|
+
save(sessionId) {
|
|
242
|
+
const session = this.cache.get(sessionId);
|
|
243
|
+
if (session) {
|
|
244
|
+
try {
|
|
245
|
+
const fp = this.filePath(sessionId);
|
|
246
|
+
const tmpFp = fp + '.tmp';
|
|
247
|
+
// Atomic write: write to temp file first, then rename
|
|
248
|
+
fs.writeFileSync(tmpFp, JSON.stringify(session, null, 2), 'utf-8');
|
|
249
|
+
fs.renameSync(tmpFp, fp);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// Write failed (read-only FS, permissions, disk full).
|
|
253
|
+
// Session stays in memory cache — restore will still work
|
|
254
|
+
// for this execution, just won't persist across restarts.
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
cleanup() {
|
|
259
|
+
let removed = 0;
|
|
260
|
+
const now = Date.now();
|
|
261
|
+
const files = fs.readdirSync(this.dir).filter((f) => f.startsWith('vault_') && f.endsWith('.json'));
|
|
262
|
+
for (const file of files) {
|
|
263
|
+
try {
|
|
264
|
+
const fp = path.join(this.dir, file);
|
|
265
|
+
const data = JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
266
|
+
if (data.ttl > 0) {
|
|
267
|
+
const created = new Date(data.createdAt).getTime();
|
|
268
|
+
if (now - created > data.ttl) {
|
|
269
|
+
fs.unlinkSync(fp);
|
|
270
|
+
this.cache.delete(data.sessionId);
|
|
271
|
+
removed++;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// Skip
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return removed;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
exports.FileVault = FileVault;
|
|
283
|
+
/**
|
|
284
|
+
* Factory to get the right vault implementation.
|
|
285
|
+
* If file vault fails (read-only filesystem, no permissions, Docker, cloud),
|
|
286
|
+
* automatically falls back to memory vault so the node never crashes.
|
|
287
|
+
*/
|
|
288
|
+
function createVault(storage, vaultDir) {
|
|
289
|
+
if (storage === 'file') {
|
|
290
|
+
try {
|
|
291
|
+
return new FileVault(vaultDir);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// Filesystem not writable — fall back to memory vault silently
|
|
295
|
+
return new MemoryVault();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return new MemoryVault();
|
|
299
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-redactor",
|
|
3
|
+
"version": "2.0.0",
|
|
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
|
+
}
|