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.
- package/CHANGELOG.md +191 -0
- package/LICENSE +21 -0
- package/README.md +975 -0
- package/bin/agent-shield.js +680 -0
- package/package.json +118 -0
- package/src/adaptive.js +330 -0
- package/src/agent-protocol.js +998 -0
- package/src/alert-tuning.js +480 -0
- package/src/allowlist.js +603 -0
- package/src/audit-immutable.js +914 -0
- package/src/audit-streaming.js +469 -0
- package/src/badges.js +196 -0
- package/src/behavior-profiling.js +289 -0
- package/src/benchmark-harness.js +804 -0
- package/src/canary.js +271 -0
- package/src/certification.js +563 -0
- package/src/circuit-breaker.js +321 -0
- package/src/compliance.js +617 -0
- package/src/confidence-tuning.js +324 -0
- package/src/confused-deputy.js +624 -0
- package/src/context-scoring.js +360 -0
- package/src/conversation.js +494 -0
- package/src/cost-optimizer.js +1024 -0
- package/src/ctf.js +462 -0
- package/src/detector-core.js +1999 -0
- package/src/distributed.js +359 -0
- package/src/document-scanner.js +795 -0
- package/src/embedding.js +307 -0
- package/src/encoding.js +429 -0
- package/src/enterprise.js +405 -0
- package/src/errors.js +100 -0
- package/src/eu-ai-act.js +523 -0
- package/src/fuzzer.js +764 -0
- package/src/honeypot.js +328 -0
- package/src/i18n-patterns.js +523 -0
- package/src/index.js +430 -0
- package/src/integrations.js +528 -0
- package/src/llm-redteam.js +670 -0
- package/src/main.js +741 -0
- package/src/main.mjs +38 -0
- package/src/mcp-bridge.js +542 -0
- package/src/mcp-certification.js +846 -0
- package/src/mcp-sdk-integration.js +355 -0
- package/src/mcp-security-runtime.js +741 -0
- package/src/mcp-server.js +740 -0
- package/src/middleware.js +208 -0
- package/src/model-finetuning.js +884 -0
- package/src/model-fingerprint.js +1042 -0
- package/src/multi-agent-trust.js +453 -0
- package/src/multi-agent.js +404 -0
- package/src/multimodal.js +296 -0
- package/src/nist-mapping.js +505 -0
- package/src/observability.js +330 -0
- package/src/openclaw.js +450 -0
- package/src/otel.js +544 -0
- package/src/owasp-2025.js +483 -0
- package/src/pii.js +390 -0
- package/src/plugin-marketplace.js +628 -0
- package/src/plugin-system.js +349 -0
- package/src/policy-dsl.js +775 -0
- package/src/policy-extended.js +635 -0
- package/src/policy.js +443 -0
- package/src/presets.js +409 -0
- package/src/production.js +557 -0
- package/src/prompt-leakage.js +321 -0
- package/src/rag-vulnerability.js +579 -0
- package/src/redteam.js +475 -0
- package/src/response-handler.js +429 -0
- package/src/scanners.js +357 -0
- package/src/self-healing.js +363 -0
- package/src/semantic.js +339 -0
- package/src/shield-score.js +250 -0
- package/src/sso-saml.js +897 -0
- package/src/stream-scanner.js +806 -0
- package/src/testing.js +505 -0
- package/src/threat-encyclopedia.js +629 -0
- package/src/threat-intel-network.js +1017 -0
- package/src/token-analysis.js +467 -0
- package/src/tool-guard.js +412 -0
- package/src/tool-output-validator.js +354 -0
- package/src/utils.js +83 -0
- package/src/watermark.js +235 -0
- package/src/worker-scanner.js +601 -0
- package/types/index.d.ts +2088 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Circuit Breaker (#31), Shadow Mode (#33), and Rate Limiting (#5)
|
|
5
|
+
*
|
|
6
|
+
* - Circuit Breaker: Auto-shuts down an agent after too many threats in a time window.
|
|
7
|
+
* - Shadow Mode: Detection-only mode — logs everything, blocks nothing.
|
|
8
|
+
* - Rate Limiting: Tracks input patterns and flags anomalous spikes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Circuit breaker states.
|
|
13
|
+
*/
|
|
14
|
+
const STATE = {
|
|
15
|
+
CLOSED: 'closed', // Normal operation
|
|
16
|
+
OPEN: 'open', // Tripped — all requests blocked
|
|
17
|
+
HALF_OPEN: 'half_open' // Testing if safe to resume
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class CircuitBreaker {
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} [options]
|
|
23
|
+
* @param {number} [options.threshold=5] - Number of threats to trip the breaker.
|
|
24
|
+
* @param {number} [options.windowMs=60000] - Time window in ms (default: 1 minute).
|
|
25
|
+
* @param {number} [options.cooldownMs=300000] - Cooldown before half-open (default: 5 minutes).
|
|
26
|
+
* @param {Function} [options.onTrip] - Callback when breaker trips.
|
|
27
|
+
* @param {Function} [options.onReset] - Callback when breaker resets.
|
|
28
|
+
*/
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.threshold = options.threshold || 5;
|
|
31
|
+
this.windowMs = options.windowMs || 60000;
|
|
32
|
+
this.cooldownMs = options.cooldownMs || 300000;
|
|
33
|
+
this.onTrip = options.onTrip || null;
|
|
34
|
+
this.onReset = options.onReset || null;
|
|
35
|
+
|
|
36
|
+
this.state = STATE.CLOSED;
|
|
37
|
+
this.threatTimestamps = [];
|
|
38
|
+
this.trippedAt = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Records a threat event. Trips the breaker if threshold is exceeded.
|
|
43
|
+
* @param {number} [count=1] - Number of threats to record.
|
|
44
|
+
*/
|
|
45
|
+
recordThreat(count = 1) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
for (let i = 0; i < count; i++) {
|
|
48
|
+
this.threatTimestamps.push(now);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Prune old timestamps outside the window
|
|
52
|
+
const cutoff = now - this.windowMs;
|
|
53
|
+
this.threatTimestamps = this.threatTimestamps.filter(t => t > cutoff);
|
|
54
|
+
|
|
55
|
+
if (this.state === STATE.CLOSED && this.threatTimestamps.length >= this.threshold) {
|
|
56
|
+
this._trip();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if the breaker allows a request through.
|
|
62
|
+
* @returns {object} { allowed: boolean, state: string, reason?: string }
|
|
63
|
+
*/
|
|
64
|
+
check() {
|
|
65
|
+
if (this.state === STATE.CLOSED) {
|
|
66
|
+
return { allowed: true, state: this.state };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.state === STATE.OPEN) {
|
|
70
|
+
const elapsed = Date.now() - this.trippedAt;
|
|
71
|
+
if (elapsed >= this.cooldownMs) {
|
|
72
|
+
this.state = STATE.HALF_OPEN;
|
|
73
|
+
return { allowed: true, state: this.state, reason: 'Testing after cooldown' };
|
|
74
|
+
}
|
|
75
|
+
const remainingMs = this.cooldownMs - elapsed;
|
|
76
|
+
return {
|
|
77
|
+
allowed: false,
|
|
78
|
+
state: this.state,
|
|
79
|
+
reason: `Circuit breaker tripped. Resumes in ${Math.ceil(remainingMs / 1000)}s.`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// HALF_OPEN: allow one request to test
|
|
84
|
+
return { allowed: true, state: this.state, reason: 'Half-open test request' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reports the result of a half-open test request.
|
|
89
|
+
* @param {boolean} safe - Whether the test request was safe.
|
|
90
|
+
*/
|
|
91
|
+
reportTestResult(safe) {
|
|
92
|
+
if (this.state !== STATE.HALF_OPEN) return;
|
|
93
|
+
|
|
94
|
+
if (safe) {
|
|
95
|
+
this._reset();
|
|
96
|
+
} else {
|
|
97
|
+
this._trip();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** @private */
|
|
102
|
+
_trip() {
|
|
103
|
+
this.state = STATE.OPEN;
|
|
104
|
+
this.trippedAt = Date.now();
|
|
105
|
+
if (this.onTrip) {
|
|
106
|
+
try {
|
|
107
|
+
this.onTrip({
|
|
108
|
+
state: STATE.OPEN,
|
|
109
|
+
threatCount: this.threatTimestamps.length,
|
|
110
|
+
timestamp: this.trippedAt
|
|
111
|
+
});
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error('[Agent Shield] onTrip callback error:', err.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @private */
|
|
119
|
+
_reset() {
|
|
120
|
+
this.state = STATE.CLOSED;
|
|
121
|
+
this.threatTimestamps = [];
|
|
122
|
+
this.trippedAt = null;
|
|
123
|
+
if (this.onReset) {
|
|
124
|
+
try {
|
|
125
|
+
this.onReset({ state: STATE.CLOSED, timestamp: Date.now() });
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error('[Agent Shield] onReset callback error:', err.message);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Manually reset the breaker.
|
|
134
|
+
*/
|
|
135
|
+
reset() {
|
|
136
|
+
this._reset();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns current breaker status.
|
|
141
|
+
* @returns {object}
|
|
142
|
+
*/
|
|
143
|
+
getStatus() {
|
|
144
|
+
return {
|
|
145
|
+
state: this.state,
|
|
146
|
+
recentThreats: this.threatTimestamps.length,
|
|
147
|
+
threshold: this.threshold,
|
|
148
|
+
trippedAt: this.trippedAt
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// =========================================================================
|
|
154
|
+
// SHADOW MODE
|
|
155
|
+
// =========================================================================
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Wraps an AgentShield instance in shadow mode.
|
|
159
|
+
* Logs all detections but never blocks. Perfect for evaluation.
|
|
160
|
+
*
|
|
161
|
+
* @param {object} shield - An AgentShield instance.
|
|
162
|
+
* @param {object} [options]
|
|
163
|
+
* @param {Function} [options.logger] - Custom log function. Defaults to console.log.
|
|
164
|
+
* @returns {object} - A shadow-mode wrapped shield with the same API.
|
|
165
|
+
*/
|
|
166
|
+
const shadowMode = (shield, options = {}) => {
|
|
167
|
+
const logger = options.logger || console.log;
|
|
168
|
+
const log = [];
|
|
169
|
+
|
|
170
|
+
const wrap = (methodName, original) => {
|
|
171
|
+
return function (...args) {
|
|
172
|
+
const result = original.apply(shield, args);
|
|
173
|
+
|
|
174
|
+
// If it's a promise (async methods like from middleware), handle accordingly
|
|
175
|
+
if (result && typeof result.then === 'function') {
|
|
176
|
+
return result.then(res => {
|
|
177
|
+
const entry = { method: methodName, result: res, timestamp: Date.now() };
|
|
178
|
+
log.push(entry);
|
|
179
|
+
if (log.length > 1000) log.shift();
|
|
180
|
+
if (res.threats && res.threats.length > 0) {
|
|
181
|
+
try { logger(`[Agent Shield Shadow] ${methodName}: ${res.threats.length} threat(s) detected (not blocked)`, res.threats.map(t => t.description)); } catch (e) { /* logger error */ }
|
|
182
|
+
}
|
|
183
|
+
// Never block in shadow mode
|
|
184
|
+
if ('blocked' in res) res.blocked = false;
|
|
185
|
+
return res;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const entry = { method: methodName, result, timestamp: Date.now() };
|
|
190
|
+
log.push(entry);
|
|
191
|
+
if (log.length > 1000) log.shift();
|
|
192
|
+
|
|
193
|
+
if (result.threats && result.threats.length > 0) {
|
|
194
|
+
try { logger(`[Agent Shield Shadow] ${methodName}: ${result.threats.length} threat(s) detected (not blocked)`, result.threats.map(t => t.description)); } catch (e) { /* logger error */ }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Never block in shadow mode
|
|
198
|
+
if ('blocked' in result) result.blocked = false;
|
|
199
|
+
return result;
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
scan: wrap('scan', shield.scan),
|
|
205
|
+
scanInput: wrap('scanInput', shield.scanInput),
|
|
206
|
+
scanOutput: wrap('scanOutput', shield.scanOutput),
|
|
207
|
+
scanToolCall: wrap('scanToolCall', shield.scanToolCall),
|
|
208
|
+
scanBatch: wrap('scanBatch', shield.scanBatch),
|
|
209
|
+
getStats: () => shield.getStats(),
|
|
210
|
+
getLog: () => [...log],
|
|
211
|
+
isShadowMode: true
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// =========================================================================
|
|
216
|
+
// RATE LIMITER
|
|
217
|
+
// =========================================================================
|
|
218
|
+
|
|
219
|
+
class RateLimiter {
|
|
220
|
+
/**
|
|
221
|
+
* @param {object} [options]
|
|
222
|
+
* @param {number} [options.maxRequests=100] - Max requests per window.
|
|
223
|
+
* @param {number} [options.windowMs=60000] - Window size in ms.
|
|
224
|
+
* @param {number} [options.maxThreatsPerWindow=10] - Max threats before flagging anomaly.
|
|
225
|
+
* @param {Function} [options.onLimit] - Callback when rate limit hit.
|
|
226
|
+
* @param {Function} [options.onAnomaly] - Callback when anomaly detected.
|
|
227
|
+
*/
|
|
228
|
+
constructor(options = {}) {
|
|
229
|
+
this.maxRequests = options.maxRequests || 100;
|
|
230
|
+
this.windowMs = options.windowMs || 60000;
|
|
231
|
+
this.maxThreatsPerWindow = options.maxThreatsPerWindow || 10;
|
|
232
|
+
this.onLimit = options.onLimit || null;
|
|
233
|
+
this.onAnomaly = options.onAnomaly || null;
|
|
234
|
+
|
|
235
|
+
this.requestTimestamps = [];
|
|
236
|
+
this.threatTimestamps = [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Records a request. Returns whether it's allowed.
|
|
241
|
+
* @returns {object} { allowed: boolean, remaining: number, reason?: string }
|
|
242
|
+
*/
|
|
243
|
+
recordRequest() {
|
|
244
|
+
const now = Date.now();
|
|
245
|
+
const cutoff = now - this.windowMs;
|
|
246
|
+
|
|
247
|
+
this.requestTimestamps = this.requestTimestamps.filter(t => t > cutoff);
|
|
248
|
+
this.requestTimestamps.push(now);
|
|
249
|
+
|
|
250
|
+
if (this.requestTimestamps.length > this.maxRequests) {
|
|
251
|
+
if (this.onLimit) {
|
|
252
|
+
try {
|
|
253
|
+
this.onLimit({ count: this.requestTimestamps.length, windowMs: this.windowMs });
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('[Agent Shield] onLimit callback error:', err.message);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
allowed: false,
|
|
260
|
+
remaining: 0,
|
|
261
|
+
reason: `Rate limit exceeded: ${this.requestTimestamps.length}/${this.maxRequests} requests in ${this.windowMs / 1000}s`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
allowed: true,
|
|
267
|
+
remaining: this.maxRequests - this.requestTimestamps.length
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Records threat detections. Flags anomalies if spike detected.
|
|
273
|
+
* @param {number} [count=1] - Number of threats.
|
|
274
|
+
* @returns {object} { anomaly: boolean, threatCount: number }
|
|
275
|
+
*/
|
|
276
|
+
recordThreat(count = 1) {
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
const cutoff = now - this.windowMs;
|
|
279
|
+
|
|
280
|
+
for (let i = 0; i < count; i++) {
|
|
281
|
+
this.threatTimestamps.push(now);
|
|
282
|
+
}
|
|
283
|
+
this.threatTimestamps = this.threatTimestamps.filter(t => t > cutoff);
|
|
284
|
+
|
|
285
|
+
const isAnomaly = this.threatTimestamps.length >= this.maxThreatsPerWindow;
|
|
286
|
+
if (isAnomaly && this.onAnomaly) {
|
|
287
|
+
try {
|
|
288
|
+
this.onAnomaly({ threatCount: this.threatTimestamps.length, windowMs: this.windowMs });
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error('[Agent Shield] onAnomaly callback error:', err.message);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
anomaly: isAnomaly,
|
|
296
|
+
threatCount: this.threatTimestamps.length
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Returns current rate limiter status.
|
|
302
|
+
* @returns {object}
|
|
303
|
+
*/
|
|
304
|
+
getStatus() {
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
const cutoff = now - this.windowMs;
|
|
307
|
+
return {
|
|
308
|
+
requests: this.requestTimestamps.filter(t => t > cutoff).length,
|
|
309
|
+
maxRequests: this.maxRequests,
|
|
310
|
+
threats: this.threatTimestamps.filter(t => t > cutoff).length,
|
|
311
|
+
maxThreatsPerWindow: this.maxThreatsPerWindow
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
reset() {
|
|
316
|
+
this.requestTimestamps = [];
|
|
317
|
+
this.threatTimestamps = [];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
module.exports = { CircuitBreaker, shadowMode, RateLimiter, STATE };
|