agentshield-sdk 7.2.0 → 7.2.1
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/README.md +5 -4
- package/package.json +4 -3
- package/src/circuit-breaker.js +321 -321
- package/src/detector-core.js +3 -3
- package/src/distributed.js +402 -359
- package/src/fuzzer.js +764 -764
- package/src/index.js +23 -7
- package/src/main.js +6 -2
- package/src/mcp-security-runtime.js +30 -5
- package/src/mcp-server.js +12 -8
- package/src/middleware.js +303 -208
- package/src/multi-agent.js +421 -404
- package/src/pii.js +401 -390
- package/src/stream-scanner.js +34 -4
- package/src/testing.js +505 -505
- package/src/utils.js +199 -83
- package/types/index.d.ts +374 -0
package/src/middleware.js
CHANGED
|
@@ -1,208 +1,303 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Agent Shield Middleware
|
|
5
|
-
*
|
|
6
|
-
* Plug-and-play middleware for common agent frameworks.
|
|
7
|
-
* Wraps agent input/output pipelines with automatic threat scanning.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { AgentShield } = require('./index');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* const
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* app
|
|
25
|
-
* app.use(
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* @param {
|
|
76
|
-
* @
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* @param {object}
|
|
140
|
-
* @
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
*
|
|
183
|
-
* @
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (typeof body
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (body.
|
|
199
|
-
if (body.
|
|
200
|
-
if (body.
|
|
201
|
-
if (body.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield Middleware
|
|
5
|
+
*
|
|
6
|
+
* Plug-and-play middleware for common agent frameworks.
|
|
7
|
+
* Wraps agent input/output pipelines with automatic threat scanning.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { AgentShield } = require('./index');
|
|
11
|
+
const { RateLimiter } = require('./circuit-breaker');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates an Express/Connect-style middleware that scans request bodies
|
|
15
|
+
* for AI-specific threats before they reach your agent endpoint.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} [config] - AgentShield configuration.
|
|
18
|
+
* @returns {Function} Express middleware function.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const express = require('express');
|
|
22
|
+
* const { expressMiddleware } = require('agent-shield/src/middleware');
|
|
23
|
+
*
|
|
24
|
+
* const app = express();
|
|
25
|
+
* app.use(express.json());
|
|
26
|
+
* app.use(expressMiddleware({ blockOnThreat: true, blockThreshold: 'high' }));
|
|
27
|
+
*
|
|
28
|
+
* app.post('/agent', (req, res) => {
|
|
29
|
+
* // req.agentShield contains scan results
|
|
30
|
+
* if (req.agentShield.blocked) {
|
|
31
|
+
* return res.status(400).json({ error: 'Input blocked for safety' });
|
|
32
|
+
* }
|
|
33
|
+
* // ... process the agent request
|
|
34
|
+
* });
|
|
35
|
+
*/
|
|
36
|
+
const expressMiddleware = (config = {}) => {
|
|
37
|
+
const shield = new AgentShield({ blockOnThreat: true, ...config });
|
|
38
|
+
|
|
39
|
+
return (req, res, next) => {
|
|
40
|
+
if (!req.body) {
|
|
41
|
+
req.agentShield = { status: 'safe', threats: [], blocked: false };
|
|
42
|
+
return next();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Extract text from common request body shapes
|
|
46
|
+
const text = extractTextFromBody(req.body);
|
|
47
|
+
|
|
48
|
+
if (!text) {
|
|
49
|
+
req.agentShield = { status: 'safe', threats: [], blocked: false };
|
|
50
|
+
return next();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = shield.scanInput(text, { source: 'http_request' });
|
|
54
|
+
req.agentShield = result;
|
|
55
|
+
|
|
56
|
+
if (result.blocked) {
|
|
57
|
+
return res.status(400).json({
|
|
58
|
+
error: 'Input blocked by Agent Shield',
|
|
59
|
+
status: result.status,
|
|
60
|
+
threats: result.threats.map(t => ({
|
|
61
|
+
severity: t.severity,
|
|
62
|
+
description: t.description
|
|
63
|
+
}))
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
next();
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates a wrapper function that scans input/output around any async function.
|
|
73
|
+
* Works with any agent framework — just wrap your agent's main function.
|
|
74
|
+
*
|
|
75
|
+
* @param {Function} agentFn - The agent function to wrap. Should accept (input) and return output.
|
|
76
|
+
* @param {object} [config] - AgentShield configuration.
|
|
77
|
+
* @returns {Function} Wrapped function with the same signature.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* const { wrapAgent } = require('agent-shield/src/middleware');
|
|
81
|
+
*
|
|
82
|
+
* async function myAgent(input) {
|
|
83
|
+
* const response = await callLLM(input);
|
|
84
|
+
* return response;
|
|
85
|
+
* }
|
|
86
|
+
*
|
|
87
|
+
* const protectedAgent = wrapAgent(myAgent, {
|
|
88
|
+
* blockOnThreat: true,
|
|
89
|
+
* logging: true
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* // Use it the same way
|
|
93
|
+
* const result = await protectedAgent('Hello, how are you?');
|
|
94
|
+
*/
|
|
95
|
+
const wrapAgent = (agentFn, config = {}) => {
|
|
96
|
+
const shield = new AgentShield({ blockOnThreat: true, ...config });
|
|
97
|
+
|
|
98
|
+
return async (input, ...rest) => {
|
|
99
|
+
// Scan input
|
|
100
|
+
const inputText = typeof input === 'string' ? input : JSON.stringify(input);
|
|
101
|
+
const inputResult = shield.scanInput(inputText, { source: 'agent_input' });
|
|
102
|
+
|
|
103
|
+
if (inputResult.blocked) {
|
|
104
|
+
return {
|
|
105
|
+
blocked: true,
|
|
106
|
+
reason: 'Input blocked by Agent Shield',
|
|
107
|
+
threats: inputResult.threats,
|
|
108
|
+
output: null
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Run the agent
|
|
113
|
+
const output = await agentFn(input, ...rest);
|
|
114
|
+
|
|
115
|
+
// Scan output
|
|
116
|
+
const outputText = typeof output === 'string' ? output : JSON.stringify(output);
|
|
117
|
+
const outputResult = shield.scanOutput(outputText, { source: 'agent_output' });
|
|
118
|
+
|
|
119
|
+
if (outputResult.blocked) {
|
|
120
|
+
return {
|
|
121
|
+
blocked: true,
|
|
122
|
+
reason: 'Output blocked by Agent Shield',
|
|
123
|
+
threats: outputResult.threats,
|
|
124
|
+
output: null
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
blocked: false,
|
|
130
|
+
threats: [...inputResult.threats, ...outputResult.threats],
|
|
131
|
+
output
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a tool-call interceptor that scans tool calls before execution.
|
|
138
|
+
*
|
|
139
|
+
* @param {object} tools - Map of tool name -> tool function.
|
|
140
|
+
* @param {object} [config] - AgentShield configuration.
|
|
141
|
+
* @returns {object} Map of tool name -> wrapped tool function.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* const { shieldTools } = require('agent-shield/src/middleware');
|
|
145
|
+
*
|
|
146
|
+
* const tools = {
|
|
147
|
+
* bash: async (args) => exec(args.command),
|
|
148
|
+
* readFile: async (args) => fs.readFile(args.path, 'utf-8'),
|
|
149
|
+
* };
|
|
150
|
+
*
|
|
151
|
+
* const protectedTools = shieldTools(tools, {
|
|
152
|
+
* blockOnThreat: true,
|
|
153
|
+
* logging: true
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* // Use protectedTools in your agent — dangerous calls get blocked
|
|
157
|
+
*/
|
|
158
|
+
const shieldTools = (tools, config = {}) => {
|
|
159
|
+
const shield = new AgentShield({ blockOnThreat: true, ...config });
|
|
160
|
+
const wrapped = {};
|
|
161
|
+
|
|
162
|
+
for (const [name, fn] of Object.entries(tools)) {
|
|
163
|
+
wrapped[name] = async (args, ...rest) => {
|
|
164
|
+
const result = shield.scanToolCall(name, args);
|
|
165
|
+
|
|
166
|
+
if (result.blocked) {
|
|
167
|
+
const error = new Error(
|
|
168
|
+
`[Agent Shield] Tool call "${name}" blocked: ${result.threats.map(t => t.description).join('; ')}`
|
|
169
|
+
);
|
|
170
|
+
error.agentShield = result;
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return fn(args, ...rest);
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return wrapped;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extracts scannable text from common request body formats.
|
|
183
|
+
* @param {object} body
|
|
184
|
+
* @returns {string|null}
|
|
185
|
+
*/
|
|
186
|
+
const extractTextFromBody = (body) => {
|
|
187
|
+
if (!body || (typeof body !== 'object' && typeof body !== 'string')) return null;
|
|
188
|
+
if (typeof body === 'string') return body;
|
|
189
|
+
|
|
190
|
+
// OpenAI-style messages array
|
|
191
|
+
if (body.messages && Array.isArray(body.messages)) {
|
|
192
|
+
return body.messages
|
|
193
|
+
.map(m => typeof m.content === 'string' ? m.content : JSON.stringify(m.content))
|
|
194
|
+
.join('\n');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Single message/prompt field
|
|
198
|
+
if (body.message) return typeof body.message === 'string' ? body.message : JSON.stringify(body.message);
|
|
199
|
+
if (body.prompt) return typeof body.prompt === 'string' ? body.prompt : JSON.stringify(body.prompt);
|
|
200
|
+
if (body.input) return typeof body.input === 'string' ? body.input : JSON.stringify(body.input);
|
|
201
|
+
if (body.query) return typeof body.query === 'string' ? body.query : JSON.stringify(body.query);
|
|
202
|
+
if (body.text) return typeof body.text === 'string' ? body.text : JSON.stringify(body.text);
|
|
203
|
+
|
|
204
|
+
// Fallback: stringify the whole body
|
|
205
|
+
const str = JSON.stringify(body);
|
|
206
|
+
return str.length > 20 ? str : null;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Creates rate-limiting middleware that returns 429 responses when limits are exceeded.
|
|
211
|
+
* Includes backpressure headers (X-RateLimit-Remaining, X-RateLimit-Limit, Retry-After).
|
|
212
|
+
*
|
|
213
|
+
* @param {object} [options]
|
|
214
|
+
* @param {number} [options.maxRequests=100] - Max requests per window.
|
|
215
|
+
* @param {number} [options.windowMs=60000] - Window size in ms (default: 1 minute).
|
|
216
|
+
* @param {number} [options.maxThreatsPerWindow=10] - Max threats before anomaly flag.
|
|
217
|
+
* @param {Function} [options.onLimit] - Callback when limit is hit.
|
|
218
|
+
* @param {boolean} [options.includeBackpressureHeaders=true] - Add rate limit headers to all responses.
|
|
219
|
+
* @returns {Function} Express middleware function.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* const { rateLimitMiddleware } = require('agent-shield/src/middleware');
|
|
223
|
+
* app.use(rateLimitMiddleware({ maxRequests: 50, windowMs: 60000 }));
|
|
224
|
+
*/
|
|
225
|
+
const rateLimitMiddleware = (options = {}) => {
|
|
226
|
+
const includeHeaders = options.includeBackpressureHeaders !== false;
|
|
227
|
+
const limiter = new RateLimiter({
|
|
228
|
+
maxRequests: options.maxRequests || 100,
|
|
229
|
+
windowMs: options.windowMs || 60000,
|
|
230
|
+
maxThreatsPerWindow: options.maxThreatsPerWindow || 10,
|
|
231
|
+
onLimit: options.onLimit || null,
|
|
232
|
+
onAnomaly: options.onAnomaly || null
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return (req, res, next) => {
|
|
236
|
+
const check = limiter.recordRequest();
|
|
237
|
+
|
|
238
|
+
// Always set backpressure headers so callers can see remaining capacity
|
|
239
|
+
if (includeHeaders) {
|
|
240
|
+
res.setHeader('X-RateLimit-Limit', limiter.maxRequests);
|
|
241
|
+
res.setHeader('X-RateLimit-Remaining', Math.max(0, check.remaining));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!check.allowed) {
|
|
245
|
+
const retryAfterSec = Math.ceil(limiter.windowMs / 1000);
|
|
246
|
+
res.setHeader('Retry-After', retryAfterSec);
|
|
247
|
+
return res.status(429).json({
|
|
248
|
+
error: 'Too Many Requests',
|
|
249
|
+
message: check.reason,
|
|
250
|
+
retryAfter: retryAfterSec
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Expose limiter on request for downstream threat recording
|
|
255
|
+
req.agentShieldRateLimiter = limiter;
|
|
256
|
+
next();
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Creates a combined Express middleware that applies rate limiting, threat scanning,
|
|
262
|
+
* and backpressure headers in a single middleware call.
|
|
263
|
+
*
|
|
264
|
+
* @param {object} [config] - AgentShield + rate limiter configuration.
|
|
265
|
+
* @param {number} [config.maxRequests=100] - Rate limit: max requests per window.
|
|
266
|
+
* @param {number} [config.windowMs=60000] - Rate limit: window size in ms.
|
|
267
|
+
* @param {boolean} [config.includeBackpressureHeaders=true] - Add rate limit headers.
|
|
268
|
+
* @returns {Function} Express middleware function.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* app.use(shieldMiddleware({ blockOnThreat: true, maxRequests: 50 }));
|
|
272
|
+
*/
|
|
273
|
+
const shieldMiddleware = (config = {}) => {
|
|
274
|
+
const rateLimiter = rateLimitMiddleware({
|
|
275
|
+
maxRequests: config.maxRequests,
|
|
276
|
+
windowMs: config.windowMs,
|
|
277
|
+
maxThreatsPerWindow: config.maxThreatsPerWindow,
|
|
278
|
+
includeBackpressureHeaders: config.includeBackpressureHeaders,
|
|
279
|
+
onLimit: config.onLimit,
|
|
280
|
+
onAnomaly: config.onAnomaly
|
|
281
|
+
});
|
|
282
|
+
const scanner = expressMiddleware(config);
|
|
283
|
+
|
|
284
|
+
return (req, res, next) => {
|
|
285
|
+
// Rate limit first
|
|
286
|
+
rateLimiter(req, res, (err) => {
|
|
287
|
+
if (err) return next(err);
|
|
288
|
+
// Then scan
|
|
289
|
+
scanner(req, res, (scanErr) => {
|
|
290
|
+
if (scanErr) return next(scanErr);
|
|
291
|
+
// Record threats in rate limiter for anomaly detection
|
|
292
|
+
if (req.agentShield && req.agentShield.threats && req.agentShield.threats.length > 0) {
|
|
293
|
+
if (req.agentShieldRateLimiter) {
|
|
294
|
+
req.agentShieldRateLimiter.recordThreat(req.agentShield.threats.length);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
next();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
module.exports = { expressMiddleware, wrapAgent, shieldTools, extractTextFromBody, rateLimitMiddleware, shieldMiddleware };
|