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
package/src/otel.js ADDED
@@ -0,0 +1,544 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — OpenTelemetry-Compatible Metrics & Tracing
5
+ *
6
+ * Emits OTel-compatible data formats without requiring the OTel SDK dependency.
7
+ * Features:
8
+ * - ShieldMetrics: counters, histograms in OTel/Prometheus format
9
+ * - ShieldTracer: spans and traces in OTLP-compatible JSON
10
+ * - MetricsDashboard: human-readable summaries and percentiles
11
+ */
12
+
13
+ const crypto = require('crypto');
14
+
15
+ // =========================================================================
16
+ // Shield Metrics
17
+ // =========================================================================
18
+
19
+ class ShieldMetrics {
20
+ /**
21
+ * @param {Object} [options]
22
+ * @param {string} [options.serviceName='agent-shield'] - Service name for metric labels.
23
+ * @param {number} [options.interval=60000] - Metric flush interval in ms.
24
+ */
25
+ constructor(options = {}) {
26
+ this.serviceName = options.serviceName || 'agent-shield';
27
+ this.interval = options.interval || 60000;
28
+
29
+ this._scans = { total: 0, blocked: 0, latencies: [] };
30
+ this._threats = {};
31
+ this._blocks = { total: 0, contexts: [] };
32
+ this._windows = [];
33
+ this._startTime = Date.now();
34
+ this._lastFlush = Date.now();
35
+ }
36
+
37
+ /**
38
+ * Record a scan event with timing information.
39
+ * @param {Object} result - Scan result.
40
+ * @param {number} [result.latencyMs] - Scan latency in milliseconds.
41
+ * @param {boolean} [result.blocked] - Whether the scan resulted in a block.
42
+ * @param {number} [result.threatCount] - Number of threats detected.
43
+ */
44
+ recordScan(result) {
45
+ this._scans.total++;
46
+
47
+ if (result.latencyMs !== undefined) {
48
+ this._scans.latencies.push(result.latencyMs);
49
+ // Keep latencies bounded
50
+ if (this._scans.latencies.length > 10000) {
51
+ this._scans.latencies = this._scans.latencies.slice(-5000);
52
+ }
53
+ }
54
+
55
+ if (result.blocked) {
56
+ this._scans.blocked++;
57
+ }
58
+
59
+ // Record window snapshot for throughput tracking
60
+ const now = Date.now();
61
+ if (now - this._lastFlush >= this.interval) {
62
+ this._windows.push({
63
+ timestamp: now,
64
+ scans: this._scans.total,
65
+ blocked: this._scans.blocked
66
+ });
67
+ this._lastFlush = now;
68
+ // Keep max 1440 windows (~24h at 1-min intervals)
69
+ if (this._windows.length > 1440) {
70
+ this._windows = this._windows.slice(-1440);
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Record a threat detection event.
77
+ * @param {Object} threat - Threat information.
78
+ * @param {string} [threat.category] - Threat category.
79
+ * @param {string} [threat.severity] - Threat severity level.
80
+ */
81
+ recordThreat(threat) {
82
+ const category = threat.category || 'unknown';
83
+ const severity = threat.severity || 'medium';
84
+
85
+ if (!this._threats[category]) {
86
+ this._threats[category] = { count: 0, severities: {} };
87
+ }
88
+
89
+ this._threats[category].count++;
90
+
91
+ if (!this._threats[category].severities[severity]) {
92
+ this._threats[category].severities[severity] = 0;
93
+ }
94
+ this._threats[category].severities[severity]++;
95
+ }
96
+
97
+ /**
98
+ * Record a block event.
99
+ * @param {Object} context - Block context.
100
+ * @param {string} [context.reason] - Reason for blocking.
101
+ * @param {string} [context.category] - Threat category that triggered the block.
102
+ */
103
+ recordBlock(context) {
104
+ this._blocks.total++;
105
+ this._blocks.contexts.push({
106
+ reason: context.reason || 'unknown',
107
+ category: context.category || 'unknown',
108
+ timestamp: Date.now()
109
+ });
110
+
111
+ // Keep bounded
112
+ if (this._blocks.contexts.length > 1000) {
113
+ this._blocks.contexts = this._blocks.contexts.slice(-500);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Return OTel-compatible metric objects.
119
+ * @returns {Array<Object>} Array of metric objects {name, type, value, labels, timestamp}.
120
+ */
121
+ getMetrics() {
122
+ const now = Date.now();
123
+ const metrics = [];
124
+
125
+ metrics.push({
126
+ name: 'agent_shield_scans_total',
127
+ type: 'counter',
128
+ value: this._scans.total,
129
+ labels: { service: this.serviceName },
130
+ timestamp: now
131
+ });
132
+
133
+ metrics.push({
134
+ name: 'agent_shield_blocks_total',
135
+ type: 'counter',
136
+ value: this._blocks.total,
137
+ labels: { service: this.serviceName },
138
+ timestamp: now
139
+ });
140
+
141
+ metrics.push({
142
+ name: 'agent_shield_scans_blocked_total',
143
+ type: 'counter',
144
+ value: this._scans.blocked,
145
+ labels: { service: this.serviceName },
146
+ timestamp: now
147
+ });
148
+
149
+ // Threat counters per category
150
+ for (const [category, data] of Object.entries(this._threats)) {
151
+ metrics.push({
152
+ name: 'agent_shield_threats_total',
153
+ type: 'counter',
154
+ value: data.count,
155
+ labels: { service: this.serviceName, category },
156
+ timestamp: now
157
+ });
158
+ }
159
+
160
+ // Latency histogram summary
161
+ if (this._scans.latencies.length > 0) {
162
+ const sorted = [...this._scans.latencies].sort((a, b) => a - b);
163
+ const p50 = sorted[Math.floor(sorted.length * 0.5)];
164
+ const p95 = sorted[Math.floor(sorted.length * 0.95)];
165
+ const p99 = sorted[Math.floor(sorted.length * 0.99)];
166
+
167
+ metrics.push({
168
+ name: 'agent_shield_scan_latency_ms',
169
+ type: 'histogram',
170
+ value: { p50, p95, p99, count: sorted.length, sum: sorted.reduce((a, b) => a + b, 0) },
171
+ labels: { service: this.serviceName },
172
+ timestamp: now
173
+ });
174
+ }
175
+
176
+ return metrics;
177
+ }
178
+
179
+ /**
180
+ * Export metrics in Prometheus text exposition format.
181
+ * @returns {string} Prometheus-formatted metrics.
182
+ */
183
+ toPrometheus() {
184
+ const lines = [];
185
+ const metrics = this.getMetrics();
186
+
187
+ for (const metric of metrics) {
188
+ const labelStr = Object.entries(metric.labels)
189
+ .map(([k, v]) => `${k}="${v}"`)
190
+ .join(',');
191
+
192
+ if (metric.type === 'histogram' && typeof metric.value === 'object') {
193
+ lines.push(`# HELP ${metric.name} Agent Shield scan latency histogram`);
194
+ lines.push(`# TYPE ${metric.name} summary`);
195
+ lines.push(`${metric.name}{${labelStr},quantile="0.5"} ${metric.value.p50}`);
196
+ lines.push(`${metric.name}{${labelStr},quantile="0.95"} ${metric.value.p95}`);
197
+ lines.push(`${metric.name}{${labelStr},quantile="0.99"} ${metric.value.p99}`);
198
+ lines.push(`${metric.name}_count{${labelStr}} ${metric.value.count}`);
199
+ lines.push(`${metric.name}_sum{${labelStr}} ${metric.value.sum}`);
200
+ } else {
201
+ lines.push(`# HELP ${metric.name} Agent Shield metric`);
202
+ lines.push(`# TYPE ${metric.name} ${metric.type}`);
203
+ lines.push(`${metric.name}{${labelStr}} ${metric.value}`);
204
+ }
205
+ }
206
+
207
+ return lines.join('\n') + '\n';
208
+ }
209
+
210
+ /**
211
+ * Export metrics as JSON (OTLP/JSON compatible).
212
+ * @returns {Object} JSON metrics payload.
213
+ */
214
+ toJSON() {
215
+ return {
216
+ resourceMetrics: [{
217
+ resource: {
218
+ attributes: [
219
+ { key: 'service.name', value: { stringValue: this.serviceName } }
220
+ ]
221
+ },
222
+ scopeMetrics: [{
223
+ scope: { name: 'agent-shield', version: '1.0.0' },
224
+ metrics: this.getMetrics().map(m => ({
225
+ name: m.name,
226
+ description: `Agent Shield: ${m.name}`,
227
+ unit: m.name.includes('latency') ? 'ms' : '1',
228
+ [m.type]: {
229
+ dataPoints: [{
230
+ asDouble: typeof m.value === 'object' ? m.value.sum : m.value,
231
+ timeUnixNano: String(m.timestamp * 1000000),
232
+ attributes: Object.entries(m.labels).map(([k, v]) => ({
233
+ key: k,
234
+ value: { stringValue: v }
235
+ }))
236
+ }]
237
+ }
238
+ }))
239
+ }]
240
+ }]
241
+ };
242
+ }
243
+
244
+ /**
245
+ * Reset all metrics.
246
+ */
247
+ reset() {
248
+ this._scans = { total: 0, blocked: 0, latencies: [] };
249
+ this._threats = {};
250
+ this._blocks = { total: 0, contexts: [] };
251
+ this._windows = [];
252
+ this._lastFlush = Date.now();
253
+ console.log('[Agent Shield] ShieldMetrics reset.');
254
+ }
255
+ }
256
+
257
+ // =========================================================================
258
+ // Shield Tracer
259
+ // =========================================================================
260
+
261
+ class ShieldTracer {
262
+ /**
263
+ * @param {Object} [options]
264
+ * @param {string} [options.serviceName='agent-shield'] - Service name for traces.
265
+ * @param {number} [options.sampleRate=1.0] - Sampling rate (0.0 to 1.0).
266
+ */
267
+ constructor(options = {}) {
268
+ this.serviceName = options.serviceName || 'agent-shield';
269
+ this.sampleRate = options.sampleRate !== undefined ? options.sampleRate : 1.0;
270
+ this._traces = [];
271
+ this._activeSpans = new Map();
272
+ }
273
+
274
+ /**
275
+ * Generate a random hex ID of a given byte length.
276
+ * @private
277
+ * @param {number} bytes - Number of bytes.
278
+ * @returns {string} Hex string.
279
+ */
280
+ _generateId(bytes) {
281
+ return crypto.randomBytes(bytes).toString('hex');
282
+ }
283
+
284
+ /**
285
+ * Start a new span.
286
+ * @param {string} name - Span name (e.g., 'shield.scan', 'shield.detect').
287
+ * @param {Object} [attributes={}] - Span attributes.
288
+ * @returns {Object|null} Span object with {traceId, spanId, name, startTime, attributes, events, end()}, or null if not sampled.
289
+ */
290
+ startSpan(name, attributes = {}) {
291
+ if (Math.random() > this.sampleRate) {
292
+ return null;
293
+ }
294
+
295
+ const span = {
296
+ traceId: this._generateId(16),
297
+ spanId: this._generateId(8),
298
+ name,
299
+ startTime: Date.now(),
300
+ endTime: null,
301
+ attributes: { ...attributes, 'service.name': this.serviceName },
302
+ events: [],
303
+ status: 'OK',
304
+
305
+ /**
306
+ * Add an event to this span.
307
+ * @param {string} eventName - Event name.
308
+ * @param {Object} [eventAttributes={}] - Event attributes.
309
+ */
310
+ addEvent: (eventName, eventAttributes = {}) => {
311
+ span.events.push({
312
+ name: eventName,
313
+ timestamp: Date.now(),
314
+ attributes: eventAttributes
315
+ });
316
+ },
317
+
318
+ /**
319
+ * End this span.
320
+ * @param {string} [status='OK'] - Final status ('OK', 'ERROR').
321
+ */
322
+ end: (status) => {
323
+ span.endTime = Date.now();
324
+ span.status = status || 'OK';
325
+ span.durationMs = span.endTime - span.startTime;
326
+ this._activeSpans.delete(span.spanId);
327
+ this._traces.push(span);
328
+
329
+ // Keep traces bounded
330
+ if (this._traces.length > 10000) {
331
+ this._traces = this._traces.slice(-5000);
332
+ }
333
+ }
334
+ };
335
+
336
+ this._activeSpans.set(span.spanId, span);
337
+ return span;
338
+ }
339
+
340
+ /**
341
+ * Wrap a function call in a span, auto-ending on completion.
342
+ * @param {string} name - Span name.
343
+ * @param {Function} fn - Function to execute.
344
+ * @returns {*} The return value of fn.
345
+ */
346
+ withSpan(name, fn) {
347
+ const span = this.startSpan(name);
348
+
349
+ try {
350
+ const result = fn(span);
351
+
352
+ // Handle async functions
353
+ if (result && typeof result.then === 'function') {
354
+ return result
355
+ .then(val => {
356
+ if (span) span.end('OK');
357
+ return val;
358
+ })
359
+ .catch(err => {
360
+ if (span) {
361
+ span.addEvent('exception', { message: err.message });
362
+ span.end('ERROR');
363
+ }
364
+ throw err;
365
+ });
366
+ }
367
+
368
+ if (span) span.end('OK');
369
+ return result;
370
+ } catch (err) {
371
+ if (span) {
372
+ span.addEvent('exception', { message: err.message });
373
+ span.end('ERROR');
374
+ }
375
+ throw err;
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Get all completed traces.
381
+ * @returns {Array<Object>} Array of completed span objects.
382
+ */
383
+ getTraces() {
384
+ return [...this._traces];
385
+ }
386
+
387
+ /**
388
+ * Export traces in OTLP-compatible JSON format.
389
+ * @returns {Object} OTLP trace payload.
390
+ */
391
+ toOTLP() {
392
+ return {
393
+ resourceSpans: [{
394
+ resource: {
395
+ attributes: [
396
+ { key: 'service.name', value: { stringValue: this.serviceName } }
397
+ ]
398
+ },
399
+ scopeSpans: [{
400
+ scope: { name: 'agent-shield', version: '1.0.0' },
401
+ spans: this._traces.map(span => ({
402
+ traceId: span.traceId,
403
+ spanId: span.spanId,
404
+ name: span.name,
405
+ kind: 1, // INTERNAL
406
+ startTimeUnixNano: String(span.startTime * 1000000),
407
+ endTimeUnixNano: span.endTime ? String(span.endTime * 1000000) : undefined,
408
+ attributes: Object.entries(span.attributes).map(([k, v]) => ({
409
+ key: k,
410
+ value: { stringValue: String(v) }
411
+ })),
412
+ events: span.events.map(e => ({
413
+ name: e.name,
414
+ timeUnixNano: String(e.timestamp * 1000000),
415
+ attributes: Object.entries(e.attributes).map(([k, v]) => ({
416
+ key: k,
417
+ value: { stringValue: String(v) }
418
+ }))
419
+ })),
420
+ status: { code: span.status === 'OK' ? 1 : 2 }
421
+ }))
422
+ }]
423
+ }]
424
+ };
425
+ }
426
+ }
427
+
428
+ // =========================================================================
429
+ // Metrics Dashboard
430
+ // =========================================================================
431
+
432
+ class MetricsDashboard {
433
+ /**
434
+ * @param {ShieldMetrics} metrics - ShieldMetrics instance to read from.
435
+ */
436
+ constructor(metrics) {
437
+ this.metrics = metrics;
438
+ }
439
+
440
+ /**
441
+ * Return a formatted text summary of current metrics.
442
+ * @returns {string} Human-readable metrics summary.
443
+ */
444
+ summary() {
445
+ const m = this.metrics;
446
+ const uptime = ((Date.now() - m._startTime) / 1000).toFixed(0);
447
+ const blockRate = m._scans.total > 0
448
+ ? ((m._scans.blocked / m._scans.total) * 100).toFixed(1)
449
+ : '0.0';
450
+
451
+ const threatCount = Object.values(m._threats).reduce((sum, t) => sum + t.count, 0);
452
+ const percentiles = this.latencyPercentiles();
453
+
454
+ const lines = [
455
+ '=== Agent Shield Metrics Summary ===',
456
+ `Uptime: ${uptime}s`,
457
+ `Total Scans: ${m._scans.total}`,
458
+ `Blocked: ${m._scans.blocked} (${blockRate}%)`,
459
+ `Threats Detected: ${threatCount}`,
460
+ `Block Events: ${m._blocks.total}`,
461
+ ''
462
+ ];
463
+
464
+ if (percentiles) {
465
+ lines.push(`Latency p50: ${percentiles.p50.toFixed(2)}ms`);
466
+ lines.push(`Latency p95: ${percentiles.p95.toFixed(2)}ms`);
467
+ lines.push(`Latency p99: ${percentiles.p99.toFixed(2)}ms`);
468
+ lines.push('');
469
+ }
470
+
471
+ const top = this.topThreats(5);
472
+ if (top.length > 0) {
473
+ lines.push('Top Threats:');
474
+ for (const t of top) {
475
+ lines.push(` ${t.category}: ${t.count}`);
476
+ }
477
+ }
478
+
479
+ return lines.join('\n');
480
+ }
481
+
482
+ /**
483
+ * Return the top N threat categories by count.
484
+ * @param {number} [n=10] - Number of top threats to return.
485
+ * @returns {Array<Object>} Sorted array [{category, count, severities}].
486
+ */
487
+ topThreats(n = 10) {
488
+ const threats = Object.entries(this.metrics._threats).map(([category, data]) => ({
489
+ category,
490
+ count: data.count,
491
+ severities: { ...data.severities }
492
+ }));
493
+
494
+ threats.sort((a, b) => b.count - a.count);
495
+ return threats.slice(0, n);
496
+ }
497
+
498
+ /**
499
+ * Return scans-per-second over recorded time windows.
500
+ * @returns {Array<Object>} Array of [{timestamp, scansPerSec}].
501
+ */
502
+ throughputHistory() {
503
+ const windows = this.metrics._windows;
504
+ if (windows.length < 2) return [];
505
+
506
+ const result = [];
507
+ for (let i = 1; i < windows.length; i++) {
508
+ const dt = (windows[i].timestamp - windows[i - 1].timestamp) / 1000;
509
+ const dScans = windows[i].scans - windows[i - 1].scans;
510
+ result.push({
511
+ timestamp: windows[i].timestamp,
512
+ scansPerSec: dt > 0 ? dScans / dt : 0
513
+ });
514
+ }
515
+
516
+ return result;
517
+ }
518
+
519
+ /**
520
+ * Return p50, p95, p99 latency percentiles.
521
+ * @returns {Object|null} {p50, p95, p99} in ms, or null if no data.
522
+ */
523
+ latencyPercentiles() {
524
+ const latencies = this.metrics._scans.latencies;
525
+ if (latencies.length === 0) return null;
526
+
527
+ const sorted = [...latencies].sort((a, b) => a - b);
528
+ return {
529
+ p50: sorted[Math.floor(sorted.length * 0.5)],
530
+ p95: sorted[Math.floor(sorted.length * 0.95)],
531
+ p99: sorted[Math.floor(sorted.length * 0.99)]
532
+ };
533
+ }
534
+ }
535
+
536
+ // =========================================================================
537
+ // Exports
538
+ // =========================================================================
539
+
540
+ module.exports = {
541
+ ShieldMetrics,
542
+ ShieldTracer,
543
+ MetricsDashboard
544
+ };