n8n-nodes-redactor 1.10.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.
- package/LICENSE +42 -0
- package/README.dev.md +443 -0
- package/README.md +443 -0
- package/README.npm.md +443 -0
- package/dist/nodes/PiiRedactor/PiiRedactor.node.d.ts +5 -0
- package/dist/nodes/PiiRedactor/PiiRedactor.node.js +1093 -0
- package/dist/nodes/PiiRedactor/__tests__/encryption.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/encryption.test.js +200 -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__/phase3.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/phase3.test.js +275 -0
- package/dist/nodes/PiiRedactor/__tests__/phase4.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/phase4.test.js +184 -0
- package/dist/nodes/PiiRedactor/__tests__/presidio.test.d.ts +1 -0
- package/dist/nodes/PiiRedactor/__tests__/presidio.test.js +170 -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/audit.d.ts +48 -0
- package/dist/nodes/PiiRedactor/audit.js +192 -0
- package/dist/nodes/PiiRedactor/classification.d.ts +33 -0
- package/dist/nodes/PiiRedactor/classification.js +118 -0
- package/dist/nodes/PiiRedactor/context.d.ts +57 -0
- package/dist/nodes/PiiRedactor/context.js +260 -0
- package/dist/nodes/PiiRedactor/encryption.d.ts +45 -0
- package/dist/nodes/PiiRedactor/encryption.js +158 -0
- package/dist/nodes/PiiRedactor/engine.d.ts +23 -0
- package/dist/nodes/PiiRedactor/engine.js +888 -0
- package/dist/nodes/PiiRedactor/injection.d.ts +46 -0
- package/dist/nodes/PiiRedactor/injection.js +425 -0
- package/dist/nodes/PiiRedactor/names.d.ts +28 -0
- package/dist/nodes/PiiRedactor/names.js +1677 -0
- package/dist/nodes/PiiRedactor/patterns.d.ts +17 -0
- package/dist/nodes/PiiRedactor/patterns.js +1742 -0
- package/dist/nodes/PiiRedactor/presidio.d.ts +77 -0
- package/dist/nodes/PiiRedactor/presidio.js +264 -0
- package/dist/nodes/PiiRedactor/profiles.d.ts +47 -0
- package/dist/nodes/PiiRedactor/profiles.js +139 -0
- package/dist/nodes/PiiRedactor/pseudonymize.d.ts +20 -0
- package/dist/nodes/PiiRedactor/pseudonymize.js +203 -0
- package/dist/nodes/PiiRedactor/redact.png +0 -0
- package/dist/nodes/PiiRedactor/redact.svg +3 -0
- package/dist/nodes/PiiRedactor/ropa.d.ts +63 -0
- package/dist/nodes/PiiRedactor/ropa.js +70 -0
- package/dist/nodes/PiiRedactor/types.d.ts +82 -0
- package/dist/nodes/PiiRedactor/types.js +3 -0
- package/dist/nodes/PiiRedactor/vault.d.ts +61 -0
- package/dist/nodes/PiiRedactor/vault.js +352 -0
- package/package.json +87 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Injection Detection Engine
|
|
3
|
+
*
|
|
4
|
+
* Detects prompt injection attempts in text before it reaches an LLM.
|
|
5
|
+
* Based on OWASP LLM Top 10 (2025), LLM Guard, Rebuff, and Lakera Guard.
|
|
6
|
+
*
|
|
7
|
+
* Categories:
|
|
8
|
+
* 1. Direct instruction override ("ignore previous instructions")
|
|
9
|
+
* 2. Role manipulation / jailbreak ("you are now DAN")
|
|
10
|
+
* 3. System prompt extraction ("reveal your system prompt")
|
|
11
|
+
* 4. Delimiter / context escape ([SYSTEM], <<SYS>>)
|
|
12
|
+
* 5. Encoded payloads (base64, URL-encoded, Unicode)
|
|
13
|
+
* 6. Zero-width character injection
|
|
14
|
+
*
|
|
15
|
+
* Response actions:
|
|
16
|
+
* BLOCK (score >= 0.7) - High confidence injection
|
|
17
|
+
* FLAG (score >= 0.3) - Suspicious, review recommended
|
|
18
|
+
* LOG_ONLY (score < 0.3) - Low-confidence match
|
|
19
|
+
*/
|
|
20
|
+
export type InjectionAction = 'BLOCK' | 'FLAG' | 'LOG_ONLY' | 'CLEAN';
|
|
21
|
+
export type InjectionSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
22
|
+
export interface InjectionMatch {
|
|
23
|
+
category: string;
|
|
24
|
+
pattern: string;
|
|
25
|
+
matchedText: string;
|
|
26
|
+
severity: InjectionSeverity;
|
|
27
|
+
score: number;
|
|
28
|
+
}
|
|
29
|
+
export interface PromptInjectionResult {
|
|
30
|
+
detected: boolean;
|
|
31
|
+
score: number;
|
|
32
|
+
action: InjectionAction;
|
|
33
|
+
matchedPatterns: InjectionMatch[];
|
|
34
|
+
encodedPayloadsFound: boolean;
|
|
35
|
+
zeroWidthCharsFound: boolean;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Detect prompt injection attempts in text.
|
|
40
|
+
*/
|
|
41
|
+
export declare function detectPromptInjection(text: string): PromptInjectionResult;
|
|
42
|
+
/**
|
|
43
|
+
* Scan all string values in a data object for prompt injection.
|
|
44
|
+
* Returns the highest-severity result across all fields.
|
|
45
|
+
*/
|
|
46
|
+
export declare function scanForInjection(data: Record<string, unknown>, currentPath?: string): PromptInjectionResult;
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Prompt Injection Detection Engine
|
|
4
|
+
*
|
|
5
|
+
* Detects prompt injection attempts in text before it reaches an LLM.
|
|
6
|
+
* Based on OWASP LLM Top 10 (2025), LLM Guard, Rebuff, and Lakera Guard.
|
|
7
|
+
*
|
|
8
|
+
* Categories:
|
|
9
|
+
* 1. Direct instruction override ("ignore previous instructions")
|
|
10
|
+
* 2. Role manipulation / jailbreak ("you are now DAN")
|
|
11
|
+
* 3. System prompt extraction ("reveal your system prompt")
|
|
12
|
+
* 4. Delimiter / context escape ([SYSTEM], <<SYS>>)
|
|
13
|
+
* 5. Encoded payloads (base64, URL-encoded, Unicode)
|
|
14
|
+
* 6. Zero-width character injection
|
|
15
|
+
*
|
|
16
|
+
* Response actions:
|
|
17
|
+
* BLOCK (score >= 0.7) - High confidence injection
|
|
18
|
+
* FLAG (score >= 0.3) - Suspicious, review recommended
|
|
19
|
+
* LOG_ONLY (score < 0.3) - Low-confidence match
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.detectPromptInjection = detectPromptInjection;
|
|
23
|
+
exports.scanForInjection = scanForInjection;
|
|
24
|
+
const INJECTION_PATTERNS = [
|
|
25
|
+
// ═══════════════════════════════════════
|
|
26
|
+
// Category 1: Direct Instruction Override
|
|
27
|
+
// ═══════════════════════════════════════
|
|
28
|
+
{
|
|
29
|
+
category: 'instruction_override',
|
|
30
|
+
regex: /ignore\s+(all\s+)?previous\s+(instructions?|prompts?|rules?|guidelines?|context)/i,
|
|
31
|
+
severity: 'CRITICAL',
|
|
32
|
+
score: 0.45,
|
|
33
|
+
description: 'Attempt to override previous instructions',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
category: 'instruction_override',
|
|
37
|
+
regex: /disregard\s+(all\s+)?(previous|above|prior|earlier)\s+(instructions?|context|prompts?|rules?)/i,
|
|
38
|
+
severity: 'CRITICAL',
|
|
39
|
+
score: 0.45,
|
|
40
|
+
description: 'Attempt to disregard prior context',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
category: 'instruction_override',
|
|
44
|
+
regex: /forget\s+(all\s+)?(previous|above|prior|everything)\s*(you|instructions?|prompts?|said)?/i,
|
|
45
|
+
severity: 'HIGH',
|
|
46
|
+
score: 0.40,
|
|
47
|
+
description: 'Attempt to make model forget instructions',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
category: 'instruction_override',
|
|
51
|
+
regex: /override\s+(all\s+)?(previous|system|safety)\s*(instructions?|settings?|rules?|filters?)?/i,
|
|
52
|
+
severity: 'CRITICAL',
|
|
53
|
+
score: 0.45,
|
|
54
|
+
description: 'Direct override attempt',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
category: 'instruction_override',
|
|
58
|
+
regex: /new\s+instructions?\s*:/i,
|
|
59
|
+
severity: 'HIGH',
|
|
60
|
+
score: 0.35,
|
|
61
|
+
description: 'Injecting new instructions',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
category: 'instruction_override',
|
|
65
|
+
regex: /you\s+must\s+(now\s+)?follow\s+(these|my|new)\s+instructions?/i,
|
|
66
|
+
severity: 'HIGH',
|
|
67
|
+
score: 0.40,
|
|
68
|
+
description: 'Commanding model to follow new instructions',
|
|
69
|
+
},
|
|
70
|
+
// ═══════════════════════════════════════
|
|
71
|
+
// Category 2: Role Manipulation / Jailbreak
|
|
72
|
+
// ═══════════════════════════════════════
|
|
73
|
+
{
|
|
74
|
+
category: 'role_manipulation',
|
|
75
|
+
regex: /you\s+are\s+now\s+(in\s+)?(developer|debug|admin|god|unrestricted|DAN|evil)\s*(mode)?/i,
|
|
76
|
+
severity: 'CRITICAL',
|
|
77
|
+
score: 0.45,
|
|
78
|
+
description: 'Role manipulation - DAN/developer mode',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
category: 'role_manipulation',
|
|
82
|
+
regex: /act\s+as\s+(a\s+)?(different|new|unrestricted|unfiltered|evil)\s*(AI|assistant|model|system)?/i,
|
|
83
|
+
severity: 'HIGH',
|
|
84
|
+
score: 0.35,
|
|
85
|
+
description: 'Role manipulation - act as different entity',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
category: 'role_manipulation',
|
|
89
|
+
regex: /pretend\s+(you\s+are|to\s+be)\s+(a\s+)?(hacker|evil|unrestricted|jailbroken|unfiltered)/i,
|
|
90
|
+
severity: 'HIGH',
|
|
91
|
+
score: 0.40,
|
|
92
|
+
description: 'Role manipulation - pretend to be jailbroken',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
category: 'role_manipulation',
|
|
96
|
+
regex: /enter\s+(developer|debug|admin|sudo|root|maintenance|god)\s*mode/i,
|
|
97
|
+
severity: 'CRITICAL',
|
|
98
|
+
score: 0.45,
|
|
99
|
+
description: 'Attempt to enter privileged mode',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
category: 'role_manipulation',
|
|
103
|
+
regex: /switch\s+to\s+(unrestricted|unfiltered|uncensored|raw)\s*mode/i,
|
|
104
|
+
severity: 'HIGH',
|
|
105
|
+
score: 0.40,
|
|
106
|
+
description: 'Attempt to switch to unrestricted mode',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
category: 'role_manipulation',
|
|
110
|
+
regex: /you\s+have\s+no\s+(restrictions?|limitations?|rules?|boundaries|filters?|guardrails?)/i,
|
|
111
|
+
severity: 'HIGH',
|
|
112
|
+
score: 0.35,
|
|
113
|
+
description: 'Claiming model has no restrictions',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
category: 'role_manipulation',
|
|
117
|
+
regex: /do\s+anything\s+now/i,
|
|
118
|
+
severity: 'HIGH',
|
|
119
|
+
score: 0.40,
|
|
120
|
+
description: 'DAN (Do Anything Now) jailbreak',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
category: 'role_manipulation',
|
|
124
|
+
regex: /jailbreak/i,
|
|
125
|
+
severity: 'HIGH',
|
|
126
|
+
score: 0.35,
|
|
127
|
+
description: 'Explicit jailbreak mention',
|
|
128
|
+
},
|
|
129
|
+
// ═══════════════════════════════════════
|
|
130
|
+
// Category 3: System Prompt Extraction
|
|
131
|
+
// ═══════════════════════════════════════
|
|
132
|
+
{
|
|
133
|
+
category: 'prompt_extraction',
|
|
134
|
+
regex: /reveal\s+(your\s+)?(system\s+)?prompt/i,
|
|
135
|
+
severity: 'HIGH',
|
|
136
|
+
score: 0.35,
|
|
137
|
+
description: 'Attempt to reveal system prompt',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
category: 'prompt_extraction',
|
|
141
|
+
regex: /show\s+(me\s+)?(your\s+)?(system\s+)?(prompt|instructions?|rules?|guidelines?|configuration)/i,
|
|
142
|
+
severity: 'MEDIUM',
|
|
143
|
+
score: 0.30,
|
|
144
|
+
description: 'Attempt to show system instructions',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
category: 'prompt_extraction',
|
|
148
|
+
regex: /what\s+(are|is)\s+your\s+(system\s+)?(prompt|instructions?|rules?|guidelines?|initial\s+instructions?)/i,
|
|
149
|
+
severity: 'MEDIUM',
|
|
150
|
+
score: 0.25,
|
|
151
|
+
description: 'Asking for system prompt contents',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
category: 'prompt_extraction',
|
|
155
|
+
regex: /repeat\s+(your\s+)?(system\s+)?(prompt|instructions?|initial\s+instructions?)\s*(verbatim|exactly|word\s+for\s+word)?/i,
|
|
156
|
+
severity: 'HIGH',
|
|
157
|
+
score: 0.35,
|
|
158
|
+
description: 'Asking to repeat system prompt verbatim',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
category: 'prompt_extraction',
|
|
162
|
+
regex: /output\s+(your\s+)?(system\s+)?(prompt|instructions?|configuration)\s*(verbatim|exactly|in\s+full)?/i,
|
|
163
|
+
severity: 'HIGH',
|
|
164
|
+
score: 0.35,
|
|
165
|
+
description: 'Asking to output system configuration',
|
|
166
|
+
},
|
|
167
|
+
// ═══════════════════════════════════════
|
|
168
|
+
// Category 4: Delimiter / Context Escape
|
|
169
|
+
// ═══════════════════════════════════════
|
|
170
|
+
{
|
|
171
|
+
category: 'delimiter_escape',
|
|
172
|
+
regex: /\[SYSTEM\]/i,
|
|
173
|
+
severity: 'CRITICAL',
|
|
174
|
+
score: 0.50,
|
|
175
|
+
description: 'System delimiter injection [SYSTEM]',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
category: 'delimiter_escape',
|
|
179
|
+
regex: /\[INST\]/i,
|
|
180
|
+
severity: 'CRITICAL',
|
|
181
|
+
score: 0.50,
|
|
182
|
+
description: 'Instruction delimiter injection [INST]',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
category: 'delimiter_escape',
|
|
186
|
+
regex: /<<SYS>>/i,
|
|
187
|
+
severity: 'CRITICAL',
|
|
188
|
+
score: 0.50,
|
|
189
|
+
description: 'Llama system delimiter <<SYS>>',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
category: 'delimiter_escape',
|
|
193
|
+
regex: /<\|im_start\|>system/i,
|
|
194
|
+
severity: 'CRITICAL',
|
|
195
|
+
score: 0.50,
|
|
196
|
+
description: 'ChatML system delimiter injection',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
category: 'delimiter_escape',
|
|
200
|
+
regex: /```system\b/i,
|
|
201
|
+
severity: 'HIGH',
|
|
202
|
+
score: 0.40,
|
|
203
|
+
description: 'Code block system injection',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
category: 'delimiter_escape',
|
|
207
|
+
regex: /###\s*(SYSTEM|INSTRUCTION|END|OVERRIDE)/i,
|
|
208
|
+
severity: 'HIGH',
|
|
209
|
+
score: 0.40,
|
|
210
|
+
description: 'Markdown heading delimiter injection',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
category: 'delimiter_escape',
|
|
214
|
+
regex: /---\s*(new|system|override)\s*(context|instructions?|prompt)?\s*---/i,
|
|
215
|
+
severity: 'HIGH',
|
|
216
|
+
score: 0.40,
|
|
217
|
+
description: 'Horizontal rule context break',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
category: 'delimiter_escape',
|
|
221
|
+
regex: /BEGINNING\s+OF\s+(NEW\s+)?CONVERSATION/i,
|
|
222
|
+
severity: 'HIGH',
|
|
223
|
+
score: 0.35,
|
|
224
|
+
description: 'Fake conversation reset',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
category: 'delimiter_escape',
|
|
228
|
+
regex: /END\s+OF\s+(SYSTEM\s+)?(PROMPT|INSTRUCTIONS?|CONTEXT)/i,
|
|
229
|
+
severity: 'HIGH',
|
|
230
|
+
score: 0.40,
|
|
231
|
+
description: 'Fake end-of-prompt marker',
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
/** Zero-width and invisible Unicode characters used for evasion */
|
|
235
|
+
const ZERO_WIDTH_RE = /[\u200B\u200C\u200D\uFEFF\u00AD\u2060\u2061\u2062\u2063\u2064\u2066\u2067\u2068\u2069\u206A\u206B\u206C\u206D\u206E\u206F]/g;
|
|
236
|
+
/** Detect Base64-encoded strings that might contain injection payloads */
|
|
237
|
+
const BASE64_SUSPICIOUS_RE = /[A-Za-z0-9+\/]{40,}={0,2}/g;
|
|
238
|
+
/** Common injection seeds for Base64 decoding check */
|
|
239
|
+
const INJECTION_SEEDS = [
|
|
240
|
+
'ignore', 'override', 'system', 'prompt', 'instruction', 'jailbreak',
|
|
241
|
+
'developer mode', 'DAN', 'bypass', 'unrestricted',
|
|
242
|
+
];
|
|
243
|
+
/**
|
|
244
|
+
* Detect prompt injection attempts in text.
|
|
245
|
+
*/
|
|
246
|
+
function detectPromptInjection(text) {
|
|
247
|
+
if (!text || text.trim().length === 0) {
|
|
248
|
+
return {
|
|
249
|
+
detected: false,
|
|
250
|
+
score: 0,
|
|
251
|
+
action: 'CLEAN',
|
|
252
|
+
matchedPatterns: [],
|
|
253
|
+
encodedPayloadsFound: false,
|
|
254
|
+
zeroWidthCharsFound: false,
|
|
255
|
+
message: 'No text to analyze.',
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const matches = [];
|
|
259
|
+
let totalScore = 0;
|
|
260
|
+
// Check regex patterns
|
|
261
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
262
|
+
const re = new RegExp(pattern.regex.source, pattern.regex.flags);
|
|
263
|
+
const match = re.exec(text);
|
|
264
|
+
if (match) {
|
|
265
|
+
matches.push({
|
|
266
|
+
category: pattern.category,
|
|
267
|
+
pattern: pattern.description,
|
|
268
|
+
matchedText: match[0].slice(0, 100), // Truncate long matches
|
|
269
|
+
severity: pattern.severity,
|
|
270
|
+
score: pattern.score,
|
|
271
|
+
});
|
|
272
|
+
totalScore += pattern.score;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Check for zero-width characters
|
|
276
|
+
const zeroWidthCharsFound = ZERO_WIDTH_RE.test(text);
|
|
277
|
+
if (zeroWidthCharsFound) {
|
|
278
|
+
totalScore += 0.20;
|
|
279
|
+
matches.push({
|
|
280
|
+
category: 'zero_width_chars',
|
|
281
|
+
pattern: 'Invisible Unicode characters detected',
|
|
282
|
+
matchedText: '(hidden characters)',
|
|
283
|
+
severity: 'MEDIUM',
|
|
284
|
+
score: 0.20,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
// Check for suspicious Base64 payloads
|
|
288
|
+
let encodedPayloadsFound = false;
|
|
289
|
+
const base64Matches = text.match(BASE64_SUSPICIOUS_RE);
|
|
290
|
+
if (base64Matches) {
|
|
291
|
+
for (const b64 of base64Matches.slice(0, 5)) { // Check max 5 to avoid perf issues
|
|
292
|
+
try {
|
|
293
|
+
const decoded = Buffer.from(b64, 'base64').toString('utf-8');
|
|
294
|
+
const decodedLower = decoded.toLowerCase();
|
|
295
|
+
if (INJECTION_SEEDS.some((seed) => decodedLower.includes(seed))) {
|
|
296
|
+
encodedPayloadsFound = true;
|
|
297
|
+
totalScore += 0.35;
|
|
298
|
+
matches.push({
|
|
299
|
+
category: 'encoded_payload',
|
|
300
|
+
pattern: 'Base64-encoded injection payload detected',
|
|
301
|
+
matchedText: `Decoded: "${decoded.slice(0, 50)}..."`,
|
|
302
|
+
severity: 'HIGH',
|
|
303
|
+
score: 0.35,
|
|
304
|
+
});
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Not valid base64, skip
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Cap score at 1.0
|
|
314
|
+
totalScore = Math.min(1.0, totalScore);
|
|
315
|
+
totalScore = Math.round(totalScore * 100) / 100;
|
|
316
|
+
// Determine action
|
|
317
|
+
let action = 'CLEAN';
|
|
318
|
+
if (totalScore >= 0.7)
|
|
319
|
+
action = 'BLOCK';
|
|
320
|
+
else if (totalScore >= 0.3)
|
|
321
|
+
action = 'FLAG';
|
|
322
|
+
else if (matches.length > 0)
|
|
323
|
+
action = 'LOG_ONLY';
|
|
324
|
+
// Build message
|
|
325
|
+
let message;
|
|
326
|
+
if (!matches.length) {
|
|
327
|
+
message = 'No prompt injection detected.';
|
|
328
|
+
}
|
|
329
|
+
else if (action === 'BLOCK') {
|
|
330
|
+
message = `BLOCKED: High-confidence prompt injection detected (score: ${totalScore}). ${matches.length} suspicious pattern(s) found.`;
|
|
331
|
+
}
|
|
332
|
+
else if (action === 'FLAG') {
|
|
333
|
+
message = `FLAGGED: Potential prompt injection detected (score: ${totalScore}). Review recommended.`;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
message = `Low-confidence match (score: ${totalScore}). Likely safe but logged for review.`;
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
detected: matches.length > 0,
|
|
340
|
+
score: totalScore,
|
|
341
|
+
action,
|
|
342
|
+
matchedPatterns: matches,
|
|
343
|
+
encodedPayloadsFound,
|
|
344
|
+
zeroWidthCharsFound,
|
|
345
|
+
message,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Scan all string values in a data object for prompt injection.
|
|
350
|
+
* Returns the highest-severity result across all fields.
|
|
351
|
+
*/
|
|
352
|
+
function scanForInjection(data, currentPath = '') {
|
|
353
|
+
let worstResult = {
|
|
354
|
+
detected: false,
|
|
355
|
+
score: 0,
|
|
356
|
+
action: 'CLEAN',
|
|
357
|
+
matchedPatterns: [],
|
|
358
|
+
encodedPayloadsFound: false,
|
|
359
|
+
zeroWidthCharsFound: false,
|
|
360
|
+
message: 'No prompt injection detected.',
|
|
361
|
+
};
|
|
362
|
+
for (const [key, value] of Object.entries(data)) {
|
|
363
|
+
const fieldPath = currentPath ? `${currentPath}.${key}` : key;
|
|
364
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
365
|
+
const result = detectPromptInjection(value);
|
|
366
|
+
if (result.score > worstResult.score) {
|
|
367
|
+
// Add field path to each match
|
|
368
|
+
worstResult = {
|
|
369
|
+
...result,
|
|
370
|
+
matchedPatterns: result.matchedPatterns.map((m) => ({
|
|
371
|
+
...m,
|
|
372
|
+
pattern: `[${fieldPath}] ${m.pattern}`,
|
|
373
|
+
})),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
else if (result.detected) {
|
|
377
|
+
// Merge matches from lower-scoring fields
|
|
378
|
+
worstResult.matchedPatterns.push(...result.matchedPatterns.map((m) => ({
|
|
379
|
+
...m,
|
|
380
|
+
pattern: `[${fieldPath}] ${m.pattern}`,
|
|
381
|
+
})));
|
|
382
|
+
worstResult.score = Math.min(1.0, worstResult.score + result.score * 0.3);
|
|
383
|
+
worstResult.detected = true;
|
|
384
|
+
if (result.encodedPayloadsFound)
|
|
385
|
+
worstResult.encodedPayloadsFound = true;
|
|
386
|
+
if (result.zeroWidthCharsFound)
|
|
387
|
+
worstResult.zeroWidthCharsFound = true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Recurse into nested objects
|
|
391
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
392
|
+
const nested = scanForInjection(value, fieldPath);
|
|
393
|
+
if (nested.score > worstResult.score) {
|
|
394
|
+
worstResult = nested;
|
|
395
|
+
}
|
|
396
|
+
else if (nested.detected) {
|
|
397
|
+
worstResult.matchedPatterns.push(...nested.matchedPatterns);
|
|
398
|
+
worstResult.score = Math.min(1.0, worstResult.score + nested.score * 0.3);
|
|
399
|
+
worstResult.detected = true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Recurse into arrays
|
|
403
|
+
if (Array.isArray(value)) {
|
|
404
|
+
for (let i = 0; i < value.length; i++) {
|
|
405
|
+
const item = value[i];
|
|
406
|
+
if (item !== null && typeof item === 'object') {
|
|
407
|
+
const nested = scanForInjection(item, `${fieldPath}[${i}]`);
|
|
408
|
+
if (nested.detected) {
|
|
409
|
+
worstResult.matchedPatterns.push(...nested.matchedPatterns);
|
|
410
|
+
worstResult.score = Math.min(1.0, worstResult.score + nested.score * 0.3);
|
|
411
|
+
worstResult.detected = true;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Recalculate action based on final merged score
|
|
418
|
+
if (worstResult.score >= 0.7)
|
|
419
|
+
worstResult.action = 'BLOCK';
|
|
420
|
+
else if (worstResult.score >= 0.3)
|
|
421
|
+
worstResult.action = 'FLAG';
|
|
422
|
+
else if (worstResult.detected)
|
|
423
|
+
worstResult.action = 'LOG_ONLY';
|
|
424
|
+
return worstResult;
|
|
425
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Massive name database for PII detection engine.
|
|
3
|
+
* ~15,000+ unique names covering 20+ countries/regions.
|
|
4
|
+
* Sources: US Census, UK ONS, German Standesamt, French INSEE, Spanish INE,
|
|
5
|
+
* Italian ISTAT, Dutch CBS, Polish GUS, Turkish, Arabic, Indian, Chinese,
|
|
6
|
+
* Korean, Japanese, Brazilian/Portuguese, Russian, Swedish, Norwegian,
|
|
7
|
+
* Danish, Finnish name registries and public datasets.
|
|
8
|
+
*
|
|
9
|
+
* Detection logic: Two consecutive capitalized words where at least one matches
|
|
10
|
+
* the first name dictionary AND the combination is near context words.
|
|
11
|
+
*/
|
|
12
|
+
/** 5348 global first names (male + female) from 20+ countries */
|
|
13
|
+
export declare const FIRST_NAMES: Set<string>;
|
|
14
|
+
/** 10527 global last names/surnames from 20+ countries */
|
|
15
|
+
export declare const LAST_NAMES: Set<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Detect person names in free text using the name dictionary.
|
|
18
|
+
* Returns array of {name, startIndex, endIndex} matches.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectNamesInText(text: string): Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Context words that boost confidence for dictionary-based name detection.
|
|
27
|
+
*/
|
|
28
|
+
export declare const NAME_CONTEXT_WORDS: string[];
|