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,349 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — Plugin System
|
|
5
|
+
*
|
|
6
|
+
* Lets users write custom detectors as lightweight plugin objects.
|
|
7
|
+
* Plugins are simple objects with a detect() method that returns an array
|
|
8
|
+
* of threat findings. All detection runs locally — no data ever leaves
|
|
9
|
+
* your environment.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
// =========================================================================
|
|
16
|
+
// HELPERS
|
|
17
|
+
// =========================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get current time in ms.
|
|
21
|
+
* @returns {number}
|
|
22
|
+
*/
|
|
23
|
+
const now = () => {
|
|
24
|
+
if (typeof performance !== 'undefined' && performance.now) {
|
|
25
|
+
return performance.now();
|
|
26
|
+
}
|
|
27
|
+
return Date.now();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// =========================================================================
|
|
31
|
+
// PLUGIN SANDBOX
|
|
32
|
+
// =========================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Runs plugins with timeout protection and error isolation.
|
|
36
|
+
* Prevents a misbehaving plugin from crashing the host agent.
|
|
37
|
+
*/
|
|
38
|
+
class PluginSandbox {
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} [options]
|
|
41
|
+
* @param {number} [options.timeoutMs=100] - Maximum execution time per plugin in ms
|
|
42
|
+
*/
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
this.timeoutMs = options.timeoutMs || 100;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Execute a plugin's detect() method with timeout and error isolation.
|
|
49
|
+
* @param {object} plugin - Plugin object with detect() method
|
|
50
|
+
* @param {string} text - Text to scan
|
|
51
|
+
* @param {object} [options] - Options passed to detect()
|
|
52
|
+
* @returns {{results: Array, error: string|null, durationMs: number}}
|
|
53
|
+
*/
|
|
54
|
+
run(plugin, text, options = {}) {
|
|
55
|
+
const start = now();
|
|
56
|
+
let results = [];
|
|
57
|
+
let error = null;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Run detection synchronously with a time check after completion.
|
|
61
|
+
// True preemptive timeout would require worker_threads, but for a
|
|
62
|
+
// lightweight zero-dependency SDK we keep it simple: run, measure,
|
|
63
|
+
// and flag if it exceeded the budget.
|
|
64
|
+
const output = plugin.detect(text, options);
|
|
65
|
+
const durationMs = now() - start;
|
|
66
|
+
|
|
67
|
+
if (durationMs > this.timeoutMs) {
|
|
68
|
+
console.log(`[Agent Shield] Plugin "${plugin.name}" exceeded timeout (${durationMs.toFixed(1)}ms > ${this.timeoutMs}ms)`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(output)) {
|
|
72
|
+
results = output;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { results, error: null, durationMs };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const durationMs = now() - start;
|
|
78
|
+
error = err.message || String(err);
|
|
79
|
+
console.log(`[Agent Shield] Plugin "${plugin.name}" threw an error: ${error}`);
|
|
80
|
+
return { results: [], error, durationMs };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// =========================================================================
|
|
86
|
+
// PLUGIN TEMPLATE
|
|
87
|
+
// =========================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Helper class to create well-formed plugins from patterns or functions.
|
|
91
|
+
*/
|
|
92
|
+
class PluginTemplate {
|
|
93
|
+
/**
|
|
94
|
+
* Create a plugin from pattern definitions (detector-core format).
|
|
95
|
+
* @param {object} config
|
|
96
|
+
* @param {string} config.name - Plugin name
|
|
97
|
+
* @param {string} [config.version='1.0.0'] - Plugin version
|
|
98
|
+
* @param {Array<{regex: RegExp, severity: string, category: string, description: string, detail: string}>} config.patterns
|
|
99
|
+
* @returns {object} A valid plugin object
|
|
100
|
+
*/
|
|
101
|
+
static create({ name, version = '1.0.0', patterns = [] }) {
|
|
102
|
+
return {
|
|
103
|
+
name,
|
|
104
|
+
version,
|
|
105
|
+
detect(text) {
|
|
106
|
+
const findings = [];
|
|
107
|
+
for (const pattern of patterns) {
|
|
108
|
+
if (pattern.regex && pattern.regex.test(text)) {
|
|
109
|
+
findings.push({
|
|
110
|
+
severity: pattern.severity || 'medium',
|
|
111
|
+
category: pattern.category || name,
|
|
112
|
+
description: pattern.description || 'Pattern match detected',
|
|
113
|
+
detail: pattern.detail || ''
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Wrap a bare detection function as a plugin object.
|
|
124
|
+
* @param {object} config
|
|
125
|
+
* @param {string} config.name - Plugin name
|
|
126
|
+
* @param {string} [config.version='1.0.0'] - Plugin version
|
|
127
|
+
* @param {function} config.detect - Detection function (text, options) => Array
|
|
128
|
+
* @returns {object} A valid plugin object
|
|
129
|
+
*/
|
|
130
|
+
static createFromFunction({ name, version = '1.0.0', detect }) {
|
|
131
|
+
return { name, version, detect };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate that a plugin object has the required fields and correct types.
|
|
136
|
+
* @param {object} plugin - Plugin object to validate
|
|
137
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
138
|
+
*/
|
|
139
|
+
static validate(plugin) {
|
|
140
|
+
const errors = [];
|
|
141
|
+
|
|
142
|
+
if (!plugin || typeof plugin !== 'object') {
|
|
143
|
+
return { valid: false, errors: ['Plugin must be a non-null object'] };
|
|
144
|
+
}
|
|
145
|
+
if (typeof plugin.name !== 'string' || plugin.name.length === 0) {
|
|
146
|
+
errors.push('Plugin must have a non-empty string "name" property');
|
|
147
|
+
}
|
|
148
|
+
if (typeof plugin.detect !== 'function') {
|
|
149
|
+
errors.push('Plugin must have a "detect" function');
|
|
150
|
+
}
|
|
151
|
+
if (plugin.version !== undefined && typeof plugin.version !== 'string') {
|
|
152
|
+
errors.push('Plugin "version" must be a string if provided');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { valid: errors.length === 0, errors };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// =========================================================================
|
|
160
|
+
// PLUGIN MANAGER
|
|
161
|
+
// =========================================================================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Manages the lifecycle of detector plugins: registration, toggling,
|
|
165
|
+
* scanning, and per-plugin statistics.
|
|
166
|
+
*/
|
|
167
|
+
class PluginManager {
|
|
168
|
+
/**
|
|
169
|
+
* Initialize an empty plugin registry.
|
|
170
|
+
*/
|
|
171
|
+
constructor() {
|
|
172
|
+
/** @type {Map<string, {plugin: object, enabled: boolean, stats: {scans: number, threats: number, totalMs: number}}>} */
|
|
173
|
+
this._registry = new Map();
|
|
174
|
+
this._sandbox = new PluginSandbox();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Register a plugin object.
|
|
179
|
+
* @param {object} plugin - Plugin with name, version, and detect()
|
|
180
|
+
* @throws {Error} If plugin is invalid or name is already registered
|
|
181
|
+
*/
|
|
182
|
+
register(plugin) {
|
|
183
|
+
const { valid, errors } = PluginTemplate.validate(plugin);
|
|
184
|
+
if (!valid) {
|
|
185
|
+
throw new Error(`[Agent Shield] Invalid plugin: ${errors.join('; ')}`);
|
|
186
|
+
}
|
|
187
|
+
if (this._registry.has(plugin.name)) {
|
|
188
|
+
throw new Error(`[Agent Shield] Plugin "${plugin.name}" is already registered`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this._registry.set(plugin.name, {
|
|
192
|
+
plugin,
|
|
193
|
+
enabled: true,
|
|
194
|
+
stats: { scans: 0, threats: 0, totalMs: 0 }
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
console.log(`[Agent Shield] Registered plugin "${plugin.name}" v${plugin.version || 'unknown'}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Load and register a plugin from a .js file.
|
|
202
|
+
* @param {string} filePath - Absolute or relative path to a .js plugin file
|
|
203
|
+
*/
|
|
204
|
+
registerFromFile(filePath) {
|
|
205
|
+
const resolved = path.resolve(filePath);
|
|
206
|
+
const plugin = require(resolved);
|
|
207
|
+
this.register(plugin);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Load all .js files from a directory as plugins.
|
|
212
|
+
* Skips files that fail to load and logs a warning.
|
|
213
|
+
* @param {string} dirPath - Path to directory containing plugin .js files
|
|
214
|
+
*/
|
|
215
|
+
loadDirectory(dirPath) {
|
|
216
|
+
const resolved = path.resolve(dirPath);
|
|
217
|
+
let files;
|
|
218
|
+
try {
|
|
219
|
+
files = fs.readdirSync(resolved);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.log(`[Agent Shield] Could not read plugin directory "${resolved}": ${err.message}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const jsFiles = files.filter(f => f.endsWith('.js'));
|
|
226
|
+
for (const file of jsFiles) {
|
|
227
|
+
try {
|
|
228
|
+
this.registerFromFile(path.join(resolved, file));
|
|
229
|
+
} catch (err) {
|
|
230
|
+
console.log(`[Agent Shield] Failed to load plugin from "${file}": ${err.message}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Remove a plugin from the registry.
|
|
237
|
+
* @param {string} name - Plugin name
|
|
238
|
+
* @returns {boolean} True if the plugin was found and removed
|
|
239
|
+
*/
|
|
240
|
+
unregister(name) {
|
|
241
|
+
const removed = this._registry.delete(name);
|
|
242
|
+
if (removed) {
|
|
243
|
+
console.log(`[Agent Shield] Unregistered plugin "${name}"`);
|
|
244
|
+
}
|
|
245
|
+
return removed;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* List all registered plugins.
|
|
250
|
+
* @returns {Array<{name: string, version: string, enabled: boolean}>}
|
|
251
|
+
*/
|
|
252
|
+
list() {
|
|
253
|
+
const result = [];
|
|
254
|
+
for (const [name, entry] of this._registry) {
|
|
255
|
+
result.push({
|
|
256
|
+
name,
|
|
257
|
+
version: entry.plugin.version || 'unknown',
|
|
258
|
+
enabled: entry.enabled
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Enable a registered plugin.
|
|
266
|
+
* @param {string} name - Plugin name
|
|
267
|
+
* @throws {Error} If plugin is not registered
|
|
268
|
+
*/
|
|
269
|
+
enable(name) {
|
|
270
|
+
const entry = this._registry.get(name);
|
|
271
|
+
if (!entry) {
|
|
272
|
+
throw new Error(`[Agent Shield] Plugin "${name}" is not registered`);
|
|
273
|
+
}
|
|
274
|
+
entry.enabled = true;
|
|
275
|
+
console.log(`[Agent Shield] Enabled plugin "${name}"`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Disable a registered plugin.
|
|
280
|
+
* @param {string} name - Plugin name
|
|
281
|
+
* @throws {Error} If plugin is not registered
|
|
282
|
+
*/
|
|
283
|
+
disable(name) {
|
|
284
|
+
const entry = this._registry.get(name);
|
|
285
|
+
if (!entry) {
|
|
286
|
+
throw new Error(`[Agent Shield] Plugin "${name}" is not registered`);
|
|
287
|
+
}
|
|
288
|
+
entry.enabled = false;
|
|
289
|
+
console.log(`[Agent Shield] Disabled plugin "${name}"`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Run all enabled plugins against the given text and merge results.
|
|
294
|
+
* @param {string} text - Text to scan
|
|
295
|
+
* @param {object} [options] - Options passed to each plugin's detect()
|
|
296
|
+
* @returns {Array<{severity: string, category: string, description: string, detail: string, plugin: string}>}
|
|
297
|
+
*/
|
|
298
|
+
scan(text, options = {}) {
|
|
299
|
+
const merged = [];
|
|
300
|
+
|
|
301
|
+
for (const [name, entry] of this._registry) {
|
|
302
|
+
if (!entry.enabled) continue;
|
|
303
|
+
|
|
304
|
+
const { results, error, durationMs } = this._sandbox.run(entry.plugin, text, options);
|
|
305
|
+
|
|
306
|
+
entry.stats.scans += 1;
|
|
307
|
+
entry.stats.totalMs += durationMs;
|
|
308
|
+
|
|
309
|
+
if (!error) {
|
|
310
|
+
for (const finding of results) {
|
|
311
|
+
merged.push({
|
|
312
|
+
severity: finding.severity,
|
|
313
|
+
category: finding.category,
|
|
314
|
+
description: finding.description,
|
|
315
|
+
detail: finding.detail || '',
|
|
316
|
+
plugin: name
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
entry.stats.threats += results.length;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return merged;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get per-plugin scan statistics.
|
|
328
|
+
* @returns {Array<{name: string, scans: number, threats: number, avgMs: number}>}
|
|
329
|
+
*/
|
|
330
|
+
getStats() {
|
|
331
|
+
const result = [];
|
|
332
|
+
for (const [name, entry] of this._registry) {
|
|
333
|
+
const { scans, threats, totalMs } = entry.stats;
|
|
334
|
+
result.push({
|
|
335
|
+
name,
|
|
336
|
+
scans,
|
|
337
|
+
threats,
|
|
338
|
+
avgMs: scans > 0 ? Math.round((totalMs / scans) * 100) / 100 : 0
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// =========================================================================
|
|
346
|
+
// EXPORTS
|
|
347
|
+
// =========================================================================
|
|
348
|
+
|
|
349
|
+
module.exports = { PluginManager, PluginTemplate, PluginSandbox };
|