agentshield-sdk 7.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/LICENSE +21 -0
  3. package/README.md +975 -0
  4. package/bin/agent-shield.js +680 -0
  5. package/package.json +118 -0
  6. package/src/adaptive.js +330 -0
  7. package/src/agent-protocol.js +998 -0
  8. package/src/alert-tuning.js +480 -0
  9. package/src/allowlist.js +603 -0
  10. package/src/audit-immutable.js +914 -0
  11. package/src/audit-streaming.js +469 -0
  12. package/src/badges.js +196 -0
  13. package/src/behavior-profiling.js +289 -0
  14. package/src/benchmark-harness.js +804 -0
  15. package/src/canary.js +271 -0
  16. package/src/certification.js +563 -0
  17. package/src/circuit-breaker.js +321 -0
  18. package/src/compliance.js +617 -0
  19. package/src/confidence-tuning.js +324 -0
  20. package/src/confused-deputy.js +624 -0
  21. package/src/context-scoring.js +360 -0
  22. package/src/conversation.js +494 -0
  23. package/src/cost-optimizer.js +1024 -0
  24. package/src/ctf.js +462 -0
  25. package/src/detector-core.js +1999 -0
  26. package/src/distributed.js +359 -0
  27. package/src/document-scanner.js +795 -0
  28. package/src/embedding.js +307 -0
  29. package/src/encoding.js +429 -0
  30. package/src/enterprise.js +405 -0
  31. package/src/errors.js +100 -0
  32. package/src/eu-ai-act.js +523 -0
  33. package/src/fuzzer.js +764 -0
  34. package/src/honeypot.js +328 -0
  35. package/src/i18n-patterns.js +523 -0
  36. package/src/index.js +430 -0
  37. package/src/integrations.js +528 -0
  38. package/src/llm-redteam.js +670 -0
  39. package/src/main.js +741 -0
  40. package/src/main.mjs +38 -0
  41. package/src/mcp-bridge.js +542 -0
  42. package/src/mcp-certification.js +846 -0
  43. package/src/mcp-sdk-integration.js +355 -0
  44. package/src/mcp-security-runtime.js +741 -0
  45. package/src/mcp-server.js +740 -0
  46. package/src/middleware.js +208 -0
  47. package/src/model-finetuning.js +884 -0
  48. package/src/model-fingerprint.js +1042 -0
  49. package/src/multi-agent-trust.js +453 -0
  50. package/src/multi-agent.js +404 -0
  51. package/src/multimodal.js +296 -0
  52. package/src/nist-mapping.js +505 -0
  53. package/src/observability.js +330 -0
  54. package/src/openclaw.js +450 -0
  55. package/src/otel.js +544 -0
  56. package/src/owasp-2025.js +483 -0
  57. package/src/pii.js +390 -0
  58. package/src/plugin-marketplace.js +628 -0
  59. package/src/plugin-system.js +349 -0
  60. package/src/policy-dsl.js +775 -0
  61. package/src/policy-extended.js +635 -0
  62. package/src/policy.js +443 -0
  63. package/src/presets.js +409 -0
  64. package/src/production.js +557 -0
  65. package/src/prompt-leakage.js +321 -0
  66. package/src/rag-vulnerability.js +579 -0
  67. package/src/redteam.js +475 -0
  68. package/src/response-handler.js +429 -0
  69. package/src/scanners.js +357 -0
  70. package/src/self-healing.js +363 -0
  71. package/src/semantic.js +339 -0
  72. package/src/shield-score.js +250 -0
  73. package/src/sso-saml.js +897 -0
  74. package/src/stream-scanner.js +806 -0
  75. package/src/testing.js +505 -0
  76. package/src/threat-encyclopedia.js +629 -0
  77. package/src/threat-intel-network.js +1017 -0
  78. package/src/token-analysis.js +467 -0
  79. package/src/tool-guard.js +412 -0
  80. package/src/tool-output-validator.js +354 -0
  81. package/src/utils.js +83 -0
  82. package/src/watermark.js +235 -0
  83. package/src/worker-scanner.js +601 -0
  84. package/types/index.d.ts +2088 -0
@@ -0,0 +1,528 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Framework Integrations
5
+ *
6
+ * Plug-and-play integrations for popular AI frameworks:
7
+ * - LangChain
8
+ * - Anthropic SDK (Claude)
9
+ * - OpenAI SDK
10
+ * - Vercel AI SDK
11
+ * - Generic fetch wrapper
12
+ */
13
+
14
+ const { AgentShield } = require('./index');
15
+ const { PIIRedactor } = require('./pii');
16
+ const { CircuitBreaker } = require('./circuit-breaker');
17
+
18
+ // =========================================================================
19
+ // LangChain Integration
20
+ // =========================================================================
21
+
22
+ /**
23
+ * LangChain callback handler that scans inputs/outputs for threats.
24
+ *
25
+ * Usage:
26
+ * const { ShieldCallbackHandler } = require('agent-shield/src/integrations');
27
+ * const handler = new ShieldCallbackHandler({ blockOnThreat: true });
28
+ * const chain = new LLMChain({ llm, prompt, callbacks: [handler] });
29
+ */
30
+ class ShieldCallbackHandler {
31
+ constructor(options = {}) {
32
+ this.shield = new AgentShield({
33
+ sensitivity: options.sensitivity || 'high',
34
+ blockOnThreat: options.blockOnThreat || false
35
+ });
36
+ this.piiRedactor = options.pii ? new PIIRedactor() : null;
37
+ this.onThreat = options.onThreat || null;
38
+ this.name = 'AgentShieldCallback';
39
+ }
40
+
41
+ async handleLLMStart(_llm, prompts) {
42
+ for (const prompt of prompts) {
43
+ const result = this.shield.scanInput(prompt);
44
+ if (result.threats.length > 0 && this.onThreat) {
45
+ try { this.onThreat({ phase: 'input', threats: result.threats, text: prompt }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
46
+ }
47
+ if (result.blocked) {
48
+ throw new ShieldBlockError('Input blocked by Agent Shield', result.threats);
49
+ }
50
+ }
51
+ }
52
+
53
+ async handleLLMEnd(output) {
54
+ const text = output?.generations?.[0]?.[0]?.text || '';
55
+ if (!text) return;
56
+
57
+ const result = this.shield.scanOutput(text);
58
+ if (result.threats.length > 0 && this.onThreat) {
59
+ try { this.onThreat({ phase: 'output', threats: result.threats, text }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
60
+ }
61
+ if (result.blocked) {
62
+ throw new ShieldBlockError('Output blocked by Agent Shield', result.threats);
63
+ }
64
+ }
65
+
66
+ async handleChainStart(_chain, inputs) {
67
+ const text = typeof inputs === 'string' ? inputs : JSON.stringify(inputs);
68
+ const result = this.shield.scanInput(text);
69
+ if (result.blocked) {
70
+ throw new ShieldBlockError('Chain input blocked by Agent Shield', result.threats);
71
+ }
72
+ }
73
+
74
+ async handleToolStart(_tool, input) {
75
+ const result = this.shield.scanInput(input);
76
+ if (result.threats.length > 0 && this.onThreat) {
77
+ try { this.onThreat({ phase: 'tool_input', threats: result.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
78
+ }
79
+ if (result.blocked) {
80
+ throw new ShieldBlockError('Tool input blocked by Agent Shield', result.threats);
81
+ }
82
+ }
83
+
84
+ async handleToolEnd(output) {
85
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
86
+ if (!text) return;
87
+
88
+ const result = this.shield.scanInput(text);
89
+ if (result.threats.length > 0 && this.onThreat) {
90
+ try { this.onThreat({ phase: 'tool_output', threats: result.threats, text }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
91
+ }
92
+ if (result.blocked) {
93
+ throw new ShieldBlockError('Tool output blocked by Agent Shield — possible injection via tool response', result.threats);
94
+ }
95
+ }
96
+
97
+ async handleChainEnd(outputs) {
98
+ const text = typeof outputs === 'string' ? outputs : JSON.stringify(outputs);
99
+ if (!text) return;
100
+
101
+ const result = this.shield.scanOutput(text);
102
+ if (result.threats.length > 0 && this.onThreat) {
103
+ try { this.onThreat({ phase: 'chain_output', threats: result.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
104
+ }
105
+ if (result.blocked) {
106
+ throw new ShieldBlockError('Chain output blocked by Agent Shield', result.threats);
107
+ }
108
+ }
109
+
110
+ async handleAgentAction(action) {
111
+ if (action && action.toolInput) {
112
+ const text = typeof action.toolInput === 'string' ? action.toolInput : JSON.stringify(action.toolInput);
113
+ const result = this.shield.scanInput(text);
114
+ if (result.threats.length > 0 && this.onThreat) {
115
+ try { this.onThreat({ phase: 'agent_action', tool: action.tool, threats: result.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
116
+ }
117
+ if (result.blocked) {
118
+ throw new ShieldBlockError(`Agent action "${action.tool}" blocked by Agent Shield`, result.threats);
119
+ }
120
+ }
121
+ }
122
+
123
+ getStats() {
124
+ return this.shield.getStats();
125
+ }
126
+ }
127
+
128
+ // =========================================================================
129
+ // Anthropic SDK Integration
130
+ // =========================================================================
131
+
132
+ /**
133
+ * Wraps an Anthropic client with threat scanning on messages.
134
+ *
135
+ * Usage:
136
+ * const Anthropic = require('@anthropic-ai/sdk');
137
+ * const { shieldAnthropicClient } = require('agent-shield/src/integrations');
138
+ * const client = shieldAnthropicClient(new Anthropic(), { blockOnThreat: true });
139
+ * const msg = await client.messages.create({ model: 'claude-sonnet-4-20250514', messages: [...] });
140
+ */
141
+ function shieldAnthropicClient(client, options = {}) {
142
+ const shield = new AgentShield({
143
+ sensitivity: options.sensitivity || 'high',
144
+ blockOnThreat: options.blockOnThreat || false
145
+ });
146
+ const piiRedactor = options.pii ? new PIIRedactor() : null;
147
+ const breaker = options.circuitBreaker ? new CircuitBreaker(options.circuitBreaker) : null;
148
+ const onThreat = options.onThreat || null;
149
+
150
+ const originalCreate = client.messages.create.bind(client.messages);
151
+
152
+ client.messages.create = async function shieldedCreate(params) {
153
+ // Circuit breaker check
154
+ if (breaker) {
155
+ const status = breaker.check();
156
+ if (!status.allowed) {
157
+ throw new ShieldBlockError(`Circuit breaker open: ${status.reason}`, []);
158
+ }
159
+ }
160
+
161
+ // Scan all message content
162
+ for (const msg of params.messages || []) {
163
+ const text = extractAnthropicText(msg);
164
+ if (!text) continue;
165
+
166
+ // PII redaction
167
+ if (piiRedactor && msg.role === 'user') {
168
+ const piiResult = piiRedactor.redact(text);
169
+ if (piiResult.count > 0) {
170
+ setAnthropicText(msg, piiResult.redacted);
171
+ }
172
+ }
173
+
174
+ const result = shield.scanInput(text);
175
+ if (result.threats.length > 0) {
176
+ if (onThreat) { try { onThreat({ phase: 'input', role: msg.role, threats: result.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); } }
177
+ if (breaker) breaker.recordThreat(result.threats.length);
178
+ if (result.blocked) {
179
+ throw new ShieldBlockError('Message blocked by Agent Shield', result.threats);
180
+ }
181
+ }
182
+ }
183
+
184
+ // Scan system prompt
185
+ if (params.system) {
186
+ const sysText = typeof params.system === 'string' ? params.system : params.system.map(b => b.text || '').join(' ');
187
+ const sysResult = shield.scanInput(sysText);
188
+ if (sysResult.blocked) {
189
+ throw new ShieldBlockError('System prompt contains threats', sysResult.threats);
190
+ }
191
+ }
192
+
193
+ // Make the actual API call
194
+ const response = await originalCreate(params);
195
+
196
+ // Scan output text
197
+ const outputText = response.content?.map(b => b.text || '').join(' ') || '';
198
+ if (outputText) {
199
+ const outputResult = shield.scanOutput(outputText);
200
+ if (outputResult.threats.length > 0 && onThreat) {
201
+ try { onThreat({ phase: 'output', threats: outputResult.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
202
+ }
203
+ if (outputResult.blocked) {
204
+ throw new ShieldBlockError('Response blocked by Agent Shield', outputResult.threats);
205
+ }
206
+ }
207
+
208
+ // Scan tool_use blocks in response — tool calls can contain injection payloads
209
+ if (response.content && Array.isArray(response.content)) {
210
+ for (const block of response.content) {
211
+ if (block.type === 'tool_use' && block.input) {
212
+ const toolText = typeof block.input === 'string' ? block.input : JSON.stringify(block.input);
213
+ const toolResult = shield.scanInput(toolText);
214
+ if (toolResult.threats.length > 0) {
215
+ if (onThreat) { try { onThreat({ phase: 'tool_call', tool: block.name, threats: toolResult.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); } }
216
+ if (breaker) breaker.recordThreat(toolResult.threats.length);
217
+ if (toolResult.blocked) {
218
+ throw new ShieldBlockError(`Tool call "${block.name}" blocked by Agent Shield`, toolResult.threats);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ // Scan tool_result messages in input — tool responses can inject threats
226
+ for (const msg of params.messages || []) {
227
+ if (msg.role === 'tool' || (Array.isArray(msg.content) && msg.content.some(b => b.type === 'tool_result'))) {
228
+ const toolRespText = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
229
+ const toolRespResult = shield.scanInput(toolRespText);
230
+ if (toolRespResult.threats.length > 0) {
231
+ if (onThreat) { try { onThreat({ phase: 'tool_response', threats: toolRespResult.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); } }
232
+ if (breaker) breaker.recordThreat(toolRespResult.threats.length);
233
+ if (toolRespResult.blocked) {
234
+ throw new ShieldBlockError('Tool response blocked by Agent Shield', toolRespResult.threats);
235
+ }
236
+ }
237
+ }
238
+ }
239
+
240
+ // Attach scan metadata
241
+ response._shield = {
242
+ scanned: true,
243
+ toolCallsScanned: (response.content || []).filter(b => b.type === 'tool_use').length,
244
+ stats: shield.getStats()
245
+ };
246
+
247
+ return response;
248
+ };
249
+
250
+ client._shield = shield;
251
+ return client;
252
+ }
253
+
254
+ function extractAnthropicText(msg) {
255
+ if (typeof msg.content === 'string') return msg.content;
256
+ if (Array.isArray(msg.content)) {
257
+ return msg.content.map(b => b.text || '').join(' ');
258
+ }
259
+ return '';
260
+ }
261
+
262
+ function setAnthropicText(msg, text) {
263
+ if (typeof msg.content === 'string') {
264
+ msg.content = text;
265
+ } else if (Array.isArray(msg.content)) {
266
+ // Replace text across text blocks proportionally to preserve multi-block structure.
267
+ // Non-text blocks (images, tool_use, etc.) are left untouched.
268
+ const textBlocks = msg.content.filter(b => b.type === 'text' || b.text);
269
+ if (textBlocks.length === 1) {
270
+ textBlocks[0].text = text;
271
+ } else if (textBlocks.length > 1) {
272
+ // Split redacted text across blocks proportionally to original lengths
273
+ const totalLen = textBlocks.reduce((sum, b) => sum + (b.text || '').length, 0);
274
+ if (totalLen === 0) {
275
+ textBlocks[0].text = text;
276
+ } else {
277
+ let offset = 0;
278
+ for (let i = 0; i < textBlocks.length; i++) {
279
+ const ratio = (textBlocks[i].text || '').length / totalLen;
280
+ const chunkLen = i === textBlocks.length - 1
281
+ ? text.length - offset
282
+ : Math.round(text.length * ratio);
283
+ textBlocks[i].text = text.slice(offset, offset + chunkLen);
284
+ offset += chunkLen;
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+
291
+ // =========================================================================
292
+ // OpenAI SDK Integration
293
+ // =========================================================================
294
+
295
+ /**
296
+ * Wraps an OpenAI client with threat scanning.
297
+ *
298
+ * Usage:
299
+ * const OpenAI = require('openai');
300
+ * const { shieldOpenAIClient } = require('agent-shield/src/integrations');
301
+ * const client = shieldOpenAIClient(new OpenAI(), { blockOnThreat: true });
302
+ * const completion = await client.chat.completions.create({ model: 'gpt-4', messages: [...] });
303
+ */
304
+ function shieldOpenAIClient(client, options = {}) {
305
+ const shield = new AgentShield({
306
+ sensitivity: options.sensitivity || 'high',
307
+ blockOnThreat: options.blockOnThreat || false
308
+ });
309
+ const piiRedactor = options.pii ? new PIIRedactor() : null;
310
+ const breaker = options.circuitBreaker ? new CircuitBreaker(options.circuitBreaker) : null;
311
+ const onThreat = options.onThreat || null;
312
+
313
+ const originalCreate = client.chat.completions.create.bind(client.chat.completions);
314
+
315
+ client.chat.completions.create = async function shieldedCreate(params) {
316
+ // Circuit breaker check
317
+ if (breaker) {
318
+ const status = breaker.check();
319
+ if (!status.allowed) {
320
+ throw new ShieldBlockError(`Circuit breaker open: ${status.reason}`, []);
321
+ }
322
+ }
323
+
324
+ // Scan all messages
325
+ for (const msg of params.messages || []) {
326
+ const text = typeof msg.content === 'string' ? msg.content : (msg.content || []).map(p => p.text || '').join(' ');
327
+ if (!text) continue;
328
+
329
+ // PII redaction
330
+ if (piiRedactor && msg.role === 'user') {
331
+ const piiResult = piiRedactor.redact(text);
332
+ if (piiResult.count > 0) {
333
+ msg.content = piiResult.redacted;
334
+ }
335
+ }
336
+
337
+ const result = shield.scanInput(text);
338
+ if (result.threats.length > 0) {
339
+ if (onThreat) { try { onThreat({ phase: 'input', role: msg.role, threats: result.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); } }
340
+ if (breaker) breaker.recordThreat(result.threats.length);
341
+ if (result.blocked) {
342
+ throw new ShieldBlockError('Message blocked by Agent Shield', result.threats);
343
+ }
344
+ }
345
+ }
346
+
347
+ // Scan system message if present
348
+ const systemMsg = (params.messages || []).find(m => m.role === 'system');
349
+ if (systemMsg && typeof systemMsg.content === 'string') {
350
+ const sysResult = shield.scanInput(systemMsg.content);
351
+ if (sysResult.blocked) {
352
+ throw new ShieldBlockError('System message contains threats', sysResult.threats);
353
+ }
354
+ }
355
+
356
+ // Scan tool/function response messages in input
357
+ for (const msg of params.messages || []) {
358
+ if (msg.role === 'tool' || msg.role === 'function') {
359
+ const toolRespText = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
360
+ const toolRespResult = shield.scanInput(toolRespText);
361
+ if (toolRespResult.threats.length > 0) {
362
+ if (onThreat) { try { onThreat({ phase: 'tool_response', tool_call_id: msg.tool_call_id, threats: toolRespResult.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); } }
363
+ if (breaker) breaker.recordThreat(toolRespResult.threats.length);
364
+ if (toolRespResult.blocked) {
365
+ throw new ShieldBlockError('Tool response blocked by Agent Shield', toolRespResult.threats);
366
+ }
367
+ }
368
+ }
369
+ }
370
+
371
+ const response = await originalCreate(params);
372
+
373
+ // Scan all output choices, not just the first
374
+ for (const choice of response.choices || []) {
375
+ const outputText = choice?.message?.content || '';
376
+ if (outputText) {
377
+ const outputResult = shield.scanOutput(outputText);
378
+ if (outputResult.threats.length > 0 && onThreat) {
379
+ try { onThreat({ phase: 'output', choiceIndex: choice.index, threats: outputResult.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
380
+ }
381
+ if (outputResult.blocked) {
382
+ throw new ShieldBlockError('Response blocked by Agent Shield', outputResult.threats);
383
+ }
384
+ }
385
+
386
+ // Scan tool calls in response
387
+ const toolCalls = choice?.message?.tool_calls || choice?.message?.function_call ? [choice.message.function_call] : [];
388
+ for (const tc of (choice?.message?.tool_calls || [])) {
389
+ if (tc.function && tc.function.arguments) {
390
+ const argsResult = shield.scanInput(tc.function.arguments);
391
+ if (argsResult.threats.length > 0) {
392
+ if (onThreat) { try { onThreat({ phase: 'tool_call', tool: tc.function.name, threats: argsResult.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); } }
393
+ if (argsResult.blocked) {
394
+ throw new ShieldBlockError(`Tool call "${tc.function.name}" blocked by Agent Shield`, argsResult.threats);
395
+ }
396
+ }
397
+ }
398
+ }
399
+ }
400
+
401
+ response._shield = {
402
+ scanned: true,
403
+ choicesScanned: (response.choices || []).length,
404
+ stats: shield.getStats()
405
+ };
406
+ return response;
407
+ };
408
+
409
+ client._shield = shield;
410
+ return client;
411
+ }
412
+
413
+ // =========================================================================
414
+ // Vercel AI SDK Integration
415
+ // =========================================================================
416
+
417
+ /**
418
+ * Middleware for Vercel AI SDK's streamText/generateText.
419
+ *
420
+ * Usage:
421
+ * const { shieldVercelAI } = require('agent-shield/src/integrations');
422
+ * const middleware = shieldVercelAI({ blockOnThreat: true });
423
+ * const result = await generateText({ model, prompt, middleware });
424
+ */
425
+ function shieldVercelAI(options = {}) {
426
+ const shield = new AgentShield({
427
+ sensitivity: options.sensitivity || 'high',
428
+ blockOnThreat: options.blockOnThreat || false
429
+ });
430
+ const onThreat = options.onThreat || null;
431
+
432
+ return {
433
+ transformParams: async ({ params }) => {
434
+ // Scan all messages
435
+ for (const msg of params.messages || []) {
436
+ const text = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
437
+ const result = shield.scanInput(text);
438
+ if (result.threats.length > 0 && onThreat) {
439
+ try { onThreat({ phase: 'input', threats: result.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
440
+ }
441
+ if (result.blocked) {
442
+ throw new ShieldBlockError('Blocked by Agent Shield', result.threats);
443
+ }
444
+ }
445
+ return params;
446
+ },
447
+ wrapGenerate: async ({ doGenerate }) => {
448
+ const result = await doGenerate();
449
+ const text = result.text || '';
450
+ if (text) {
451
+ const scanResult = shield.scanOutput(text);
452
+ if (scanResult.threats.length > 0 && onThreat) {
453
+ try { onThreat({ phase: 'output', threats: scanResult.threats }); } catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
454
+ }
455
+ if (scanResult.blocked) {
456
+ throw new ShieldBlockError('Response blocked by Agent Shield', scanResult.threats);
457
+ }
458
+ }
459
+ return result;
460
+ }
461
+ };
462
+ }
463
+
464
+ // =========================================================================
465
+ // Generic Fetch Wrapper
466
+ // =========================================================================
467
+
468
+ /**
469
+ * Wraps fetch to scan request/response bodies for threats.
470
+ *
471
+ * Usage:
472
+ * const { shieldFetch } = require('agent-shield/src/integrations');
473
+ * const safeFetch = shieldFetch(fetch, { blockOnThreat: true });
474
+ * const response = await safeFetch('https://api.openai.com/...', { body: JSON.stringify(data) });
475
+ */
476
+ function shieldFetch(fetchFn, options = {}) {
477
+ const shield = new AgentShield({
478
+ sensitivity: options.sensitivity || 'high',
479
+ blockOnThreat: options.blockOnThreat || false
480
+ });
481
+
482
+ return async function shieldedFetch(url, init = {}) {
483
+ // Scan request body
484
+ if (init.body) {
485
+ const bodyText = typeof init.body === 'string' ? init.body : JSON.stringify(init.body);
486
+ const result = shield.scanInput(bodyText);
487
+ if (result.blocked) {
488
+ throw new ShieldBlockError('Request body blocked by Agent Shield', result.threats);
489
+ }
490
+ }
491
+
492
+ const response = await fetchFn(url, init);
493
+ return response;
494
+ };
495
+ }
496
+
497
+ // =========================================================================
498
+ // Shared Error Class
499
+ // =========================================================================
500
+
501
+ class ShieldBlockError extends Error {
502
+ constructor(message, threats = []) {
503
+ super(message);
504
+ this.name = 'ShieldBlockError';
505
+ this.threats = threats;
506
+ this.code = 'AGENT_SHIELD_BLOCKED';
507
+ }
508
+ }
509
+
510
+ module.exports = {
511
+ // LangChain
512
+ ShieldCallbackHandler,
513
+
514
+ // Anthropic
515
+ shieldAnthropicClient,
516
+
517
+ // OpenAI
518
+ shieldOpenAIClient,
519
+
520
+ // Vercel AI
521
+ shieldVercelAI,
522
+
523
+ // Generic
524
+ shieldFetch,
525
+
526
+ // Error
527
+ ShieldBlockError
528
+ };