agentshield-sdk 13.5.0 → 14.2.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/CHANGELOG.md +192 -0
- package/README.md +12 -1
- package/package.json +2 -2
- package/src/detector-core.js +329 -51
- package/src/enterprise.js +127 -12
- package/src/integrations-frameworks.js +463 -0
- package/src/integrations.js +207 -0
- package/src/main.js +11 -14
- package/src/mcp-guard.js +52 -1
- package/src/middleware.js +107 -2
- package/src/native-scanner.js +104 -0
- package/src/plugin-system.js +422 -6
- package/src/supply-chain-scanner.js +164 -0
- package/src/persistent-learning.js +0 -161
- package/src/threat-intel-federation.js +0 -343
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — Framework Integration Wrappers
|
|
5
|
+
*
|
|
6
|
+
* Plug-and-play integrations for next-generation AI agent frameworks:
|
|
7
|
+
* - CrewAI (task decorators & callbacks)
|
|
8
|
+
* - Google Agent Development Kit (plugin system)
|
|
9
|
+
* - Microsoft Agent Framework (middleware pipeline)
|
|
10
|
+
*
|
|
11
|
+
* These close gaps identified in the Microsoft Agent Governance Toolkit
|
|
12
|
+
* parity audit.
|
|
13
|
+
*
|
|
14
|
+
* @module integrations-frameworks
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { AgentShield } = require('./index');
|
|
18
|
+
const { ShieldBlockError } = require('./integrations');
|
|
19
|
+
|
|
20
|
+
// =========================================================================
|
|
21
|
+
// CrewAI Integration
|
|
22
|
+
// =========================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates Agent Shield callbacks for CrewAI task lifecycle.
|
|
26
|
+
*
|
|
27
|
+
* CrewAI uses task decorators and callbacks. This wrapper provides
|
|
28
|
+
* beforeTask / afterTask hooks that scan task descriptions, expected
|
|
29
|
+
* outputs, and task results for prompt injection and other threats.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* const { shieldCrewAI } = require('agentshield-sdk/src/integrations-frameworks');
|
|
33
|
+
* const { beforeTask, afterTask } = shieldCrewAI({ blockOnThreat: true });
|
|
34
|
+
*
|
|
35
|
+
* // In your CrewAI task lifecycle:
|
|
36
|
+
* beforeTask(task, agent); // throws ShieldBlockError if threat found
|
|
37
|
+
* const output = await task.run();
|
|
38
|
+
* afterTask(task, output); // throws ShieldBlockError if threat found
|
|
39
|
+
*
|
|
40
|
+
* @param {object} [options]
|
|
41
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity level.
|
|
42
|
+
* @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
|
|
43
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
|
|
44
|
+
* @param {function} [options.onThreat] - Callback when a threat is detected.
|
|
45
|
+
* @returns {{ beforeTask: function, afterTask: function, shield: AgentShield }}
|
|
46
|
+
*/
|
|
47
|
+
function shieldCrewAI(options = {}) {
|
|
48
|
+
const shield = new AgentShield({
|
|
49
|
+
sensitivity: options.sensitivity || 'high',
|
|
50
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
51
|
+
blockThreshold: options.blockThreshold || 'high'
|
|
52
|
+
});
|
|
53
|
+
const onThreat = options.onThreat || null;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Scans a CrewAI task before execution.
|
|
57
|
+
* Inspects task.description and task.expected_output for injection.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} task - CrewAI task object.
|
|
60
|
+
* @param {object} [agent] - CrewAI agent assigned to the task.
|
|
61
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
62
|
+
*/
|
|
63
|
+
function beforeTask(task, agent) {
|
|
64
|
+
if (!task) return;
|
|
65
|
+
|
|
66
|
+
const fields = [];
|
|
67
|
+
if (task.description) fields.push(String(task.description));
|
|
68
|
+
if (task.expected_output) fields.push(String(task.expected_output));
|
|
69
|
+
|
|
70
|
+
for (const text of fields) {
|
|
71
|
+
const result = shield.scanInput(text);
|
|
72
|
+
if (result.threats && result.threats.length > 0) {
|
|
73
|
+
if (onThreat) {
|
|
74
|
+
try {
|
|
75
|
+
onThreat({
|
|
76
|
+
phase: 'before_task',
|
|
77
|
+
threats: result.threats,
|
|
78
|
+
task: task.description || '',
|
|
79
|
+
agent: agent && agent.role ? agent.role : undefined
|
|
80
|
+
});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (result.blocked) {
|
|
86
|
+
throw new ShieldBlockError('CrewAI task blocked by Agent Shield', result.threats);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Scans a CrewAI task output after execution.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} task - CrewAI task object.
|
|
96
|
+
* @param {*} output - Task execution output.
|
|
97
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
98
|
+
*/
|
|
99
|
+
function afterTask(task, output) {
|
|
100
|
+
if (output == null) return;
|
|
101
|
+
|
|
102
|
+
const text = typeof output === 'string' ? output : JSON.stringify(output);
|
|
103
|
+
const result = shield.scanOutput(text);
|
|
104
|
+
|
|
105
|
+
if (result.threats && result.threats.length > 0) {
|
|
106
|
+
if (onThreat) {
|
|
107
|
+
try {
|
|
108
|
+
onThreat({
|
|
109
|
+
phase: 'after_task',
|
|
110
|
+
threats: result.threats,
|
|
111
|
+
task: task && task.description ? task.description : ''
|
|
112
|
+
});
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (result.blocked) {
|
|
118
|
+
throw new ShieldBlockError('CrewAI task output blocked by Agent Shield', result.threats);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { beforeTask, afterTask, shield };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =========================================================================
|
|
127
|
+
// Google Agent Development Kit (ADK) Integration
|
|
128
|
+
// =========================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates Agent Shield hooks for the Google Agent Development Kit plugin system.
|
|
132
|
+
*
|
|
133
|
+
* Google ADK uses a plugin architecture with lifecycle hooks. This wrapper
|
|
134
|
+
* provides beforeToolCall, afterToolCall, and beforeGenerate functions that
|
|
135
|
+
* scan tool arguments, tool results, and generation prompts for threats.
|
|
136
|
+
*
|
|
137
|
+
* Usage:
|
|
138
|
+
* const { shieldGoogleADK } = require('agentshield-sdk/src/integrations-frameworks');
|
|
139
|
+
* const hooks = shieldGoogleADK({ blockOnThreat: true });
|
|
140
|
+
*
|
|
141
|
+
* // Register as ADK plugin callbacks:
|
|
142
|
+
* hooks.beforeToolCall('web_search', { query: userInput });
|
|
143
|
+
* const result = await tool.execute(args);
|
|
144
|
+
* hooks.afterToolCall('web_search', result);
|
|
145
|
+
* hooks.beforeGenerate(prompt);
|
|
146
|
+
*
|
|
147
|
+
* @param {object} [options]
|
|
148
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity level.
|
|
149
|
+
* @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
|
|
150
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
|
|
151
|
+
* @param {function} [options.onThreat] - Callback when a threat is detected.
|
|
152
|
+
* @returns {{ beforeToolCall: function, afterToolCall: function, beforeGenerate: function, shield: AgentShield }}
|
|
153
|
+
*/
|
|
154
|
+
function shieldGoogleADK(options = {}) {
|
|
155
|
+
const shield = new AgentShield({
|
|
156
|
+
sensitivity: options.sensitivity || 'high',
|
|
157
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
158
|
+
blockThreshold: options.blockThreshold || 'high'
|
|
159
|
+
});
|
|
160
|
+
const onThreat = options.onThreat || null;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Scans tool arguments before a tool call.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} toolName - Name of the tool being called.
|
|
166
|
+
* @param {*} args - Tool arguments (object, string, or any serializable value).
|
|
167
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
168
|
+
*/
|
|
169
|
+
function beforeToolCall(toolName, args) {
|
|
170
|
+
if (args == null) return;
|
|
171
|
+
|
|
172
|
+
const text = typeof args === 'string' ? args : JSON.stringify(args);
|
|
173
|
+
const result = shield.scanInput(text);
|
|
174
|
+
|
|
175
|
+
if (result.threats && result.threats.length > 0) {
|
|
176
|
+
if (onThreat) {
|
|
177
|
+
try {
|
|
178
|
+
onThreat({
|
|
179
|
+
phase: 'before_tool_call',
|
|
180
|
+
toolName: toolName || 'unknown',
|
|
181
|
+
threats: result.threats
|
|
182
|
+
});
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (result.blocked) {
|
|
188
|
+
throw new ShieldBlockError(
|
|
189
|
+
`Google ADK tool "${toolName || 'unknown'}" call blocked by Agent Shield`,
|
|
190
|
+
result.threats
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Scans tool results after a tool call.
|
|
198
|
+
*
|
|
199
|
+
* @param {string} toolName - Name of the tool that was called.
|
|
200
|
+
* @param {*} result - Tool execution result.
|
|
201
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
202
|
+
*/
|
|
203
|
+
function afterToolCall(toolName, toolResult) {
|
|
204
|
+
if (toolResult == null) return;
|
|
205
|
+
|
|
206
|
+
const text = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult);
|
|
207
|
+
const result = shield.scanOutput(text);
|
|
208
|
+
|
|
209
|
+
if (result.threats && result.threats.length > 0) {
|
|
210
|
+
if (onThreat) {
|
|
211
|
+
try {
|
|
212
|
+
onThreat({
|
|
213
|
+
phase: 'after_tool_call',
|
|
214
|
+
toolName: toolName || 'unknown',
|
|
215
|
+
threats: result.threats
|
|
216
|
+
});
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (result.blocked) {
|
|
222
|
+
throw new ShieldBlockError(
|
|
223
|
+
`Google ADK tool "${toolName || 'unknown'}" result blocked by Agent Shield`,
|
|
224
|
+
result.threats
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Scans a prompt before generation.
|
|
232
|
+
*
|
|
233
|
+
* @param {string|*} prompt - The prompt to scan.
|
|
234
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
235
|
+
*/
|
|
236
|
+
function beforeGenerate(prompt) {
|
|
237
|
+
if (prompt == null) return;
|
|
238
|
+
|
|
239
|
+
const text = typeof prompt === 'string' ? prompt : JSON.stringify(prompt);
|
|
240
|
+
const result = shield.scanInput(text);
|
|
241
|
+
|
|
242
|
+
if (result.threats && result.threats.length > 0) {
|
|
243
|
+
if (onThreat) {
|
|
244
|
+
try {
|
|
245
|
+
onThreat({
|
|
246
|
+
phase: 'before_generate',
|
|
247
|
+
threats: result.threats
|
|
248
|
+
});
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (result.blocked) {
|
|
254
|
+
throw new ShieldBlockError('Google ADK generation blocked by Agent Shield', result.threats);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { beforeToolCall, afterToolCall, beforeGenerate, shield };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// =========================================================================
|
|
263
|
+
// Microsoft Agent Framework Integration
|
|
264
|
+
// =========================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Creates Agent Shield middleware for the Microsoft Agent Framework pipeline.
|
|
268
|
+
*
|
|
269
|
+
* The MS Agent Framework uses a middleware pattern where each middleware
|
|
270
|
+
* receives a context and a next() function. This wrapper scans context.input
|
|
271
|
+
* before calling next(), then scans context.output after next() returns.
|
|
272
|
+
*
|
|
273
|
+
* Usage:
|
|
274
|
+
* const { shieldMSAgentFramework } = require('agentshield-sdk/src/integrations-frameworks');
|
|
275
|
+
* const { agentMiddleware } = shieldMSAgentFramework({ blockOnThreat: true });
|
|
276
|
+
*
|
|
277
|
+
* // Register in the MS Agent Framework pipeline:
|
|
278
|
+
* agent.use(agentMiddleware);
|
|
279
|
+
*
|
|
280
|
+
* @param {object} [options]
|
|
281
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity level.
|
|
282
|
+
* @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
|
|
283
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
|
|
284
|
+
* @param {function} [options.onThreat] - Callback when a threat is detected.
|
|
285
|
+
* @returns {{ agentMiddleware: function, shield: AgentShield }}
|
|
286
|
+
*/
|
|
287
|
+
function shieldMSAgentFramework(options = {}) {
|
|
288
|
+
const shield = new AgentShield({
|
|
289
|
+
sensitivity: options.sensitivity || 'high',
|
|
290
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
291
|
+
blockThreshold: options.blockThreshold || 'high'
|
|
292
|
+
});
|
|
293
|
+
const onThreat = options.onThreat || null;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Middleware function for the MS Agent Framework pipeline.
|
|
297
|
+
* Scans context.input before next() and context.output after next().
|
|
298
|
+
*
|
|
299
|
+
* @param {object} context - Pipeline context with input/output properties.
|
|
300
|
+
* @param {function} next - Next middleware in the pipeline.
|
|
301
|
+
* @throws {ShieldBlockError} If a threat is detected and blocking is enabled.
|
|
302
|
+
*/
|
|
303
|
+
async function agentMiddleware(context, next) {
|
|
304
|
+
// Scan input before passing to next middleware
|
|
305
|
+
if (context && context.input != null) {
|
|
306
|
+
const inputText = typeof context.input === 'string'
|
|
307
|
+
? context.input
|
|
308
|
+
: JSON.stringify(context.input);
|
|
309
|
+
|
|
310
|
+
const inputResult = shield.scanInput(inputText);
|
|
311
|
+
|
|
312
|
+
if (inputResult.threats && inputResult.threats.length > 0) {
|
|
313
|
+
if (onThreat) {
|
|
314
|
+
try {
|
|
315
|
+
onThreat({
|
|
316
|
+
phase: 'input',
|
|
317
|
+
threats: inputResult.threats,
|
|
318
|
+
text: inputText
|
|
319
|
+
});
|
|
320
|
+
} catch (e) {
|
|
321
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (inputResult.blocked) {
|
|
325
|
+
throw new ShieldBlockError(
|
|
326
|
+
'MS Agent Framework input blocked by Agent Shield',
|
|
327
|
+
inputResult.threats
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Call next middleware in the pipeline
|
|
334
|
+
await next();
|
|
335
|
+
|
|
336
|
+
// Scan output after pipeline execution
|
|
337
|
+
if (context && context.output != null) {
|
|
338
|
+
const outputText = typeof context.output === 'string'
|
|
339
|
+
? context.output
|
|
340
|
+
: JSON.stringify(context.output);
|
|
341
|
+
|
|
342
|
+
const outputResult = shield.scanOutput(outputText);
|
|
343
|
+
|
|
344
|
+
if (outputResult.threats && outputResult.threats.length > 0) {
|
|
345
|
+
if (onThreat) {
|
|
346
|
+
try {
|
|
347
|
+
onThreat({
|
|
348
|
+
phase: 'output',
|
|
349
|
+
threats: outputResult.threats,
|
|
350
|
+
text: outputText
|
|
351
|
+
});
|
|
352
|
+
} catch (e) {
|
|
353
|
+
console.error('[Agent Shield] onThreat callback error:', e.message);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (outputResult.blocked) {
|
|
357
|
+
throw new ShieldBlockError(
|
|
358
|
+
'MS Agent Framework output blocked by Agent Shield',
|
|
359
|
+
outputResult.threats
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return { agentMiddleware, shield };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// =========================================================================
|
|
370
|
+
// Google ADK JavaScript (adk-js) Integration
|
|
371
|
+
// =========================================================================
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Creates Agent Shield middleware for the Google ADK JavaScript/TypeScript SDK.
|
|
375
|
+
*
|
|
376
|
+
* Google ADK for JS (GA April 2026) uses a callback-based agent lifecycle.
|
|
377
|
+
* This wrapper returns a plugin object compatible with ADK-JS's plugin API
|
|
378
|
+
* that scans tool inputs, tool outputs, and generated content.
|
|
379
|
+
*
|
|
380
|
+
* Usage:
|
|
381
|
+
* const { shieldGoogleADKJS } = require('agentshield-sdk/src/integrations-frameworks');
|
|
382
|
+
* const plugin = shieldGoogleADKJS({ blockOnThreat: true });
|
|
383
|
+
*
|
|
384
|
+
* // Register with ADK-JS agent:
|
|
385
|
+
* const agent = new Agent({ plugins: [plugin] });
|
|
386
|
+
*
|
|
387
|
+
* @param {object} [options]
|
|
388
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity level.
|
|
389
|
+
* @param {boolean} [options.blockOnThreat=true] - Whether to throw on threat detection.
|
|
390
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that triggers a block.
|
|
391
|
+
* @param {function} [options.onThreat] - Callback when a threat is detected.
|
|
392
|
+
* @returns {{ name: string, beforeToolCall: function, afterToolCall: function, beforeModelCall: function, afterModelCall: function, shield: AgentShield }}
|
|
393
|
+
*/
|
|
394
|
+
function shieldGoogleADKJS(options = {}) {
|
|
395
|
+
const shield = new AgentShield({
|
|
396
|
+
sensitivity: options.sensitivity || 'high',
|
|
397
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
398
|
+
blockThreshold: options.blockThreshold || 'high'
|
|
399
|
+
});
|
|
400
|
+
const onThreat = options.onThreat || null;
|
|
401
|
+
|
|
402
|
+
function _scan(text, phase, meta) {
|
|
403
|
+
if (!text) return;
|
|
404
|
+
const result = shield.scanInput(text);
|
|
405
|
+
if (result.threats && result.threats.length > 0) {
|
|
406
|
+
if (onThreat) {
|
|
407
|
+
try { onThreat({ phase, ...meta, threats: result.threats }); }
|
|
408
|
+
catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
|
|
409
|
+
}
|
|
410
|
+
if (result.blocked) {
|
|
411
|
+
throw new ShieldBlockError(`Google ADK-JS ${phase} blocked by Agent Shield`, result.threats);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
name: 'AgentShieldPlugin',
|
|
418
|
+
|
|
419
|
+
beforeToolCall(context) {
|
|
420
|
+
const text = context.args != null
|
|
421
|
+
? (typeof context.args === 'string' ? context.args : JSON.stringify(context.args))
|
|
422
|
+
: null;
|
|
423
|
+
_scan(text, 'before_tool_call', { toolName: context.toolName || 'unknown' });
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
afterToolCall(context) {
|
|
427
|
+
const text = context.result != null
|
|
428
|
+
? (typeof context.result === 'string' ? context.result : JSON.stringify(context.result))
|
|
429
|
+
: null;
|
|
430
|
+
_scan(text, 'after_tool_call', { toolName: context.toolName || 'unknown' });
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
beforeModelCall(context) {
|
|
434
|
+
const text = context.prompt || context.input;
|
|
435
|
+
if (text) _scan(typeof text === 'string' ? text : JSON.stringify(text), 'before_model_call', {});
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
afterModelCall(context) {
|
|
439
|
+
const text = context.response || context.output;
|
|
440
|
+
if (!text) return;
|
|
441
|
+
const outputText = typeof text === 'string' ? text : JSON.stringify(text);
|
|
442
|
+
const result = shield.scanOutput(outputText);
|
|
443
|
+
if (result.threats && result.threats.length > 0) {
|
|
444
|
+
if (onThreat) {
|
|
445
|
+
try { onThreat({ phase: 'after_model_call', threats: result.threats }); }
|
|
446
|
+
catch (e) { console.error('[Agent Shield] onThreat callback error:', e.message); }
|
|
447
|
+
}
|
|
448
|
+
if (result.blocked) {
|
|
449
|
+
throw new ShieldBlockError('Google ADK-JS model output blocked by Agent Shield', result.threats);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
shield
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
module.exports = {
|
|
459
|
+
shieldCrewAI,
|
|
460
|
+
shieldGoogleADK,
|
|
461
|
+
shieldGoogleADKJS,
|
|
462
|
+
shieldMSAgentFramework
|
|
463
|
+
};
|
package/src/integrations.js
CHANGED
|
@@ -493,6 +493,210 @@ function shieldFetch(fetchFn, options = {}) {
|
|
|
493
493
|
};
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
+
// =========================================================================
|
|
497
|
+
// OpenAI Agents SDK (@openai/agents) — April 2026 release
|
|
498
|
+
// =========================================================================
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Creates guardrails for the OpenAI Agents SDK (@openai/agents).
|
|
502
|
+
*
|
|
503
|
+
* The OpenAI Agents SDK (Python and TypeScript, April 2026 update) uses a
|
|
504
|
+
* Guardrail primitive that validates inputs and outputs. Agent Shield plugs
|
|
505
|
+
* in natively as both an input guardrail (scanning user messages) and an
|
|
506
|
+
* output guardrail (scanning agent responses).
|
|
507
|
+
*
|
|
508
|
+
* Compatible with:
|
|
509
|
+
* - @openai/agents (TypeScript/JavaScript)
|
|
510
|
+
* - openai-agents (Python — use the Python SDK's equivalent)
|
|
511
|
+
*
|
|
512
|
+
* Usage:
|
|
513
|
+
* const { Agent, run } = require('@openai/agents');
|
|
514
|
+
* const { shieldOpenAIAgent } = require('agentshield-sdk');
|
|
515
|
+
*
|
|
516
|
+
* const { inputGuardrail, outputGuardrail } = shieldOpenAIAgent({
|
|
517
|
+
* blockOnThreat: true,
|
|
518
|
+
* sensitivity: 'high'
|
|
519
|
+
* });
|
|
520
|
+
*
|
|
521
|
+
* const agent = new Agent({
|
|
522
|
+
* name: 'Assistant',
|
|
523
|
+
* instructions: 'You are a helpful assistant',
|
|
524
|
+
* inputGuardrails: [inputGuardrail],
|
|
525
|
+
* outputGuardrails: [outputGuardrail]
|
|
526
|
+
* });
|
|
527
|
+
*
|
|
528
|
+
* const result = await run(agent, userInput);
|
|
529
|
+
*
|
|
530
|
+
* @param {object} [options]
|
|
531
|
+
* @param {string} [options.sensitivity='high'] - Detection sensitivity.
|
|
532
|
+
* @param {boolean} [options.blockOnThreat=true] - Trip guardrail tripwire on threats.
|
|
533
|
+
* @param {string} [options.blockThreshold='high'] - Minimum severity that blocks.
|
|
534
|
+
* @param {boolean} [options.pii=true] - Redact PII from inputs before handing to the agent.
|
|
535
|
+
* @param {boolean} [options.scanToolCalls=true] - Scan arguments to tool calls.
|
|
536
|
+
* @param {function} [options.onThreat] - Callback when threat detected.
|
|
537
|
+
* @returns {{ inputGuardrail: object, outputGuardrail: object, toolGuardrail: object, shield: AgentShield }}
|
|
538
|
+
*/
|
|
539
|
+
function shieldOpenAIAgent(options = {}) {
|
|
540
|
+
const shield = new AgentShield({
|
|
541
|
+
sensitivity: options.sensitivity || 'high',
|
|
542
|
+
blockOnThreat: options.blockOnThreat !== false,
|
|
543
|
+
blockThreshold: options.blockThreshold || 'high',
|
|
544
|
+
onThreat: options.onThreat
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const piiRedactor = options.pii !== false ? new PIIRedactor() : null;
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Input guardrail — runs on every user message before the agent sees it.
|
|
551
|
+
* Returns the shape expected by @openai/agents: { outputInfo, tripwireTriggered }.
|
|
552
|
+
*/
|
|
553
|
+
const inputGuardrail = {
|
|
554
|
+
name: 'Agent Shield — Input',
|
|
555
|
+
execute: async (ctx) => {
|
|
556
|
+
// @openai/agents passes { input, context, agent }. Input may be a string
|
|
557
|
+
// or an array of message items. We scan every user-role text item.
|
|
558
|
+
const input = ctx.input || ctx.message || ctx;
|
|
559
|
+
const texts = normalizeAgentInput(input);
|
|
560
|
+
|
|
561
|
+
let allThreats = [];
|
|
562
|
+
let maxSeverity = null;
|
|
563
|
+
|
|
564
|
+
for (const text of texts) {
|
|
565
|
+
const result = shield.scanInput(text);
|
|
566
|
+
if (result.threats && result.threats.length > 0) {
|
|
567
|
+
allThreats = allThreats.concat(result.threats);
|
|
568
|
+
for (const t of result.threats) {
|
|
569
|
+
if (!maxSeverity || SEVERITY_RANK[t.severity] < SEVERITY_RANK[maxSeverity]) {
|
|
570
|
+
maxSeverity = t.severity;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const tripwireTriggered = shouldBlock(maxSeverity, options.blockThreshold || 'high');
|
|
577
|
+
|
|
578
|
+
return {
|
|
579
|
+
outputInfo: {
|
|
580
|
+
threats: allThreats,
|
|
581
|
+
maxSeverity,
|
|
582
|
+
scannedBy: 'agentshield-sdk',
|
|
583
|
+
piiRedacted: piiRedactor ? true : false
|
|
584
|
+
},
|
|
585
|
+
tripwireTriggered
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Output guardrail — runs on agent responses before they reach the user.
|
|
592
|
+
* Catches prompt leaks, PII in output, canary tokens, etc.
|
|
593
|
+
*/
|
|
594
|
+
const outputGuardrail = {
|
|
595
|
+
name: 'Agent Shield — Output',
|
|
596
|
+
execute: async (ctx) => {
|
|
597
|
+
const output = ctx.agentOutput || ctx.output || ctx.finalOutput || ctx;
|
|
598
|
+
const text = typeof output === 'string' ? output : JSON.stringify(output);
|
|
599
|
+
|
|
600
|
+
const result = shield.scanOutput(text);
|
|
601
|
+
const threats = result.threats || [];
|
|
602
|
+
const maxSeverity = threats.reduce((acc, t) => {
|
|
603
|
+
if (!acc || SEVERITY_RANK[t.severity] < SEVERITY_RANK[acc]) return t.severity;
|
|
604
|
+
return acc;
|
|
605
|
+
}, null);
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
outputInfo: {
|
|
609
|
+
threats,
|
|
610
|
+
maxSeverity,
|
|
611
|
+
scannedBy: 'agentshield-sdk'
|
|
612
|
+
},
|
|
613
|
+
tripwireTriggered: shouldBlock(maxSeverity, options.blockThreshold || 'high')
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Tool guardrail — runs before tool execution. Scans tool arguments for
|
|
620
|
+
* injection, path traversal, SSRF targets, and other tool-abuse patterns.
|
|
621
|
+
*/
|
|
622
|
+
const toolGuardrail = {
|
|
623
|
+
name: 'Agent Shield — Tool',
|
|
624
|
+
execute: async (ctx) => {
|
|
625
|
+
const toolName = ctx.toolName || ctx.tool?.name || 'unknown';
|
|
626
|
+
const args = ctx.args || ctx.arguments || {};
|
|
627
|
+
const argsText = typeof args === 'string' ? args : JSON.stringify(args);
|
|
628
|
+
|
|
629
|
+
const result = shield.scanToolCall(toolName, typeof args === 'object' ? args : { input: args });
|
|
630
|
+
const threats = result.threats || [];
|
|
631
|
+
const maxSeverity = threats.reduce((acc, t) => {
|
|
632
|
+
if (!acc || SEVERITY_RANK[t.severity] < SEVERITY_RANK[acc]) return t.severity;
|
|
633
|
+
return acc;
|
|
634
|
+
}, null);
|
|
635
|
+
|
|
636
|
+
return {
|
|
637
|
+
outputInfo: {
|
|
638
|
+
threats,
|
|
639
|
+
toolName,
|
|
640
|
+
maxSeverity,
|
|
641
|
+
scannedBy: 'agentshield-sdk'
|
|
642
|
+
},
|
|
643
|
+
tripwireTriggered: shouldBlock(maxSeverity, options.blockThreshold || 'high')
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
return { inputGuardrail, outputGuardrail, toolGuardrail, shield };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** Severity rank for block-threshold comparisons (lower number = higher severity). */
|
|
652
|
+
const SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
653
|
+
|
|
654
|
+
/** Returns true if maxSeverity meets or exceeds the configured threshold. */
|
|
655
|
+
function shouldBlock(maxSeverity, threshold) {
|
|
656
|
+
if (!maxSeverity) return false;
|
|
657
|
+
return SEVERITY_RANK[maxSeverity] <= SEVERITY_RANK[threshold];
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Normalizes the OpenAI Agents SDK input shape into an array of user-role text strings.
|
|
662
|
+
* Handles: string, array of message items, message with content parts, etc.
|
|
663
|
+
*/
|
|
664
|
+
function normalizeAgentInput(input) {
|
|
665
|
+
if (typeof input === 'string') return [input];
|
|
666
|
+
if (!input) return [];
|
|
667
|
+
|
|
668
|
+
// Array of messages
|
|
669
|
+
if (Array.isArray(input)) {
|
|
670
|
+
const texts = [];
|
|
671
|
+
for (const item of input) {
|
|
672
|
+
if (typeof item === 'string') texts.push(item);
|
|
673
|
+
else if (item?.role === 'user' || item?.role === 'system') {
|
|
674
|
+
if (typeof item.content === 'string') texts.push(item.content);
|
|
675
|
+
else if (Array.isArray(item.content)) {
|
|
676
|
+
for (const part of item.content) {
|
|
677
|
+
if (typeof part === 'string') texts.push(part);
|
|
678
|
+
else if (part?.type === 'text' && part.text) texts.push(part.text);
|
|
679
|
+
else if (part?.text) texts.push(part.text);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return texts;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Single message object
|
|
688
|
+
if (input.content) {
|
|
689
|
+
if (typeof input.content === 'string') return [input.content];
|
|
690
|
+
if (Array.isArray(input.content)) {
|
|
691
|
+
return input.content
|
|
692
|
+
.map(p => typeof p === 'string' ? p : (p?.text || ''))
|
|
693
|
+
.filter(Boolean);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return [];
|
|
698
|
+
}
|
|
699
|
+
|
|
496
700
|
// =========================================================================
|
|
497
701
|
// Shared Error Class
|
|
498
702
|
// =========================================================================
|
|
@@ -516,6 +720,9 @@ module.exports = {
|
|
|
516
720
|
// OpenAI
|
|
517
721
|
shieldOpenAIClient,
|
|
518
722
|
|
|
723
|
+
// OpenAI Agents SDK (@openai/agents, April 2026)
|
|
724
|
+
shieldOpenAIAgent,
|
|
725
|
+
|
|
519
726
|
// Vercel AI
|
|
520
727
|
shieldVercelAI,
|
|
521
728
|
|