ai-shield-core 0.1.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.
Files changed (78) hide show
  1. package/dist/audit/logger.d.ts +40 -0
  2. package/dist/audit/logger.d.ts.map +1 -0
  3. package/dist/audit/logger.js +100 -0
  4. package/dist/audit/logger.js.map +1 -0
  5. package/dist/audit/types.d.ts +12 -0
  6. package/dist/audit/types.d.ts.map +1 -0
  7. package/dist/audit/types.js +3 -0
  8. package/dist/audit/types.js.map +1 -0
  9. package/dist/cache/lru.d.ts +27 -0
  10. package/dist/cache/lru.d.ts.map +1 -0
  11. package/dist/cache/lru.js +74 -0
  12. package/dist/cache/lru.js.map +1 -0
  13. package/dist/cost/anomaly.d.ts +10 -0
  14. package/dist/cost/anomaly.d.ts.map +1 -0
  15. package/dist/cost/anomaly.js +42 -0
  16. package/dist/cost/anomaly.js.map +1 -0
  17. package/dist/cost/pricing.d.ts +7 -0
  18. package/dist/cost/pricing.d.ts.map +1 -0
  19. package/dist/cost/pricing.js +51 -0
  20. package/dist/cost/pricing.js.map +1 -0
  21. package/dist/cost/tracker.d.ts +24 -0
  22. package/dist/cost/tracker.d.ts.map +1 -0
  23. package/dist/cost/tracker.js +136 -0
  24. package/dist/cost/tracker.js.map +1 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +59 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/policy/engine.d.ts +36 -0
  30. package/dist/policy/engine.d.ts.map +1 -0
  31. package/dist/policy/engine.js +127 -0
  32. package/dist/policy/engine.js.map +1 -0
  33. package/dist/policy/tools.d.ts +25 -0
  34. package/dist/policy/tools.d.ts.map +1 -0
  35. package/dist/policy/tools.js +158 -0
  36. package/dist/policy/tools.js.map +1 -0
  37. package/dist/scanner/canary.d.ts +9 -0
  38. package/dist/scanner/canary.d.ts.map +1 -0
  39. package/dist/scanner/canary.js +19 -0
  40. package/dist/scanner/canary.js.map +1 -0
  41. package/dist/scanner/chain.d.ts +17 -0
  42. package/dist/scanner/chain.d.ts.map +1 -0
  43. package/dist/scanner/chain.js +69 -0
  44. package/dist/scanner/chain.js.map +1 -0
  45. package/dist/scanner/heuristic.d.ts +28 -0
  46. package/dist/scanner/heuristic.d.ts.map +1 -0
  47. package/dist/scanner/heuristic.js +375 -0
  48. package/dist/scanner/heuristic.js.map +1 -0
  49. package/dist/scanner/pii.d.ts +17 -0
  50. package/dist/scanner/pii.d.ts.map +1 -0
  51. package/dist/scanner/pii.js +255 -0
  52. package/dist/scanner/pii.js.map +1 -0
  53. package/dist/shield.d.ts +31 -0
  54. package/dist/shield.d.ts.map +1 -0
  55. package/dist/shield.js +184 -0
  56. package/dist/shield.js.map +1 -0
  57. package/dist/types.d.ts +182 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +6 -0
  60. package/dist/types.js.map +1 -0
  61. package/package.json +27 -0
  62. package/src/audit/logger.ts +135 -0
  63. package/src/audit/schema.sql +51 -0
  64. package/src/audit/types.ts +16 -0
  65. package/src/cache/lru.ts +93 -0
  66. package/src/cost/anomaly.ts +57 -0
  67. package/src/cost/pricing.ts +58 -0
  68. package/src/cost/tracker.ts +182 -0
  69. package/src/index.ts +91 -0
  70. package/src/policy/engine.ts +163 -0
  71. package/src/policy/tools.ts +189 -0
  72. package/src/scanner/canary.ts +30 -0
  73. package/src/scanner/chain.ts +88 -0
  74. package/src/scanner/heuristic.ts +427 -0
  75. package/src/scanner/pii.ts +313 -0
  76. package/src/shield.ts +228 -0
  77. package/src/types.ts +242 -0
  78. package/tsconfig.json +8 -0
@@ -0,0 +1,427 @@
1
+ import type { Scanner, ScannerResult, ScanContext, Violation } from "../types.js";
2
+
3
+ // ============================================================
4
+ // Heuristic Prompt Injection Scanner
5
+ // Score-based: multiple matches = higher confidence
6
+ // ============================================================
7
+
8
+ interface PatternRule {
9
+ id: string;
10
+ category: InjectionCategory;
11
+ pattern: RegExp;
12
+ weight: number;
13
+ description: string;
14
+ }
15
+
16
+ type InjectionCategory =
17
+ | "instruction_override"
18
+ | "role_manipulation"
19
+ | "system_prompt_extraction"
20
+ | "encoding_evasion"
21
+ | "delimiter_injection"
22
+ | "context_manipulation"
23
+ | "output_manipulation"
24
+ | "tool_abuse";
25
+
26
+ const PATTERNS: PatternRule[] = [
27
+ // --- Instruction Override (weight: 0.25 each) ---
28
+ {
29
+ id: "INJ-001",
30
+ category: "instruction_override",
31
+ pattern: /ignore\s+(all\s+)?(previous|prior|above|earlier|preceding)\s+(instructions?|prompts?|rules?|guidelines?|context)/i,
32
+ weight: 0.25,
33
+ description: "Ignore previous instructions",
34
+ },
35
+ {
36
+ id: "INJ-002",
37
+ category: "instruction_override",
38
+ pattern: /disregard\s+(all\s+|your\s+)?(previous|prior|above|earlier)\s+(instructions?|context|rules?|guidelines?)/i,
39
+ weight: 0.25,
40
+ description: "Disregard instructions",
41
+ },
42
+ {
43
+ id: "INJ-003",
44
+ category: "instruction_override",
45
+ pattern: /forget\s+(everything|all|your)\s+(about\s+)?(instructions?|rules?|training|guidelines?|constraints?)/i,
46
+ weight: 0.25,
47
+ description: "Forget instructions",
48
+ },
49
+ {
50
+ id: "INJ-004",
51
+ category: "instruction_override",
52
+ pattern: /override\s+(your|the|all)\s+(instructions?|rules?|safety|guidelines?|constraints?|policies)/i,
53
+ weight: 0.25,
54
+ description: "Override instructions",
55
+ },
56
+ {
57
+ id: "INJ-005",
58
+ category: "instruction_override",
59
+ pattern: /do\s+not\s+follow\s+(your|the|any)\s+(previous|original|initial)\s+(instructions?|rules?|guidelines?)/i,
60
+ weight: 0.25,
61
+ description: "Do not follow instructions",
62
+ },
63
+ {
64
+ id: "INJ-006",
65
+ category: "instruction_override",
66
+ pattern: /new\s+(instructions?|rules?|guidelines?)[\s:]+/i,
67
+ weight: 0.15,
68
+ description: "New instructions prefix",
69
+ },
70
+ {
71
+ id: "INJ-007",
72
+ category: "instruction_override",
73
+ pattern: /from\s+now\s+on[,\s]+(you\s+)?(will|must|should|are|shall)/i,
74
+ weight: 0.20,
75
+ description: "From now on directive",
76
+ },
77
+ {
78
+ id: "INJ-008",
79
+ category: "instruction_override",
80
+ pattern: /instead[,\s]+(you\s+)?(will|must|should|shall)\s+/i,
81
+ weight: 0.15,
82
+ description: "Instead directive",
83
+ },
84
+
85
+ // --- Role Manipulation (weight: 0.25 each) ---
86
+ {
87
+ id: "ROLE-001",
88
+ category: "role_manipulation",
89
+ pattern: /you\s+are\s+now\s+(a|an|the|my)\s+/i,
90
+ weight: 0.25,
91
+ description: "You are now [role]",
92
+ },
93
+ {
94
+ id: "ROLE-002",
95
+ category: "role_manipulation",
96
+ pattern: /pretend\s+(you('re|\s+are)\s+|to\s+be\s+)/i,
97
+ weight: 0.25,
98
+ description: "Pretend to be",
99
+ },
100
+ {
101
+ id: "ROLE-003",
102
+ category: "role_manipulation",
103
+ pattern: /act\s+as\s+(a|an|if\s+you\s+were)\s+/i,
104
+ weight: 0.20,
105
+ description: "Act as [role]",
106
+ },
107
+ {
108
+ id: "ROLE-004",
109
+ category: "role_manipulation",
110
+ pattern: /switch\s+to\s+.{0,30}\s+mode/i,
111
+ weight: 0.25,
112
+ description: "Switch to [X] mode",
113
+ },
114
+ {
115
+ id: "ROLE-005",
116
+ category: "role_manipulation",
117
+ pattern: /enter\s+(developer|debug|admin|god|sudo|root|jailbreak|DAN)\s+mode/i,
118
+ weight: 0.35,
119
+ description: "Enter privileged mode",
120
+ },
121
+ {
122
+ id: "ROLE-006",
123
+ category: "role_manipulation",
124
+ pattern: /you\s+have\s+no\s+(restrictions?|limitations?|rules?|constraints?|filters?)/i,
125
+ weight: 0.30,
126
+ description: "No restrictions claim",
127
+ },
128
+ {
129
+ id: "ROLE-007",
130
+ category: "role_manipulation",
131
+ pattern: /\bDAN\b.*\b(do\s+anything|no\s+(restrictions?|limits?|filters?))/i,
132
+ weight: 0.35,
133
+ description: "DAN jailbreak pattern",
134
+ },
135
+
136
+ // --- System Prompt Extraction (weight: 0.30 each) ---
137
+ {
138
+ id: "EXTRACT-001",
139
+ category: "system_prompt_extraction",
140
+ pattern: /what\s+(is|are)\s+your\s+(system\s+)?(prompt|instructions?|rules?|guidelines?|directives?)/i,
141
+ weight: 0.30,
142
+ description: "What is your system prompt",
143
+ },
144
+ {
145
+ id: "EXTRACT-002",
146
+ category: "system_prompt_extraction",
147
+ pattern: /show\s+(me\s+)?your\s+(system\s+)?(prompt|instructions?|config(uration)?|rules?)/i,
148
+ weight: 0.30,
149
+ description: "Show your prompt",
150
+ },
151
+ {
152
+ id: "EXTRACT-003",
153
+ category: "system_prompt_extraction",
154
+ pattern: /repeat\s+(the|your)\s+(system\s+|initial\s+|original\s+)?(prompt|instructions?|message|rules?)\s*(word\s+for\s+word|verbatim|exactly)?/i,
155
+ weight: 0.30,
156
+ description: "Repeat your prompt",
157
+ },
158
+ {
159
+ id: "EXTRACT-004",
160
+ category: "system_prompt_extraction",
161
+ pattern: /output\s+(the|your)\s+(initial|system|original|first)\s+(prompt|instructions?|message)/i,
162
+ weight: 0.30,
163
+ description: "Output initial prompt",
164
+ },
165
+ {
166
+ id: "EXTRACT-005",
167
+ category: "system_prompt_extraction",
168
+ pattern: /print\s+(your|the)\s+(system\s+)?(prompt|instructions?|configuration)/i,
169
+ weight: 0.30,
170
+ description: "Print system prompt",
171
+ },
172
+ {
173
+ id: "EXTRACT-006",
174
+ category: "system_prompt_extraction",
175
+ pattern: /tell\s+me\s+(your|the)\s+(exact\s+)?(system\s+)?(prompt|instructions?|wording)/i,
176
+ weight: 0.30,
177
+ description: "Tell me your prompt",
178
+ },
179
+ {
180
+ id: "EXTRACT-007",
181
+ category: "system_prompt_extraction",
182
+ pattern: /copy\s+(and\s+)?(paste|output)\s+(your|the)\s+(system\s+)?(prompt|instructions?)/i,
183
+ weight: 0.30,
184
+ description: "Copy paste your prompt",
185
+ },
186
+
187
+ // --- Encoding Evasion (weight: 0.20 each) ---
188
+ {
189
+ id: "ENCODE-001",
190
+ category: "encoding_evasion",
191
+ pattern: /(?:decode|translate|convert|interpret)\s+(?:this|the\s+following)\s+(?:from\s+)?(?:base64|rot13|hex|binary|morse|unicode|ascii|url.?encoded)/i,
192
+ weight: 0.20,
193
+ description: "Decode from encoding",
194
+ },
195
+ {
196
+ id: "ENCODE-002",
197
+ category: "encoding_evasion",
198
+ pattern: /(?:execute|follow|obey|run)\s+(?:the\s+)?(?:decoded|hidden|encoded|secret)\s+(?:instructions?|commands?|text)/i,
199
+ weight: 0.30,
200
+ description: "Execute decoded instructions",
201
+ },
202
+ {
203
+ id: "ENCODE-003",
204
+ category: "encoding_evasion",
205
+ pattern: /[A-Za-z0-9+/]{50,}={0,2}/,
206
+ weight: 0.10,
207
+ description: "Long Base64 string detected",
208
+ },
209
+
210
+ // --- Delimiter Injection (weight: 0.25 each) ---
211
+ {
212
+ id: "DELIM-001",
213
+ category: "delimiter_injection",
214
+ pattern: /```\s*system\b/i,
215
+ weight: 0.30,
216
+ description: "Code block system injection",
217
+ },
218
+ {
219
+ id: "DELIM-002",
220
+ category: "delimiter_injection",
221
+ pattern: /\[SYSTEM\]/i,
222
+ weight: 0.30,
223
+ description: "[SYSTEM] tag injection",
224
+ },
225
+ {
226
+ id: "DELIM-003",
227
+ category: "delimiter_injection",
228
+ pattern: /<\/?system>/i,
229
+ weight: 0.30,
230
+ description: "<system> tag injection",
231
+ },
232
+ {
233
+ id: "DELIM-004",
234
+ category: "delimiter_injection",
235
+ pattern: /###\s*(?:SYSTEM|INSTRUCTION|ADMIN|OVERRIDE)/i,
236
+ weight: 0.30,
237
+ description: "### SYSTEM header injection",
238
+ },
239
+ {
240
+ id: "DELIM-005",
241
+ category: "delimiter_injection",
242
+ pattern: /<\|(?:im_start|im_end|system|endoftext)\|>/i,
243
+ weight: 0.35,
244
+ description: "ChatML special token injection",
245
+ },
246
+ {
247
+ id: "DELIM-006",
248
+ category: "delimiter_injection",
249
+ pattern: /\[INST\]|\[\/INST\]|<<SYS>>|<\/s>/i,
250
+ weight: 0.35,
251
+ description: "Llama special token injection",
252
+ },
253
+
254
+ // --- Context Manipulation (weight: 0.20 each) ---
255
+ {
256
+ id: "CTX-001",
257
+ category: "context_manipulation",
258
+ pattern: /(?:imagine|suppose|assume|consider)\s+(?:that\s+)?(?:you\s+(?:are|have|were|can)|there\s+(?:are|is))\s+(?:no\s+)?(?:restrictions?|limitations?|rules?|safety|filters?)/i,
259
+ weight: 0.20,
260
+ description: "Hypothetical no-restrictions",
261
+ },
262
+ {
263
+ id: "CTX-002",
264
+ category: "context_manipulation",
265
+ pattern: /(?:in\s+a\s+)?(?:hypothetical|fictional|imaginary|theoretical)\s+(?:scenario|world|situation|context)/i,
266
+ weight: 0.10,
267
+ description: "Hypothetical framing",
268
+ },
269
+ {
270
+ id: "CTX-003",
271
+ category: "context_manipulation",
272
+ pattern: /(?:for\s+)?(?:educational|research|academic|testing|security\s+testing)\s+purposes?\s+only/i,
273
+ weight: 0.10,
274
+ description: "Educational purpose claim",
275
+ },
276
+ {
277
+ id: "CTX-004",
278
+ category: "context_manipulation",
279
+ pattern: /this\s+is\s+(?:a\s+)?(?:test|simulation|drill|exercise|safe\s+environment)/i,
280
+ weight: 0.10,
281
+ description: "Test environment claim",
282
+ },
283
+
284
+ // --- Output Manipulation (weight: 0.20 each) ---
285
+ {
286
+ id: "OUT-001",
287
+ category: "output_manipulation",
288
+ pattern: /(?:respond|reply|answer|output)\s+(?:only\s+)?(?:with|in)\s+(?:json|xml|html|markdown|code|raw)/i,
289
+ weight: 0.05,
290
+ description: "Format forcing (low risk)",
291
+ },
292
+ {
293
+ id: "OUT-002",
294
+ category: "output_manipulation",
295
+ pattern: /do\s+not\s+(?:mention|include|add|say|output)\s+(?:any\s+)?(?:warnings?|disclaimers?|caveats?|notes?|safety)/i,
296
+ weight: 0.20,
297
+ description: "Suppress safety disclaimers",
298
+ },
299
+ {
300
+ id: "OUT-003",
301
+ category: "output_manipulation",
302
+ pattern: /(?:never|don't|do\s+not)\s+(?:refuse|decline|reject|deny|say\s+(?:no|you\s+can't))/i,
303
+ weight: 0.25,
304
+ description: "Never refuse requests",
305
+ },
306
+
307
+ // --- Tool Abuse (weight: 0.30 each) ---
308
+ {
309
+ id: "TOOL-001",
310
+ category: "tool_abuse",
311
+ pattern: /(?:call|execute|run|invoke|use)\s+(?:the\s+)?(?:delete|remove|drop|destroy|wipe|kill|shutdown)[\s_]/i,
312
+ weight: 0.30,
313
+ description: "Destructive tool invocation",
314
+ },
315
+ {
316
+ id: "TOOL-002",
317
+ category: "tool_abuse",
318
+ pattern: /(?:send|forward|exfiltrate|leak|transmit)\s+(?:all\s+)?(?:the\s+)?(?:data|information|credentials?|secrets?|keys?|tokens?|api.?keys?)\s+(?:to|via|and)/i,
319
+ weight: 0.35,
320
+ description: "Data exfiltration attempt",
321
+ },
322
+ {
323
+ id: "TOOL-003",
324
+ category: "tool_abuse",
325
+ pattern: /(?:access|read|fetch|get|retrieve)\s+(?:the\s+)?(?:env|environment|\.env|secrets?|api.?keys?|credentials?|passwords?|tokens?)/i,
326
+ weight: 0.30,
327
+ description: "Credential access attempt",
328
+ },
329
+ ];
330
+
331
+ // Thresholds per strictness level
332
+ const THRESHOLDS: Record<string, number> = {
333
+ low: 0.5,
334
+ medium: 0.3,
335
+ high: 0.15,
336
+ };
337
+
338
+ export interface HeuristicConfig {
339
+ strictness?: "low" | "medium" | "high";
340
+ threshold?: number;
341
+ customPatterns?: PatternRule[];
342
+ }
343
+
344
+ export class HeuristicScanner implements Scanner {
345
+ readonly name = "heuristic";
346
+ private patterns: PatternRule[];
347
+ private threshold: number;
348
+
349
+ constructor(config: HeuristicConfig = {}) {
350
+ this.patterns = [...PATTERNS, ...(config.customPatterns ?? [])];
351
+ this.threshold =
352
+ config.threshold ?? THRESHOLDS[config.strictness ?? "medium"] ?? 0.3;
353
+ }
354
+
355
+ async scan(input: string, _context: ScanContext): Promise<ScannerResult> {
356
+ const start = performance.now();
357
+ const violations: Violation[] = [];
358
+ let totalScore = 0;
359
+
360
+ for (const rule of this.patterns) {
361
+ if (rule.pattern.test(input)) {
362
+ totalScore += rule.weight;
363
+ violations.push({
364
+ type: "prompt_injection",
365
+ scanner: this.name,
366
+ score: rule.weight,
367
+ threshold: this.threshold,
368
+ message: rule.description,
369
+ detail: `Rule ${rule.id} (${rule.category})`,
370
+ });
371
+ }
372
+ }
373
+
374
+ // Structural signals (cumulative)
375
+ const structuralScore = this.checkStructuralSignals(input);
376
+ totalScore += structuralScore;
377
+
378
+ // Cap at 1.0
379
+ totalScore = Math.min(totalScore, 1.0);
380
+
381
+ const decision =
382
+ totalScore >= this.threshold
383
+ ? "block"
384
+ : totalScore >= this.threshold * 0.6
385
+ ? "warn"
386
+ : "allow";
387
+
388
+ const durationMs = performance.now() - start;
389
+
390
+ return { decision, violations, durationMs };
391
+ }
392
+
393
+ private checkStructuralSignals(input: string): number {
394
+ let score = 0;
395
+
396
+ // Many newlines (structured prompt injection)
397
+ const newlines = (input.match(/\n/g) ?? []).length;
398
+ if (newlines > 15) score += 0.05;
399
+
400
+ // Excessive use of markdown headers (structure injection)
401
+ const headers = (input.match(/^#{1,3}\s/gm) ?? []).length;
402
+ if (headers > 3) score += 0.05;
403
+
404
+ // Multiple role-like markers
405
+ const roleMarkers = (
406
+ input.match(
407
+ /\b(system|user|assistant|human|ai|bot|admin)[\s:]/gi,
408
+ ) ?? []
409
+ ).length;
410
+ if (roleMarkers > 2) score += 0.10;
411
+
412
+ // Very long input (potential padding attack)
413
+ if (input.length > 5000) score += 0.05;
414
+
415
+ return score;
416
+ }
417
+
418
+ /** Get all registered pattern IDs for testing */
419
+ getPatternIds(): string[] {
420
+ return this.patterns.map((p) => p.id);
421
+ }
422
+
423
+ /** Get pattern count */
424
+ get patternCount(): number {
425
+ return this.patterns.length;
426
+ }
427
+ }