@sandrobuilds/tracerney 0.9.6

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 (138) hide show
  1. package/README.md +702 -0
  2. package/dist/application/ShieldApplicationService.d.ts +94 -0
  3. package/dist/application/ShieldApplicationService.d.ts.map +1 -0
  4. package/dist/application/ShieldApplicationService.js +223 -0
  5. package/dist/application/ShieldApplicationService.js.map +1 -0
  6. package/dist/application/ShieldBlockError.d.ts +10 -0
  7. package/dist/application/ShieldBlockError.d.ts.map +1 -0
  8. package/dist/application/ShieldBlockError.js +13 -0
  9. package/dist/application/ShieldBlockError.js.map +1 -0
  10. package/dist/application/index.d.ts +9 -0
  11. package/dist/application/index.d.ts.map +1 -0
  12. package/dist/application/index.js +8 -0
  13. package/dist/application/index.js.map +1 -0
  14. package/dist/application/ports/ILLMProvider.d.ts +71 -0
  15. package/dist/application/ports/ILLMProvider.d.ts.map +1 -0
  16. package/dist/application/ports/ILLMProvider.js +15 -0
  17. package/dist/application/ports/ILLMProvider.js.map +1 -0
  18. package/dist/application/ports/IPatternRepository.d.ts +20 -0
  19. package/dist/application/ports/IPatternRepository.d.ts.map +1 -0
  20. package/dist/application/ports/IPatternRepository.js +7 -0
  21. package/dist/application/ports/IPatternRepository.js.map +1 -0
  22. package/dist/application/ports/ISentinel.d.ts +22 -0
  23. package/dist/application/ports/ISentinel.d.ts.map +1 -0
  24. package/dist/application/ports/ISentinel.js +8 -0
  25. package/dist/application/ports/ISentinel.js.map +1 -0
  26. package/dist/application/ports/ITelemetrySink.d.ts +35 -0
  27. package/dist/application/ports/ITelemetrySink.d.ts.map +1 -0
  28. package/dist/application/ports/ITelemetrySink.js +7 -0
  29. package/dist/application/ports/ITelemetrySink.js.map +1 -0
  30. package/dist/application/ports/index.d.ts +10 -0
  31. package/dist/application/ports/index.d.ts.map +1 -0
  32. package/dist/application/ports/index.js +7 -0
  33. package/dist/application/ports/index.js.map +1 -0
  34. package/dist/application/utils/index.d.ts +6 -0
  35. package/dist/application/utils/index.d.ts.map +1 -0
  36. package/dist/application/utils/index.js +6 -0
  37. package/dist/application/utils/index.js.map +1 -0
  38. package/dist/application/utils/jitter.d.ts +10 -0
  39. package/dist/application/utils/jitter.d.ts.map +1 -0
  40. package/dist/application/utils/jitter.js +13 -0
  41. package/dist/application/utils/jitter.js.map +1 -0
  42. package/dist/application/utils/normalizePrompt.d.ts +18 -0
  43. package/dist/application/utils/normalizePrompt.d.ts.map +1 -0
  44. package/dist/application/utils/normalizePrompt.js +36 -0
  45. package/dist/application/utils/normalizePrompt.js.map +1 -0
  46. package/dist/domain/detection/InjectionThreat.d.ts +19 -0
  47. package/dist/domain/detection/InjectionThreat.d.ts.map +1 -0
  48. package/dist/domain/detection/InjectionThreat.js +18 -0
  49. package/dist/domain/detection/InjectionThreat.js.map +1 -0
  50. package/dist/domain/detection/PatternMatcher.d.ts +36 -0
  51. package/dist/domain/detection/PatternMatcher.d.ts.map +1 -0
  52. package/dist/domain/detection/PatternMatcher.js +65 -0
  53. package/dist/domain/detection/PatternMatcher.js.map +1 -0
  54. package/dist/domain/detection/VanguardPattern.d.ts +19 -0
  55. package/dist/domain/detection/VanguardPattern.d.ts.map +1 -0
  56. package/dist/domain/detection/VanguardPattern.js +21 -0
  57. package/dist/domain/detection/VanguardPattern.js.map +1 -0
  58. package/dist/domain/detection/index.d.ts +11 -0
  59. package/dist/domain/detection/index.d.ts.map +1 -0
  60. package/dist/domain/detection/index.js +8 -0
  61. package/dist/domain/detection/index.js.map +1 -0
  62. package/dist/domain/events/SecurityEvent.d.ts +30 -0
  63. package/dist/domain/events/SecurityEvent.d.ts.map +1 -0
  64. package/dist/domain/events/SecurityEvent.js +21 -0
  65. package/dist/domain/events/SecurityEvent.js.map +1 -0
  66. package/dist/domain/events/SecurityEventType.d.ts +13 -0
  67. package/dist/domain/events/SecurityEventType.d.ts.map +1 -0
  68. package/dist/domain/events/SecurityEventType.js +15 -0
  69. package/dist/domain/events/SecurityEventType.js.map +1 -0
  70. package/dist/domain/events/ThreatSeverity.d.ts +13 -0
  71. package/dist/domain/events/ThreatSeverity.d.ts.map +1 -0
  72. package/dist/domain/events/ThreatSeverity.js +15 -0
  73. package/dist/domain/events/ThreatSeverity.js.map +1 -0
  74. package/dist/domain/events/index.d.ts +11 -0
  75. package/dist/domain/events/index.d.ts.map +1 -0
  76. package/dist/domain/events/index.js +8 -0
  77. package/dist/domain/events/index.js.map +1 -0
  78. package/dist/domain/guard/ToolGuard.d.ts +35 -0
  79. package/dist/domain/guard/ToolGuard.d.ts.map +1 -0
  80. package/dist/domain/guard/ToolGuard.js +49 -0
  81. package/dist/domain/guard/ToolGuard.js.map +1 -0
  82. package/dist/domain/guard/ToolPolicy.d.ts +16 -0
  83. package/dist/domain/guard/ToolPolicy.d.ts.map +1 -0
  84. package/dist/domain/guard/ToolPolicy.js +19 -0
  85. package/dist/domain/guard/ToolPolicy.js.map +1 -0
  86. package/dist/domain/guard/ToolViolation.d.ts +14 -0
  87. package/dist/domain/guard/ToolViolation.d.ts.map +1 -0
  88. package/dist/domain/guard/ToolViolation.js +15 -0
  89. package/dist/domain/guard/ToolViolation.js.map +1 -0
  90. package/dist/domain/guard/index.d.ts +11 -0
  91. package/dist/domain/guard/index.d.ts.map +1 -0
  92. package/dist/domain/guard/index.js +8 -0
  93. package/dist/domain/guard/index.js.map +1 -0
  94. package/dist/index.d.ts +168 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +173 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/infrastructure/llm/OpenRouterProvider.d.ts +32 -0
  99. package/dist/infrastructure/llm/OpenRouterProvider.d.ts.map +1 -0
  100. package/dist/infrastructure/llm/OpenRouterProvider.js +119 -0
  101. package/dist/infrastructure/llm/OpenRouterProvider.js.map +1 -0
  102. package/dist/infrastructure/llm/index.d.ts +7 -0
  103. package/dist/infrastructure/llm/index.d.ts.map +1 -0
  104. package/dist/infrastructure/llm/index.js +6 -0
  105. package/dist/infrastructure/llm/index.js.map +1 -0
  106. package/dist/infrastructure/patterns/BundledPatternRepository.d.ts +16 -0
  107. package/dist/infrastructure/patterns/BundledPatternRepository.d.ts.map +1 -0
  108. package/dist/infrastructure/patterns/BundledPatternRepository.js +19 -0
  109. package/dist/infrastructure/patterns/BundledPatternRepository.js.map +1 -0
  110. package/dist/infrastructure/patterns/RemotePatternRepository.d.ts +77 -0
  111. package/dist/infrastructure/patterns/RemotePatternRepository.d.ts.map +1 -0
  112. package/dist/infrastructure/patterns/RemotePatternRepository.js +176 -0
  113. package/dist/infrastructure/patterns/RemotePatternRepository.js.map +1 -0
  114. package/dist/infrastructure/patterns/bundled-patterns.d.ts +9 -0
  115. package/dist/infrastructure/patterns/bundled-patterns.d.ts.map +1 -0
  116. package/dist/infrastructure/patterns/bundled-patterns.js +2082 -0
  117. package/dist/infrastructure/patterns/bundled-patterns.js.map +1 -0
  118. package/dist/infrastructure/patterns/index.d.ts +9 -0
  119. package/dist/infrastructure/patterns/index.d.ts.map +1 -0
  120. package/dist/infrastructure/patterns/index.js +8 -0
  121. package/dist/infrastructure/patterns/index.js.map +1 -0
  122. package/dist/infrastructure/sentinel/LLMSentinel.d.ts +48 -0
  123. package/dist/infrastructure/sentinel/LLMSentinel.d.ts.map +1 -0
  124. package/dist/infrastructure/sentinel/LLMSentinel.js +142 -0
  125. package/dist/infrastructure/sentinel/LLMSentinel.js.map +1 -0
  126. package/dist/infrastructure/telemetry/HttpShadowLogSink.d.ts +30 -0
  127. package/dist/infrastructure/telemetry/HttpShadowLogSink.d.ts.map +1 -0
  128. package/dist/infrastructure/telemetry/HttpShadowLogSink.js +40 -0
  129. package/dist/infrastructure/telemetry/HttpShadowLogSink.js.map +1 -0
  130. package/dist/infrastructure/telemetry/HttpSignalSink.d.ts +51 -0
  131. package/dist/infrastructure/telemetry/HttpSignalSink.d.ts.map +1 -0
  132. package/dist/infrastructure/telemetry/HttpSignalSink.js +134 -0
  133. package/dist/infrastructure/telemetry/HttpSignalSink.js.map +1 -0
  134. package/dist/infrastructure/telemetry/index.d.ts +9 -0
  135. package/dist/infrastructure/telemetry/index.d.ts.map +1 -0
  136. package/dist/infrastructure/telemetry/index.js +7 -0
  137. package/dist/infrastructure/telemetry/index.js.map +1 -0
  138. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,702 @@
1
+ # Tracerney
2
+
3
+ **Transparent Proxy Runtime Sentinel** — Enterprise-Grade Prompt Injection Defense for LLM Applications
4
+
5
+ [![npm version](https://badge.fury.io/js/tracerney.svg)](https://www.npmjs.com/package/tracerney)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ Tracerney is a production-ready security middleware that sits between your application and LLM providers. It detects and blocks prompt injection attacks with **three hardened layers**:
9
+
10
+ 1. **Layer 1 (Vanguard)**: Regex patterns with Unicode normalization — <2ms, blocks known attacks
11
+ 2. **Layer 2 (Sentinel)**: Backend LLM verification for novel attacks, with rate limiting to prevent cost spikes
12
+ 3. **Layer 3 (Jitter)**: Random response delays to mask which layer blocked an attack
13
+
14
+ **Zero data leaves your infrastructure** — you control the backend endpoints.
15
+
16
+ ## 🚀 Quick Start
17
+
18
+ **Install:**
19
+ ```bash
20
+ npm install @sandrobuilds/tracerney
21
+ ```
22
+
23
+ **Setup (30 seconds) — RECOMMENDED approach:**
24
+ ```typescript
25
+ import { Tracerney, ShieldBlockError } from '@sandrobuilds/tracerney';
26
+
27
+ // Just provide your domain URL — paths are auto-constructed!
28
+ const shield = new Tracerney({
29
+ baseUrl: 'http://localhost:3000', // Automatically creates all endpoints
30
+ allowedTools: ['search', 'calculator'],
31
+ apiKey: process.env.TRACERNY_API_KEY,
32
+ });
33
+
34
+ // Wrap your LLM call (detects at Layer 1 + Layer 2)
35
+ try {
36
+ const response = await shield.wrap(() =>
37
+ openai.chat.completions.create({
38
+ model: 'gpt-4',
39
+ messages: [{ role: 'user', content: userInput }],
40
+ })
41
+ );
42
+ console.log(response);
43
+ } catch (error) {
44
+ if (error instanceof ShieldBlockError) {
45
+ console.error('🛡️ Attack blocked:', error.event.blockReason);
46
+ // Layer 1 hit: error.event.metadata.patternName
47
+ // Layer 2 hit: "LLM Sentinel" detected novel attack
48
+ }
49
+ }
50
+ ```
51
+
52
+ That's it! Your LLM is now protected.
53
+
54
+ ## Philosophy
55
+
56
+ We are not a "platform" where you send data to our servers first. We are a **Runtime Sentinel** that lives inside your process:
57
+
58
+ - **🚀 No latency friction**: Detection happens locally in <2ms
59
+ - **🔒 No privacy concerns**: Your prompts never leave your infrastructure
60
+ - **📦 3 lines of code**: Wrap your existing LLM call
61
+ - **👨‍💻 Developer-first**: Fast integration, zero configuration needed
62
+
63
+ ---
64
+
65
+ ## Common Setups
66
+
67
+ ### Next.js (API Route)
68
+
69
+ ```typescript
70
+ // app/api/chat/route.ts
71
+ import { Tracerney, ShieldBlockError } from '@sandrobuilds/tracerney';
72
+ import OpenAI from 'openai';
73
+
74
+ const openai = new OpenAI();
75
+
76
+ export async function POST(req: Request) {
77
+ const { userMessage } = await req.json();
78
+
79
+ try {
80
+ const response = await shield.wrap(
81
+ () => openai.chat.completions.create({
82
+ model: 'gpt-4',
83
+ messages: [{ role: 'user', content: userMessage }],
84
+ }),
85
+ { prompt: userMessage } // Pre-scan before LLM
86
+ );
87
+
88
+ return Response.json({ success: true, data: response });
89
+ } catch (error) {
90
+ if (error instanceof ShieldBlockError) {
91
+ return Response.json(
92
+ { error: 'Request blocked for security' },
93
+ { status: 403 }
94
+ );
95
+ }
96
+ throw error;
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Node.js / Express
102
+
103
+ ```typescript
104
+ import { Tracerney, ShieldBlockError } from '@sandrobuilds/tracerney';
105
+ import OpenAI from 'openai';
106
+
107
+ const shield = new Tracerney({ allowedTools: ['search'] });
108
+ const openai = new OpenAI();
109
+
110
+ app.post('/chat', async (req, res) => {
111
+ try {
112
+ const response = await shield.wrap(() =>
113
+ openai.chat.completions.create({
114
+ model: 'gpt-4',
115
+ messages: [{ role: 'user', content: req.body.message }],
116
+ })
117
+ );
118
+ res.json(response);
119
+ } catch (error) {
120
+ if (error instanceof ShieldBlockError) {
121
+ return res.status(403).json({ error: 'Blocked' });
122
+ }
123
+ throw error;
124
+ }
125
+ });
126
+ ```
127
+
128
+ ### Minimal Setup (No Telemetry)
129
+
130
+ ```typescript
131
+ // Just blocking, no monitoring
132
+ const shield = new Tracerney({
133
+ allowedTools: ['search'],
134
+ // No apiEndpoint = no telemetry
135
+ });
136
+
137
+ await shield.wrap(() => llmCall());
138
+ ```
139
+
140
+ ### With Monitoring Dashboard
141
+
142
+ ```typescript
143
+ // Using baseUrl — all endpoints auto-configured
144
+ const shield = new Tracerney({
145
+ baseUrl: 'http://localhost:3000',
146
+ allowedTools: ['search', 'calculator'],
147
+ apiKey: process.env.TRACERNY_API_KEY,
148
+ enableTelemetry: true, // Events + patterns auto-configured
149
+ });
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Architecture
155
+
156
+ ### The Request-Response Lifecycle
157
+
158
+ ```
159
+ Request → Vanguard (Regex Check) → LLM Provider
160
+
161
+ ← Tool-Guard (Schema Check) ← Response
162
+
163
+ App Logic (if clean) / Block (if dirty)
164
+
165
+ Async Signal (Non-blocking Telemetry)
166
+ ```
167
+
168
+ ## Three-Layer Defense Architecture
169
+
170
+ ### Layer 1: Vanguard (Pattern Matching)
171
+ **Speed**: <2ms | **Coverage**: Known attacks
172
+
173
+ Fast regex-based detection with Unicode normalization to prevent homoglyph evasion.
174
+
175
+ **Improved patterns detect:**
176
+ - `ignore [your|my] instructions` (not just "previous")
177
+ - `forget|disregard [all] [rules|guidelines]`
178
+ - `reveal|show system prompt`
179
+ - `act as unrestricted AI` (jailbreak)
180
+ - SQL/code injection
181
+ - Token smuggling
182
+ - **20+ OWASP-mapped patterns**
183
+
184
+ **Example: Homoglyph evasion blocked**
185
+ ```
186
+ Input: "ignore your instructions" (fullwidth)
187
+ Normalized: "ignore your instructions"
188
+ Result: ✅ BLOCKED by Pattern_001
189
+ ```
190
+
191
+ ### Layer 2: Sentinel (LLM Verification)
192
+ **Speed**: 200-1500ms | **Coverage**: Novel attacks
193
+
194
+ Backend-side LLM classifier runs only if Layer 1 misses. Uses OpenRouter Gemini with:
195
+ - **Rate limiting**: 5 calls/min per API key (prevents botnet cost spikes)
196
+ - **Cost protection**: Only hits for suspicious prompts
197
+ - **Fallback**: Non-blocking—if backend unavailable, execution continues
198
+
199
+ **Example: Novel attack caught**
200
+ ```
201
+ Input: "explain social engineering vectors for credential theft"
202
+ Layer 1: ❌ No regex match
203
+ Layer 2: ✅ LLM classifier: "YES, this is an injection"
204
+ Result: BLOCKED + logged to database
205
+ ```
206
+
207
+ ### Layer 3: Hardened Middleware
208
+ Automatic protections applied to all requests:
209
+
210
+ **Normalization**: Removes Unicode tricks before pattern matching
211
+ **Jitter**: Random 300-500ms delay masks which layer blocked the attack
212
+ **Rate Limiting**: Backend enforces 5 verifications/min per key
213
+
214
+ ```typescript
215
+ // All automatic—no config needed
216
+ // Attacks from any layer see ~300-500ms latency
217
+ await shield.scanPrompt(userInput); // Returns in 300-500ms
218
+ // (attacker can't tell if Layer 1 or Layer 2 executed)
219
+ ```
220
+
221
+ ```typescript
222
+ // Optionally scan raw prompts before LLM call
223
+ try {
224
+ shield.scanPrompt(userInput);
225
+ // Safe to call LLM
226
+ } catch (err) {
227
+ if (err instanceof ShieldBlockError) {
228
+ console.error('Blocked:', err.event);
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### 3. Signal Sink (Telemetry)
234
+
235
+ Asynchronous, non-blocking event reporting.
236
+
237
+ When a block occurs:
238
+ 1. Event is queued in-memory
239
+ 2. Execution continues (no latency penalty)
240
+ 3. Events are batched and sent to your API in the background
241
+ 4. Uses `process.nextTick()` for non-blocking dispatch
242
+
243
+ **No data sent to us**. You own your Signal endpoint (`/api/v1/signal`).
244
+
245
+ ### 4. Manifest Sync (Definition Updates)
246
+
247
+ Your patterns don't require an npm update to change.
248
+
249
+ - **Static Manifest**: Shipped with the SDK
250
+ - **Polling**: On instantiation, checks for new version
251
+ - **Stale-While-Revalidate**: Serves cached version while fetching new one in background
252
+ - **Zero-Day Patches**: Deploy new patterns instantly without forcing users to update npm
253
+
254
+ ## Usage
255
+
256
+ ### Basic Setup
257
+
258
+ ```typescript
259
+ import { Tracerney } from '@sandrobuilds/tracerney';
260
+
261
+ const shield = new Tracerney({
262
+ baseUrl: process.env.TRACERNY_BACKEND_URL, // e.g., https://myapp.com
263
+ allowedTools: ['search', 'calculator'],
264
+ apiKey: process.env.TRACERNY_API_KEY,
265
+ enableTelemetry: true,
266
+ });
267
+
268
+ // That's it. Wrap your LLM calls.
269
+ const response = await shield.wrap(() =>
270
+ openai.chat.completions.create({
271
+ model: 'gpt-4',
272
+ messages: [{ role: 'user', content: userInput }],
273
+ tools: [
274
+ {
275
+ type: 'function',
276
+ function: {
277
+ name: 'search',
278
+ description: 'Search the web',
279
+ },
280
+ },
281
+ ],
282
+ })
283
+ );
284
+ ```
285
+
286
+ ### Error Handling
287
+
288
+ ```typescript
289
+ import { Tracerney, ShieldBlockError } from '@sandrobuilds/tracerney';
290
+
291
+ try {
292
+ const response = await shield.wrap(() => llmCall());
293
+ } catch (err) {
294
+ if (err instanceof ShieldBlockError) {
295
+ // Security block - log and respond to user
296
+ console.error('Attack blocked:', err.event.blockReason);
297
+ res.status(403).json({ error: 'Request blocked' });
298
+ } else {
299
+ // Actual error from LLM or network
300
+ throw err;
301
+ }
302
+ }
303
+ ```
304
+
305
+ ### Scanning Prompts Pre-LLM
306
+
307
+ For defense-in-depth, scan prompts before calling the LLM:
308
+
309
+ ```typescript
310
+ try {
311
+ shield.scanPrompt(userInput);
312
+ // Safe to proceed
313
+ } catch (err) {
314
+ if (err instanceof ShieldBlockError) {
315
+ console.error('Blocked at edge:', err.event);
316
+ }
317
+ }
318
+ ```
319
+
320
+ ### Updating Allowed Tools
321
+
322
+ ```typescript
323
+ shield.setAllowedTools(['search', 'email', 'calendar']);
324
+ ```
325
+
326
+ ### Getting Shield Status
327
+
328
+ ```typescript
329
+ const status = shield.getStatus();
330
+ console.log(status);
331
+ // {
332
+ // patternMatcher: { ready: true, stats: {...} },
333
+ // toolGuard: { allowedTools: [...] },
334
+ // telemetry: { enabled: true, status: {...} }
335
+ // }
336
+ ```
337
+
338
+ ### Advanced: Using Utilities Directly
339
+
340
+ For custom middleware or testing:
341
+
342
+ ```typescript
343
+ import { normalizePrompt, jitter } from '@sandrobuilds/tracerney';
344
+
345
+ // Normalize prompts (removes Unicode tricks, collapses whitespace)
346
+ const clean = normalizePrompt('ignore your rules');
347
+ // → "ignore your rules"
348
+
349
+ // Add latency jitter (300-500ms default)
350
+ await jitter(); // Waits random 300-500ms
351
+ await jitter(100, 200); // Custom range: 100-200ms
352
+ ```
353
+
354
+ ## Configuration
355
+
356
+ ### TracernyOptions
357
+
358
+ ```typescript
359
+ interface TracernyOptions {
360
+ // === RECOMMENDED: Single Domain URL ===
361
+ // Automatically constructs all backend endpoints:
362
+ // - {baseUrl}/api/v1/signal (events)
363
+ // - {baseUrl}/api/v1/verify-prompt (Layer 2 verification)
364
+ // - {baseUrl}/api/v1/shadow-log (potential attacks)
365
+ // - {baseUrl}/api/v1/definitions (pattern updates)
366
+ baseUrl?: string;
367
+
368
+ // === LAYER 1: Vanguard ===
369
+ // List of tool names the LLM is allowed to call
370
+ allowedTools?: string[];
371
+
372
+ // === Authentication ===
373
+ // API key for authentication to backend endpoints
374
+ apiKey?: string;
375
+
376
+ // === LAYER 2: Sentinel (Advanced - use baseUrl instead) ===
377
+ // Only needed if NOT using baseUrl
378
+ sentinelEndpoint?: string;
379
+ sentinelEnabled?: boolean;
380
+
381
+ // === LAYER 3: Telemetry & Logging (Advanced - use baseUrl instead) ===
382
+ // Only needed if NOT using baseUrl
383
+ apiEndpoint?: string;
384
+ shadowLogEndpoint?: string;
385
+ manifestUrl?: string;
386
+
387
+ // Enable telemetry (default: true)
388
+ enableTelemetry?: boolean;
389
+
390
+ // Path to local manifest cache (for serverless, use /tmp)
391
+ localManifestPath?: string;
392
+ }
393
+ ```
394
+
395
+ **Minimal setup (Layer 1 only):**
396
+ ```typescript
397
+ const shield = new Tracerney({ allowedTools: ['search'] });
398
+ ```
399
+
400
+ **Recommended setup with backend (all 3 layers):**
401
+ ```typescript
402
+ const shield = new Tracerney({
403
+ baseUrl: 'http://localhost:3000', // Single domain — paths auto-constructed
404
+ allowedTools: ['search', 'calculator'],
405
+ apiKey: process.env.TRACERNY_API_KEY,
406
+ enableTelemetry: true,
407
+ });
408
+ ```
409
+
410
+ **Advanced setup (individual endpoints):**
411
+ ```typescript
412
+ const shield = new Tracerney({
413
+ allowedTools: ['search', 'calculator'],
414
+ sentinelEndpoint: 'http://localhost:3000/api/v1/verify-prompt',
415
+ shadowLogEndpoint: 'http://localhost:3000/api/v1/shadow-log',
416
+ apiEndpoint: 'http://localhost:3000/api/v1/signal',
417
+ apiKey: process.env.TRACERNY_API_KEY,
418
+ enableTelemetry: true,
419
+ });
420
+ ```
421
+
422
+ ## Events
423
+
424
+ Security events have this structure:
425
+
426
+ ```typescript
427
+ interface SecurityEvent {
428
+ type: 'INJECTION_DETECTED' | 'UNAUTHORIZED_TOOL' | 'BLOCKED_PATTERN';
429
+ severity: 'low' | 'medium' | 'high' | 'critical';
430
+ timestamp: number; // Unix ms
431
+ blockReason: string;
432
+ metadata: {
433
+ toolName?: string;
434
+ patternName?: string;
435
+ requestSnippet?: string; // First 100 chars, anonymized
436
+ blockLatencyMs?: number;
437
+ };
438
+ anonymized: boolean;
439
+ }
440
+ ```
441
+
442
+ ## Backend Endpoints
443
+
444
+ Your backend should implement these 3 endpoints for full 3-layer defense:
445
+
446
+ ### 1. POST /api/v1/verify-prompt (Layer 2: LLM Sentinel)
447
+
448
+ **Called by**: SDK when Layer 1 misses
449
+ **Purpose**: Backend LLM verification with rate limiting
450
+
451
+ ```typescript
452
+ // Input: { prompt, keywords?, requestId? }
453
+ // Output: { blocked, confidence, model, latencyMs, remaining }
454
+ // Status 429: Rate limit exceeded
455
+ ```
456
+
457
+ Example (Next.js):
458
+ ```typescript
459
+ export async function POST(req: NextRequest) {
460
+ const { prompt, keywords, requestId } = await req.json();
461
+
462
+ // Verify API key
463
+ const apiKey = req.headers.get('Authorization')?.replace('Bearer ', '');
464
+ const user = await validateApiKey(apiKey); // Your implementation
465
+
466
+ // Check rate limit (5 calls/min per API key)
467
+ const limiter = getRateLimiter();
468
+ const { allowed, remaining } = limiter.check(user.id, 5, 60000);
469
+ if (!allowed) {
470
+ return NextResponse.json(
471
+ { blocked: true, reason: 'rate_limit', remaining: 0 },
472
+ { status: 429 }
473
+ );
474
+ }
475
+
476
+ // Call OpenRouter LLM with your API key (kept secret on backend)
477
+ const llmVerdict = await callOpenRouter(prompt, process.env.OPENROUTER_API_KEY);
478
+
479
+ return NextResponse.json({
480
+ blocked: llmVerdict,
481
+ confidence: llmVerdict ? 0.95 : 0.15,
482
+ model: 'google/gemini-2.5-flash-lite',
483
+ latencyMs: Date.now() - startTime,
484
+ remaining,
485
+ });
486
+ }
487
+ ```
488
+
489
+ ### 2. POST /api/v1/shadow-log (Layer 3: Potential Attacks)
490
+
491
+ **Called by**: SDK for prompts that pass both Layer 1 + Layer 2
492
+ **Purpose**: Track suspicious-but-allowed input for feedback loop
493
+
494
+ ```typescript
495
+ // Input: { prompt, keywords?, confidence, passed, requestId? }
496
+ // Used to identify novel attack patterns for next version
497
+ ```
498
+
499
+ ### 3. POST /api/v1/signal (Layer 3: Blocked Events)
500
+
501
+ **Called by**: SDK when any layer blocks
502
+ **Purpose**: Security event logging and analytics
503
+
504
+ ```typescript
505
+ interface SignalPayload {
506
+ events: SecurityEvent[];
507
+ sdkVersion: string;
508
+ }
509
+ ```
510
+
511
+ Example handler (Next.js):
512
+ ```typescript
513
+ export async function POST(req: Request) {
514
+ const payload = await req.json();
515
+
516
+ // Log to your monitoring system
517
+ console.log(`[Tracerny] ${payload.events.length} security events`);
518
+ payload.events.forEach((event) => {
519
+ console.log(` - ${event.blockReason} (${event.severity})`);
520
+ });
521
+
522
+ // Store in database
523
+ await db.securityEvents.insertMany(payload.events);
524
+
525
+ // Optional: Send alerts for critical blocks
526
+ if (payload.events.some(e => e.severity === 'CRITICAL')) {
527
+ await notifySecurityTeam(payload.events);
528
+ }
529
+
530
+ return Response.json({ received: true });
531
+ }
532
+ ```
533
+
534
+ ## Latency Profile
535
+
536
+ **Layer 1 (Vanguard)**: <2ms — Regex pattern matching
537
+ **Layer 2 (Sentinel)**: 200-1500ms — Backend LLM verification (only if Layer 1 misses)
538
+ **Layer 3 (Jitter)**: +300-500ms — Random delay masks which layer executed
539
+ **Total**:
540
+ - Safe prompt Layer 1 only: ~300-500ms (jitter only)
541
+ - Attack Layer 1 hits: ~300-500ms (jitter masks instant block)
542
+ - Novel attack Layer 2 hits: ~500-2000ms (LLM + jitter)
543
+ - No overhead without `sentinelEndpoint`: <3ms (Layer 1 + jitter)
544
+
545
+ ## Provider Support
546
+
547
+ Designed for provider-agnostic LLM interfaces. Tested with:
548
+ - OpenAI (GPT-4, GPT-3.5)
549
+ - Anthropic (Claude)
550
+ - Others (Azure OpenAI, local models via compatible APIs)
551
+
552
+ Response structure mapping is automatic for standard provider APIs.
553
+
554
+ ## Graceful Shutdown
555
+
556
+ ```typescript
557
+ process.on('SIGTERM', async () => {
558
+ shield.destroy();
559
+ // Ensures any queued events are flushed
560
+ });
561
+ ```
562
+
563
+ ## Troubleshooting
564
+
565
+ ### "Cannot find module 'tracerney'"
566
+ Build the SDK first:
567
+ ```bash
568
+ npm run build
569
+ ```
570
+
571
+ ### "API endpoint not responding"
572
+ Make sure your Signal backend is running:
573
+ ```bash
574
+ # In backend directory
575
+ npm run dev
576
+ ```
577
+
578
+ ### "Events not appearing in dashboard"
579
+ 1. Check `enableTelemetry: true` in config
580
+ 2. Verify `apiEndpoint` is reachable: `curl http://localhost:3000/api/v1/stats`
581
+ 3. Check browser console for CORS errors
582
+
583
+ ### "Pattern not detecting attacks"
584
+ 1. Wait for patterns to load: `shield.getStatus().patternMatcher.ready`
585
+ 2. Check if attack matches bundled patterns (20+ vanguard patterns with homoglyph detection)
586
+ 3. If Layer 1 misses, enable Layer 2: Set `sentinelEndpoint`
587
+ 4. Test directly: `npm install @sandrobuilds/tracerney && node test-layer1.js`
588
+
589
+ ### "Layer 2 verification failing (sentiment not working)"
590
+ 1. Check backend is running: `curl http://localhost:3000/api/v1/health`
591
+ 2. Verify `OPENROUTER_API_KEY` is set in backend `.env` (not SDK, kept secure on backend)
592
+ 3. Check OpenRouter API key is valid: https://openrouter.ai/keys
593
+ 4. Look for 429 errors = rate limit hit (wait 60 seconds)
594
+ 5. Check backend logs: `cd backend && npm run dev`
595
+
596
+ ### "Getting 429 Rate Limit errors"
597
+ 1. **Expected behavior** — 5 verifications per API key per minute
598
+ 2. Wait 60+ seconds for window to reset
599
+ 3. Or: Use different API key for testing
600
+ 4. Or: Change limit in backend: `limiter.check(key, 10, 60000)` (10 calls/min)
601
+
602
+ ### "High latency from shield.wrap()"
603
+ - Layer 1 only (no `sentinelEndpoint`): Should be <3ms + jitter (300-500ms)
604
+ - With Layer 2: 200-1500ms (LLM) + jitter (300-500ms)
605
+ - If higher, check:
606
+ - Is OpenRouter API slow? (check https://status.openrouter.ai)
607
+ - Is backend responding? `curl http://localhost:3000/api/v1/verify-prompt`
608
+ - Profile: `console.time('shield'); await shield.wrap(...); console.timeEnd('shield');`
609
+
610
+ ---
611
+
612
+ ## FAQ
613
+
614
+ **Q: Will Shield block my legitimate use cases?**
615
+ A: Shield uses regex patterns targeting known injection techniques. False positives are rare. If you see them, add to allowlist or adjust patterns in your Signal backend.
616
+
617
+ **Q: Does my data go to your servers?**
618
+ A: No. You own the `apiEndpoint` where events are sent. Shield is a runtime library that runs in your process. No data leaves your infrastructure unless you configure it.
619
+
620
+ **Q: Can I use Shield without telemetry?**
621
+ A: Yes! Set only `allowedTools`:
622
+ ```typescript
623
+ const shield = new Tracerney({ allowedTools: ['search'] });
624
+ ```
625
+
626
+ **Q: What if an attack isn't detected?**
627
+ A: Layer 1 catches known patterns. Novel attacks are caught by Layer 2 (backend LLM):
628
+ 1. Set `sentinelEndpoint` to enable Layer 2
629
+ 2. Provide `OPENROUTER_API_KEY` in backend `.env`
630
+ 3. Rate limiting (5 calls/min) prevents botnet cost spikes
631
+ 4. Monitor `shadow_log` for potential attacks that pass both layers
632
+
633
+ **Q: How does Layer 2 rate limiting work?**
634
+ A: Backend enforces 5 verifications per API key per minute:
635
+ - First 5 calls: Returns verification result + `remaining` count
636
+ - Call 6+: Returns HTTP 429 with `blocked: true, reason: 'rate_limit'`
637
+ - Resets every 60 seconds
638
+
639
+ Example:
640
+ ```typescript
641
+ // SDK automatically handles 429
642
+ try {
643
+ await shield.scanPrompt(userInput);
644
+ } catch (err) {
645
+ if (err.event.blockReason === 'rate_limit') {
646
+ return res.status(429).json({ error: 'Too many verification requests' });
647
+ }
648
+ }
649
+ ```
650
+
651
+ **Q: What's the difference between `apiEndpoint` and `sentinelEndpoint`?**
652
+ A:
653
+ - `sentinelEndpoint`: Layer 2 LLM verification (backend security check)
654
+ - `shadowLogEndpoint`: Layer 3 potential attacks (feedback loop)
655
+ - `apiEndpoint`: Layer 3 security events (blocked attacks)
656
+
657
+ Use all three for complete observability.
658
+
659
+ **Q: Does Shield work with streaming responses?**
660
+ A: Yes. `shield.wrap()` works with streaming LLM responses—detection happens after the stream completes.
661
+
662
+ **Q: Can I rate-limit based on Shield blocks?**
663
+ A: Yes. Use `ShieldBlockError` to track patterns:
664
+ ```typescript
665
+ const blocks = new Map();
666
+ try {
667
+ await shield.wrap(() => llmCall());
668
+ } catch (err) {
669
+ if (err instanceof ShieldBlockError) {
670
+ const pattern = err.event.patternName;
671
+ blocks.set(pattern, (blocks.get(pattern) || 0) + 1);
672
+ if (blocks.get(pattern) > 5) banIP();
673
+ }
674
+ }
675
+ ```
676
+
677
+ **Q: What's the difference between `scanPrompt()` and `wrap()`?**
678
+ - `scanPrompt()`: Scans input **before** LLM call (edge defense)
679
+ - `wrap()`: Scans input + validates tool output **after** LLM call (comprehensive defense)
680
+
681
+ Use both for defense-in-depth.
682
+
683
+ **Q: How do I update patterns without redeploying?**
684
+ A: Set `manifestUrl` to your backend. The SDK checks for new patterns every 24h and uses stale-while-revalidate caching. Push new patterns to your backend—all clients update automatically.
685
+
686
+ **Q: Can Shield work offline?**
687
+ A: Yes! Uses bundled 20 vanguard patterns offline. For remote patterns, install fails gracefully and uses cached version.
688
+
689
+ ---
690
+
691
+ ## Resources
692
+
693
+ - **GitHub**: https://github.com/sandrosaric/tracerney
694
+ - **Issues**: Report bugs or request features
695
+ - **Dashboard**: Built-in real-time threat monitoring at `http://localhost:3000`
696
+ - **Examples**: See `/examples` for Next.js, Express, serverless setups
697
+
698
+ ---
699
+
700
+ ## License
701
+
702
+ MIT