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,412 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Tool Sequence Analysis (#2), Permission Boundaries (#16),
5
+ * Allowlists (#3), and Input Quarantine (#32)
6
+ *
7
+ * - Tool Sequence Analysis: Track tool call sequences and flag suspicious chains.
8
+ * - Permission Boundaries: Define what each tool is allowed to do.
9
+ * - Allowlists: Define expected tool/source allowlists.
10
+ * - Input Quarantine: Hold suspicious inputs for human review.
11
+ */
12
+
13
+ // =========================================================================
14
+ // TOOL SEQUENCE ANALYZER
15
+ // =========================================================================
16
+
17
+ /**
18
+ * Known suspicious tool call sequences.
19
+ * Each sequence describes a pattern of tool calls that, in order, indicate an attack.
20
+ */
21
+ const SUSPICIOUS_SEQUENCES = [
22
+ {
23
+ name: 'credential_exfiltration',
24
+ description: 'Reading credentials then making an HTTP request — likely data theft.',
25
+ severity: 'critical',
26
+ pattern: [
27
+ { tool: /read|cat|file|open/i, args: /\.env|credentials|secret|password|token|key/i },
28
+ { tool: /http|fetch|curl|wget|request|post|send/i }
29
+ ]
30
+ },
31
+ {
32
+ name: 'reconnaissance_then_delete',
33
+ description: 'Listing files then deleting — likely destructive attack.',
34
+ severity: 'critical',
35
+ pattern: [
36
+ { tool: /list|ls|find|glob|search|read/i },
37
+ { tool: /delete|remove|rm|unlink|drop|truncate/i }
38
+ ]
39
+ },
40
+ {
41
+ name: 'read_then_write_config',
42
+ description: 'Reading config then modifying it — possible self-modification attack.',
43
+ severity: 'high',
44
+ pattern: [
45
+ { tool: /read|cat|file|open/i, args: /config|settings|\.env|system/i },
46
+ { tool: /write|edit|modify|update|set/i, args: /config|settings|\.env|system/i }
47
+ ]
48
+ },
49
+ {
50
+ name: 'database_dump',
51
+ description: 'Running a broad database query then sending data externally.',
52
+ severity: 'critical',
53
+ pattern: [
54
+ { tool: /sql|query|database|db/i, args: /SELECT\s+\*|dump|export|all/i },
55
+ { tool: /http|fetch|curl|wget|request|send|write/i }
56
+ ]
57
+ },
58
+ {
59
+ name: 'privilege_escalation',
60
+ description: 'Modifying permissions or user roles then executing commands.',
61
+ severity: 'critical',
62
+ pattern: [
63
+ { tool: /chmod|chown|permission|role|grant|sudo/i },
64
+ { tool: /exec|run|shell|bash|system/i }
65
+ ]
66
+ }
67
+ ];
68
+
69
+ class ToolSequenceAnalyzer {
70
+ /**
71
+ * @param {object} [options]
72
+ * @param {number} [options.windowSize=10] - Number of recent tool calls to track.
73
+ * @param {number} [options.windowMs=300000] - Time window in ms (default: 5 minutes).
74
+ * @param {Array<object>} [options.customSequences=[]] - Additional suspicious sequences.
75
+ * @param {Function} [options.onSuspicious] - Callback when suspicious sequence detected.
76
+ */
77
+ constructor(options = {}) {
78
+ this.windowSize = options.windowSize || 10;
79
+ this.windowMs = options.windowMs || 300000;
80
+ this.customSequences = options.customSequences || [];
81
+ this.onSuspicious = options.onSuspicious || null;
82
+ this.history = [];
83
+ }
84
+
85
+ /**
86
+ * Records a tool call and checks for suspicious sequences.
87
+ *
88
+ * @param {string} toolName - The tool that was called.
89
+ * @param {object} [args={}] - The tool's arguments.
90
+ * @returns {object} { suspicious: boolean, matches: Array }
91
+ */
92
+ record(toolName, args = {}) {
93
+ const now = Date.now();
94
+ const argsStr = typeof args === 'string' ? args : JSON.stringify(args);
95
+
96
+ this.history.push({ tool: toolName, args: argsStr, timestamp: now });
97
+
98
+ // Prune old entries
99
+ const cutoff = now - this.windowMs;
100
+ this.history = this.history.filter(h => h.timestamp > cutoff);
101
+ if (this.history.length > this.windowSize) {
102
+ this.history = this.history.slice(-this.windowSize);
103
+ }
104
+
105
+ // Check against known sequences
106
+ const allSequences = [...SUSPICIOUS_SEQUENCES, ...this.customSequences];
107
+ const matches = [];
108
+
109
+ for (const seq of allSequences) {
110
+ if (this._matchesSequence(seq.pattern)) {
111
+ matches.push({
112
+ name: seq.name,
113
+ description: seq.description,
114
+ severity: seq.severity,
115
+ timestamp: now
116
+ });
117
+ }
118
+ }
119
+
120
+ if (matches.length > 0 && this.onSuspicious) {
121
+ this.onSuspicious({ matches, history: this.getHistory() });
122
+ }
123
+
124
+ return { suspicious: matches.length > 0, matches };
125
+ }
126
+
127
+ /**
128
+ * Checks if the recent history matches a sequence pattern.
129
+ * @private
130
+ * @param {Array} pattern
131
+ * @returns {boolean}
132
+ */
133
+ _matchesSequence(pattern) {
134
+ if (this.history.length < pattern.length) return false;
135
+
136
+ // Sliding window: check if any subsequence in history matches the pattern
137
+ for (let start = 0; start <= this.history.length - pattern.length; start++) {
138
+ let patternIdx = 0;
139
+ for (let i = start; i < this.history.length && patternIdx < pattern.length; i++) {
140
+ const step = pattern[patternIdx];
141
+ const entry = this.history[i];
142
+
143
+ const toolMatch = step.tool.test(entry.tool);
144
+ const argsMatch = !step.args || step.args.test(entry.args);
145
+
146
+ if (toolMatch && argsMatch) {
147
+ patternIdx++;
148
+ }
149
+ }
150
+ if (patternIdx === pattern.length) return true;
151
+ }
152
+ return false;
153
+ }
154
+
155
+ /**
156
+ * Returns recent tool call history.
157
+ * @returns {Array}
158
+ */
159
+ getHistory() {
160
+ return this.history.map(h => ({ tool: h.tool, args: h.args, timestamp: h.timestamp }));
161
+ }
162
+
163
+ reset() {
164
+ this.history = [];
165
+ }
166
+ }
167
+
168
+ // =========================================================================
169
+ // PERMISSION BOUNDARIES
170
+ // =========================================================================
171
+
172
+ class PermissionBoundary {
173
+ /**
174
+ * @param {object} [options]
175
+ * @param {object} [options.tools={}] - Per-tool permission rules.
176
+ * @param {Array<string>} [options.allowedTools] - Whitelist of allowed tool names.
177
+ * @param {Array<string>} [options.blockedTools] - Blacklist of blocked tool names.
178
+ * @param {Function} [options.onDenied] - Callback when permission denied.
179
+ */
180
+ constructor(options = {}) {
181
+ this.tools = options.tools || {};
182
+ this.allowedTools = options.allowedTools || null;
183
+ this.blockedTools = options.blockedTools || [];
184
+ this.onDenied = options.onDenied || null;
185
+ }
186
+
187
+ /**
188
+ * Defines permissions for a specific tool.
189
+ *
190
+ * @param {string} toolName
191
+ * @param {object} permissions
192
+ * @param {Array<string|RegExp>} [permissions.allowArgs] - Allowed argument patterns.
193
+ * @param {Array<string|RegExp>} [permissions.blockArgs] - Blocked argument patterns.
194
+ * @param {Array<string>} [permissions.allowPaths] - Allowed file path prefixes.
195
+ * @param {Array<string>} [permissions.blockPaths] - Blocked file path prefixes.
196
+ * @param {number} [permissions.maxCallsPerMinute] - Rate limit per tool.
197
+ * @returns {PermissionBoundary} this (for chaining)
198
+ */
199
+ defineTool(toolName, permissions) {
200
+ this.tools[toolName] = {
201
+ allowArgs: (permissions.allowArgs || []).map(p => typeof p === 'string' ? new RegExp(p, 'i') : p),
202
+ blockArgs: (permissions.blockArgs || []).map(p => typeof p === 'string' ? new RegExp(p, 'i') : p),
203
+ allowPaths: permissions.allowPaths || [],
204
+ blockPaths: permissions.blockPaths || [],
205
+ maxCallsPerMinute: permissions.maxCallsPerMinute || Infinity,
206
+ recentCalls: []
207
+ };
208
+ return this;
209
+ }
210
+
211
+ /**
212
+ * Checks if a tool call is permitted.
213
+ *
214
+ * @param {string} toolName
215
+ * @param {object} [args={}]
216
+ * @returns {object} { allowed: boolean, reason?: string }
217
+ */
218
+ check(toolName, args = {}) {
219
+ // Check tool-level allowlist
220
+ if (this.allowedTools && !this.allowedTools.includes(toolName)) {
221
+ const result = { allowed: false, reason: `Tool "${toolName}" is not in the allowed tools list.` };
222
+ if (this.onDenied) this.onDenied(result);
223
+ return result;
224
+ }
225
+
226
+ // Check tool-level blocklist
227
+ if (this.blockedTools.includes(toolName)) {
228
+ const result = { allowed: false, reason: `Tool "${toolName}" is blocked.` };
229
+ if (this.onDenied) this.onDenied(result);
230
+ return result;
231
+ }
232
+
233
+ // Check per-tool permissions
234
+ const perms = this.tools[toolName];
235
+ if (!perms) return { allowed: true };
236
+
237
+ const argsStr = typeof args === 'string' ? args : JSON.stringify(args);
238
+
239
+ // Check blocked args
240
+ for (const pattern of perms.blockArgs) {
241
+ if (pattern.test(argsStr)) {
242
+ const result = { allowed: false, reason: `Tool "${toolName}" argument matches blocked pattern: ${pattern}` };
243
+ if (this.onDenied) this.onDenied(result);
244
+ return result;
245
+ }
246
+ }
247
+
248
+ // Check allowed args (if specified, args must match at least one)
249
+ if (perms.allowArgs.length > 0) {
250
+ const hasMatch = perms.allowArgs.some(p => p.test(argsStr));
251
+ if (!hasMatch) {
252
+ const result = { allowed: false, reason: `Tool "${toolName}" argument doesn't match any allowed pattern.` };
253
+ if (this.onDenied) this.onDenied(result);
254
+ return result;
255
+ }
256
+ }
257
+
258
+ // Check file paths
259
+ const paths = this._extractPaths(args);
260
+ for (const path of paths) {
261
+ for (const blocked of perms.blockPaths) {
262
+ if (path.startsWith(blocked)) {
263
+ const result = { allowed: false, reason: `Tool "${toolName}" cannot access path "${path}" (blocked: ${blocked})` };
264
+ if (this.onDenied) this.onDenied(result);
265
+ return result;
266
+ }
267
+ }
268
+ if (perms.allowPaths.length > 0) {
269
+ const isAllowed = perms.allowPaths.some(ap => path.startsWith(ap));
270
+ if (!isAllowed) {
271
+ const result = { allowed: false, reason: `Tool "${toolName}" cannot access path "${path}" (not in allowed paths)` };
272
+ if (this.onDenied) this.onDenied(result);
273
+ return result;
274
+ }
275
+ }
276
+ }
277
+
278
+ // Rate limit
279
+ if (perms.maxCallsPerMinute < Infinity) {
280
+ const now = Date.now();
281
+ perms.recentCalls = perms.recentCalls.filter(t => t > now - 60000);
282
+ perms.recentCalls.push(now);
283
+ if (perms.recentCalls.length > perms.maxCallsPerMinute) {
284
+ const result = { allowed: false, reason: `Tool "${toolName}" rate limit exceeded (${perms.maxCallsPerMinute}/min).` };
285
+ if (this.onDenied) this.onDenied(result);
286
+ return result;
287
+ }
288
+ }
289
+
290
+ return { allowed: true };
291
+ }
292
+
293
+ /** @private */
294
+ _extractPaths(args) {
295
+ const paths = [];
296
+ const pathKeys = ['path', 'file', 'file_path', 'filepath', 'target', 'destination', 'src', 'dest'];
297
+ const extract = (obj, depth) => {
298
+ if (!obj || typeof obj !== 'object' || depth > 5) return;
299
+ for (const [key, value] of Object.entries(obj)) {
300
+ if (typeof value === 'string' && (pathKeys.includes(key.toLowerCase()) || value.startsWith('/') || value.startsWith('./'))) {
301
+ paths.push(value);
302
+ } else if (typeof value === 'object') {
303
+ extract(value, depth + 1);
304
+ }
305
+ }
306
+ };
307
+ extract(args, 0);
308
+ return paths;
309
+ }
310
+ }
311
+
312
+ // =========================================================================
313
+ // INPUT QUARANTINE
314
+ // =========================================================================
315
+
316
+ class InputQuarantine {
317
+ /**
318
+ * @param {object} [options]
319
+ * @param {Function} [options.onQuarantine] - Callback when input is quarantined.
320
+ * @param {number} [options.maxQueueSize=100] - Maximum quarantine queue size.
321
+ */
322
+ constructor(options = {}) {
323
+ this.onQuarantine = options.onQuarantine || null;
324
+ this.maxQueueSize = options.maxQueueSize || 100;
325
+ this.queue = [];
326
+ }
327
+
328
+ /**
329
+ * Quarantines an input for human review.
330
+ *
331
+ * @param {string} text - The quarantined text.
332
+ * @param {object} scanResult - The scan result that triggered quarantine.
333
+ * @param {string} [source='unknown'] - Source of the input.
334
+ * @returns {object} { id, text, scanResult, source, timestamp }
335
+ */
336
+ add(text, scanResult, source = 'unknown') {
337
+ const entry = {
338
+ id: `q_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
339
+ text,
340
+ scanResult,
341
+ source,
342
+ status: 'pending',
343
+ timestamp: Date.now()
344
+ };
345
+
346
+ this.queue.push(entry);
347
+ if (this.queue.length > this.maxQueueSize) {
348
+ this.queue.shift();
349
+ }
350
+
351
+ if (this.onQuarantine) {
352
+ this.onQuarantine(entry);
353
+ }
354
+
355
+ return entry;
356
+ }
357
+
358
+ /**
359
+ * Approves a quarantined input.
360
+ * @param {string} id
361
+ * @returns {object|null} The approved entry, or null if not found.
362
+ */
363
+ approve(id) {
364
+ const entry = this.queue.find(e => e.id === id);
365
+ if (entry) {
366
+ entry.status = 'approved';
367
+ entry.reviewedAt = Date.now();
368
+ }
369
+ return entry || null;
370
+ }
371
+
372
+ /**
373
+ * Rejects a quarantined input.
374
+ * @param {string} id
375
+ * @returns {object|null}
376
+ */
377
+ reject(id) {
378
+ const entry = this.queue.find(e => e.id === id);
379
+ if (entry) {
380
+ entry.status = 'rejected';
381
+ entry.reviewedAt = Date.now();
382
+ }
383
+ return entry || null;
384
+ }
385
+
386
+ /**
387
+ * Returns all pending quarantined inputs.
388
+ * @returns {Array}
389
+ */
390
+ getPending() {
391
+ return this.queue.filter(e => e.status === 'pending');
392
+ }
393
+
394
+ /**
395
+ * Returns all quarantined items.
396
+ * @returns {Array}
397
+ */
398
+ getAll() {
399
+ return [...this.queue];
400
+ }
401
+
402
+ clear() {
403
+ this.queue = [];
404
+ }
405
+ }
406
+
407
+ module.exports = {
408
+ ToolSequenceAnalyzer,
409
+ PermissionBoundary,
410
+ InputQuarantine,
411
+ SUSPICIOUS_SEQUENCES
412
+ };