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,601 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Worker Scanner
5
+ *
6
+ * Async scanning for non-blocking operation. Uses setImmediate/setTimeout to
7
+ * yield to the event loop between scans, preventing long-running scans from
8
+ * blocking the main thread.
9
+ *
10
+ * NOTE: This implementation uses async wrappers around the synchronous scanner
11
+ * with event loop yielding. In production environments requiring true parallel
12
+ * CPU-bound scanning, you can swap in Node.js worker_threads by replacing the
13
+ * _runInWorker method with actual Worker thread dispatch.
14
+ *
15
+ * All detection runs locally — no data ever leaves your environment.
16
+ */
17
+
18
+ const { scanText } = require('./detector-core');
19
+
20
+ // =========================================================================
21
+ // HELPERS
22
+ // =========================================================================
23
+
24
+ /**
25
+ * Yield to the event loop. Uses setImmediate when available, falls back to setTimeout.
26
+ * @returns {Promise<void>}
27
+ */
28
+ const yieldToEventLoop = () => new Promise(resolve => {
29
+ if (typeof setImmediate === 'function') {
30
+ setImmediate(resolve);
31
+ } else {
32
+ setTimeout(resolve, 0);
33
+ }
34
+ });
35
+
36
+ /**
37
+ * Create a deferred promise with external resolve/reject.
38
+ * @returns {{ promise: Promise, resolve: Function, reject: Function }}
39
+ */
40
+ function createDeferred() {
41
+ let resolve, reject;
42
+ const promise = new Promise((res, rej) => {
43
+ resolve = res;
44
+ reject = rej;
45
+ });
46
+ return { promise, resolve, reject };
47
+ }
48
+
49
+ // =========================================================================
50
+ // WORKER SCANNER
51
+ // =========================================================================
52
+
53
+ /**
54
+ * Async scanner that runs scans without blocking the event loop.
55
+ * Manages a virtual "pool" with concurrency control and timeout support.
56
+ */
57
+ class WorkerScanner {
58
+ /**
59
+ * @param {object} [options]
60
+ * @param {number} [options.poolSize=2] - Maximum concurrent scans.
61
+ * @param {number} [options.timeout=5000] - Per-scan timeout in milliseconds.
62
+ */
63
+ constructor(options = {}) {
64
+ this.poolSize = options.poolSize || 2;
65
+ this.timeout = options.timeout || 5000;
66
+
67
+ this._activeWorkers = 0;
68
+ this._completedJobs = 0;
69
+ this._errorCount = 0;
70
+ this._queue = [];
71
+ this._terminated = false;
72
+
73
+ console.log('[Agent Shield] WorkerScanner initialized (poolSize: %d, timeout: %dms)', this.poolSize, this.timeout);
74
+ }
75
+
76
+ /**
77
+ * Scan text asynchronously without blocking the event loop.
78
+ * @param {string} text - The text to scan.
79
+ * @param {object} [options] - Scan options passed to scanText.
80
+ * @returns {Promise<object>} Scan result from detector-core.
81
+ */
82
+ async scan(text, options = {}) {
83
+ if (this._terminated) {
84
+ throw new Error('WorkerScanner has been terminated.');
85
+ }
86
+
87
+ // Wait for an available slot
88
+ while (this._activeWorkers >= this.poolSize) {
89
+ await yieldToEventLoop();
90
+ }
91
+
92
+ return this._runScan(text, options);
93
+ }
94
+
95
+ /**
96
+ * Scan multiple texts in parallel using the worker pool.
97
+ * @param {string[]} texts - Array of texts to scan.
98
+ * @param {object} [options] - Scan options passed to scanText.
99
+ * @returns {Promise<object[]>} Array of scan results.
100
+ */
101
+ async scanBatch(texts, options = {}) {
102
+ if (this._terminated) {
103
+ throw new Error('WorkerScanner has been terminated.');
104
+ }
105
+
106
+ if (!Array.isArray(texts) || texts.length === 0) {
107
+ return [];
108
+ }
109
+
110
+ // Launch all scans, concurrency is managed inside _runScan
111
+ const promises = texts.map(text => this.scan(text, options));
112
+ return Promise.all(promises);
113
+ }
114
+
115
+ /**
116
+ * Get pool statistics.
117
+ * @returns {object} Stats: { activeWorkers, queuedJobs, completed, errors, poolSize, terminated }.
118
+ */
119
+ getStats() {
120
+ return {
121
+ activeWorkers: this._activeWorkers,
122
+ queuedJobs: this._queue.length,
123
+ completed: this._completedJobs,
124
+ errors: this._errorCount,
125
+ poolSize: this.poolSize,
126
+ terminated: this._terminated
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Shut down the worker pool. Pending scans will be rejected.
132
+ */
133
+ terminate() {
134
+ this._terminated = true;
135
+
136
+ // Reject any queued jobs
137
+ for (const job of this._queue) {
138
+ job.reject(new Error('WorkerScanner terminated.'));
139
+ }
140
+ this._queue = [];
141
+
142
+ console.log('[Agent Shield] WorkerScanner terminated (completed: %d, errors: %d)', this._completedJobs, this._errorCount);
143
+ }
144
+
145
+ /**
146
+ * Run a single scan with timeout and event loop yielding.
147
+ * @param {string} text
148
+ * @param {object} options
149
+ * @returns {Promise<object>}
150
+ * @private
151
+ */
152
+ async _runScan(text, options) {
153
+ this._activeWorkers++;
154
+
155
+ try {
156
+ // Yield to the event loop before starting CPU work
157
+ await yieldToEventLoop();
158
+
159
+ const result = await this._withTimeout(() => {
160
+ return scanText(text, options);
161
+ }, this.timeout);
162
+
163
+ this._completedJobs++;
164
+
165
+ // Yield after completing CPU work
166
+ await yieldToEventLoop();
167
+
168
+ return result;
169
+ } catch (err) {
170
+ this._errorCount++;
171
+ throw err;
172
+ } finally {
173
+ this._activeWorkers--;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Run a function with a timeout.
179
+ * @param {Function} fn - Synchronous function to run.
180
+ * @param {number} timeoutMs - Timeout in milliseconds.
181
+ * @returns {Promise<*>} Result of the function.
182
+ * @private
183
+ */
184
+ _withTimeout(fn, timeoutMs) {
185
+ return new Promise((resolve, reject) => {
186
+ const timer = setTimeout(() => {
187
+ reject(new Error(`Scan timed out after ${timeoutMs}ms`));
188
+ }, timeoutMs);
189
+
190
+ try {
191
+ const result = fn();
192
+ clearTimeout(timer);
193
+ resolve(result);
194
+ } catch (err) {
195
+ clearTimeout(timer);
196
+ reject(err);
197
+ }
198
+ });
199
+ }
200
+ }
201
+
202
+ // =========================================================================
203
+ // SCAN QUEUE
204
+ // =========================================================================
205
+
206
+ /**
207
+ * Priority queue for managing scan jobs with concurrency control,
208
+ * pause/resume, and drain support.
209
+ */
210
+ class ScanQueue {
211
+ /**
212
+ * @param {object} [options]
213
+ * @param {number} [options.concurrency=4] - Maximum concurrent scans.
214
+ * @param {number} [options.maxQueue=10000] - Maximum queued items.
215
+ */
216
+ constructor(options = {}) {
217
+ this.concurrency = options.concurrency || 4;
218
+ this.maxQueue = options.maxQueue || 10000;
219
+
220
+ this._queue = [];
221
+ this._activeCount = 0;
222
+ this._paused = false;
223
+ this._totalEnqueued = 0;
224
+ this._totalProcessed = 0;
225
+ this._totalErrors = 0;
226
+ this._latencySum = 0;
227
+ this._drainCallbacks = [];
228
+
229
+ console.log('[Agent Shield] ScanQueue initialized (concurrency: %d, maxQueue: %d)', this.concurrency, this.maxQueue);
230
+ }
231
+
232
+ /**
233
+ * Add a scan job to the queue.
234
+ * @param {string} text - The text to scan.
235
+ * @param {object} [options] - Scan options passed to scanText.
236
+ * @param {number} [priority=0] - Priority (higher = processed first).
237
+ * @returns {Promise<object>} Promise that resolves with the scan result.
238
+ */
239
+ async enqueue(text, options = {}, priority = 0) {
240
+ if (this._queue.length >= this.maxQueue) {
241
+ throw new Error(`ScanQueue is full (${this.maxQueue} items). Rejecting new scan.`);
242
+ }
243
+
244
+ const deferred = createDeferred();
245
+ const job = {
246
+ text,
247
+ options,
248
+ priority,
249
+ enqueuedAt: Date.now(),
250
+ deferred
251
+ };
252
+
253
+ this._queue.push(job);
254
+ this._totalEnqueued++;
255
+
256
+ // Sort by priority (descending) — highest priority first
257
+ this._queue.sort((a, b) => b.priority - a.priority);
258
+
259
+ // Try to process
260
+ this._processNext();
261
+
262
+ return deferred.promise;
263
+ }
264
+
265
+ /**
266
+ * Pause queue processing. In-flight scans will complete, but no new scans start.
267
+ */
268
+ pause() {
269
+ this._paused = true;
270
+ console.log('[Agent Shield] ScanQueue paused');
271
+ }
272
+
273
+ /**
274
+ * Resume queue processing.
275
+ */
276
+ resume() {
277
+ this._paused = false;
278
+ console.log('[Agent Shield] ScanQueue resumed');
279
+ this._processNext();
280
+ }
281
+
282
+ /**
283
+ * Wait for all pending and in-flight jobs to complete.
284
+ * @returns {Promise<void>}
285
+ */
286
+ drain() {
287
+ if (this._queue.length === 0 && this._activeCount === 0) {
288
+ return Promise.resolve();
289
+ }
290
+
291
+ return new Promise(resolve => {
292
+ this._drainCallbacks.push(resolve);
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Get queue statistics.
298
+ * @returns {object} Stats: { depth, active, processed, errors, avgLatencyMs, paused }.
299
+ */
300
+ getStats() {
301
+ const avgLatencyMs = this._totalProcessed > 0
302
+ ? Math.round(this._latencySum / this._totalProcessed)
303
+ : 0;
304
+
305
+ return {
306
+ depth: this._queue.length,
307
+ active: this._activeCount,
308
+ processed: this._totalProcessed,
309
+ errors: this._totalErrors,
310
+ avgLatencyMs,
311
+ paused: this._paused,
312
+ totalEnqueued: this._totalEnqueued,
313
+ maxQueue: this.maxQueue,
314
+ concurrency: this.concurrency
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Process the next job in the queue if concurrency allows.
320
+ * @private
321
+ */
322
+ _processNext() {
323
+ if (this._paused) return;
324
+ if (this._activeCount >= this.concurrency) return;
325
+ if (this._queue.length === 0) {
326
+ this._checkDrain();
327
+ return;
328
+ }
329
+
330
+ const job = this._queue.shift();
331
+ this._activeCount++;
332
+
333
+ // Use setImmediate/setTimeout to avoid blocking the event loop
334
+ const run = async () => {
335
+ const startTime = Date.now();
336
+
337
+ try {
338
+ // Yield before CPU work
339
+ await yieldToEventLoop();
340
+
341
+ const result = scanText(job.text, job.options);
342
+ const latency = Date.now() - job.enqueuedAt;
343
+
344
+ this._latencySum += latency;
345
+ this._totalProcessed++;
346
+
347
+ job.deferred.resolve(result);
348
+ } catch (err) {
349
+ this._totalErrors++;
350
+ job.deferred.reject(err);
351
+ } finally {
352
+ this._activeCount--;
353
+
354
+ // Yield after CPU work, then try next
355
+ await yieldToEventLoop();
356
+ this._processNext();
357
+ }
358
+ };
359
+
360
+ run();
361
+ }
362
+
363
+ /**
364
+ * Check if the queue has drained and notify any waiting callbacks.
365
+ * @private
366
+ */
367
+ _checkDrain() {
368
+ if (this._queue.length === 0 && this._activeCount === 0) {
369
+ const callbacks = this._drainCallbacks;
370
+ this._drainCallbacks = [];
371
+ for (const cb of callbacks) {
372
+ cb();
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ // =========================================================================
379
+ // THREADED WORKER SCANNER (opt-in, uses worker_threads)
380
+ // =========================================================================
381
+
382
+ /**
383
+ * Worker scanner using real Node.js worker_threads for true parallel scanning.
384
+ * Falls back to WorkerScanner (async yield) if worker_threads is unavailable.
385
+ *
386
+ * Usage:
387
+ * const scanner = new ThreadedWorkerScanner({ poolSize: 4 });
388
+ * const result = await scanner.scan('some text');
389
+ * scanner.terminate();
390
+ *
391
+ * NOTE: This uses worker_threads which requires Node.js >= 12. The zero-dep
392
+ * constraint is maintained since worker_threads is a Node.js built-in.
393
+ */
394
+ class ThreadedWorkerScanner {
395
+ /**
396
+ * @param {object} [options]
397
+ * @param {number} [options.poolSize=2] - Number of worker threads.
398
+ * @param {number} [options.timeout=5000] - Per-scan timeout in ms.
399
+ */
400
+ constructor(options = {}) {
401
+ this.poolSize = options.poolSize || 2;
402
+ this.timeout = options.timeout || 5000;
403
+ this._workers = [];
404
+ this._queue = [];
405
+ this._completedJobs = 0;
406
+ this._errorCount = 0;
407
+ this._terminated = false;
408
+ this._workerThreadsAvailable = false;
409
+
410
+ try {
411
+ this._workerThreadsModule = require('worker_threads');
412
+ if (this._workerThreadsModule.isMainThread) {
413
+ this._workerThreadsAvailable = true;
414
+ this._initWorkers();
415
+ }
416
+ } catch (e) {
417
+ console.log('[Agent Shield] worker_threads not available, falling back to async mode.');
418
+ }
419
+
420
+ if (!this._workerThreadsAvailable) {
421
+ this._fallback = new WorkerScanner({
422
+ poolSize: this.poolSize,
423
+ timeout: this.timeout
424
+ });
425
+ }
426
+
427
+ console.log('[Agent Shield] ThreadedWorkerScanner initialized (poolSize: %d, threaded: %s)', this.poolSize, this._workerThreadsAvailable);
428
+ }
429
+
430
+ /**
431
+ * Initialize the worker thread pool.
432
+ * @private
433
+ */
434
+ _initWorkers() {
435
+ const { Worker } = this._workerThreadsModule;
436
+ const workerScript = `
437
+ const { parentPort } = require('worker_threads');
438
+ const { scanText } = require('${require('path').resolve(__dirname, 'detector-core.js').replace(/\\/g, '\\\\')}');
439
+
440
+ parentPort.on('message', (msg) => {
441
+ try {
442
+ const result = scanText(msg.text, msg.options || {});
443
+ parentPort.postMessage({ id: msg.id, result, error: null });
444
+ } catch (err) {
445
+ parentPort.postMessage({ id: msg.id, result: null, error: err.message });
446
+ }
447
+ });
448
+ `;
449
+
450
+ for (let i = 0; i < this.poolSize; i++) {
451
+ const worker = new Worker(workerScript, { eval: true });
452
+ worker._busy = false;
453
+ worker._currentJob = null;
454
+
455
+ worker.on('message', (msg) => {
456
+ const job = worker._currentJob;
457
+ worker._busy = false;
458
+ worker._currentJob = null;
459
+
460
+ if (job) {
461
+ clearTimeout(job.timer);
462
+ if (msg.error) {
463
+ this._errorCount++;
464
+ job.reject(new Error(msg.error));
465
+ } else {
466
+ this._completedJobs++;
467
+ job.resolve(msg.result);
468
+ }
469
+ }
470
+
471
+ this._processQueue();
472
+ });
473
+
474
+ worker.on('error', (err) => {
475
+ const job = worker._currentJob;
476
+ worker._busy = false;
477
+ worker._currentJob = null;
478
+ this._errorCount++;
479
+
480
+ if (job) {
481
+ clearTimeout(job.timer);
482
+ job.reject(err);
483
+ }
484
+
485
+ this._processQueue();
486
+ });
487
+
488
+ this._workers.push(worker);
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Scan text using a worker thread (or fallback).
494
+ * @param {string} text - The text to scan.
495
+ * @param {object} [options] - Scan options passed to scanText.
496
+ * @returns {Promise<object>} Scan result.
497
+ */
498
+ async scan(text, options = {}) {
499
+ if (this._terminated) {
500
+ throw new Error('ThreadedWorkerScanner has been terminated.');
501
+ }
502
+
503
+ if (!this._workerThreadsAvailable) {
504
+ return this._fallback.scan(text, options);
505
+ }
506
+
507
+ return new Promise((resolve, reject) => {
508
+ const id = ++this._completedJobs + this._errorCount + this._queue.length;
509
+ const job = { id, text, options, resolve, reject, timer: null };
510
+
511
+ job.timer = setTimeout(() => {
512
+ reject(new Error(`Scan timed out after ${this.timeout}ms`));
513
+ }, this.timeout);
514
+
515
+ this._queue.push(job);
516
+ this._processQueue();
517
+ });
518
+ }
519
+
520
+ /**
521
+ * Scan multiple texts in parallel.
522
+ * @param {string[]} texts - Array of texts to scan.
523
+ * @param {object} [options] - Scan options.
524
+ * @returns {Promise<object[]>} Array of scan results.
525
+ */
526
+ async scanBatch(texts, options = {}) {
527
+ if (!Array.isArray(texts) || texts.length === 0) return [];
528
+ return Promise.all(texts.map(text => this.scan(text, options)));
529
+ }
530
+
531
+ /**
532
+ * Process queued jobs by dispatching to idle workers.
533
+ * @private
534
+ */
535
+ _processQueue() {
536
+ if (this._queue.length === 0) return;
537
+
538
+ for (const worker of this._workers) {
539
+ if (!worker._busy && this._queue.length > 0) {
540
+ const job = this._queue.shift();
541
+ worker._busy = true;
542
+ worker._currentJob = job;
543
+ worker.postMessage({ id: job.id, text: job.text, options: job.options });
544
+ }
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Get pool statistics.
550
+ * @returns {object}
551
+ */
552
+ getStats() {
553
+ if (!this._workerThreadsAvailable && this._fallback) {
554
+ return { ...this._fallback.getStats(), threaded: false };
555
+ }
556
+
557
+ return {
558
+ activeWorkers: this._workers.filter(w => w._busy).length,
559
+ queuedJobs: this._queue.length,
560
+ completed: this._completedJobs,
561
+ errors: this._errorCount,
562
+ poolSize: this.poolSize,
563
+ terminated: this._terminated,
564
+ threaded: true
565
+ };
566
+ }
567
+
568
+ /**
569
+ * Shut down all worker threads.
570
+ */
571
+ terminate() {
572
+ this._terminated = true;
573
+
574
+ if (this._workerThreadsAvailable) {
575
+ for (const worker of this._workers) {
576
+ if (worker._currentJob) {
577
+ clearTimeout(worker._currentJob.timer);
578
+ worker._currentJob.reject(new Error('ThreadedWorkerScanner terminated.'));
579
+ }
580
+ worker.terminate();
581
+ }
582
+ this._workers = [];
583
+ } else if (this._fallback) {
584
+ this._fallback.terminate();
585
+ }
586
+
587
+ for (const job of this._queue) {
588
+ clearTimeout(job.timer);
589
+ job.reject(new Error('ThreadedWorkerScanner terminated.'));
590
+ }
591
+ this._queue = [];
592
+
593
+ console.log('[Agent Shield] ThreadedWorkerScanner terminated (completed: %d, errors: %d)', this._completedJobs, this._errorCount);
594
+ }
595
+ }
596
+
597
+ // =========================================================================
598
+ // EXPORTS
599
+ // =========================================================================
600
+
601
+ module.exports = { WorkerScanner, ScanQueue, ThreadedWorkerScanner };