agentshield-sdk 8.0.0 → 10.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 (51) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -21
  3. package/README.md +26 -60
  4. package/bin/agentshield-audit +51 -0
  5. package/package.json +7 -10
  6. package/src/adaptive.js +330 -330
  7. package/src/alert-tuning.js +480 -480
  8. package/src/audit-streaming.js +1 -1
  9. package/src/badges.js +196 -196
  10. package/src/behavioral-dna.js +12 -0
  11. package/src/canary.js +2 -3
  12. package/src/certification.js +563 -563
  13. package/src/circuit-breaker.js +2 -2
  14. package/src/confused-deputy.js +4 -0
  15. package/src/conversation.js +494 -494
  16. package/src/cross-turn.js +3 -17
  17. package/src/ctf.js +462 -462
  18. package/src/detector-core.js +71 -152
  19. package/src/document-scanner.js +795 -795
  20. package/src/drift-monitor.js +344 -0
  21. package/src/encoding.js +429 -429
  22. package/src/enterprise.js +405 -405
  23. package/src/flight-recorder.js +2 -0
  24. package/src/i18n-patterns.js +523 -523
  25. package/src/index.js +19 -0
  26. package/src/main.js +61 -41
  27. package/src/mcp-guard.js +974 -0
  28. package/src/micro-model.js +762 -0
  29. package/src/ml-detector.js +316 -0
  30. package/src/model-finetuning.js +884 -884
  31. package/src/multimodal.js +296 -296
  32. package/src/nist-mapping.js +2 -2
  33. package/src/observability.js +330 -330
  34. package/src/openclaw.js +450 -450
  35. package/src/otel.js +544 -544
  36. package/src/owasp-2025.js +1 -1
  37. package/src/owasp-agentic.js +420 -0
  38. package/src/plugin-marketplace.js +628 -628
  39. package/src/plugin-system.js +349 -349
  40. package/src/policy-extended.js +635 -635
  41. package/src/policy.js +443 -443
  42. package/src/prompt-leakage.js +2 -2
  43. package/src/real-attack-datasets.js +2 -2
  44. package/src/redteam-cli.js +439 -0
  45. package/src/supply-chain-scanner.js +691 -0
  46. package/src/testing.js +5 -1
  47. package/src/threat-encyclopedia.js +629 -629
  48. package/src/threat-intel-network.js +1017 -1017
  49. package/src/token-analysis.js +467 -467
  50. package/src/tool-output-validator.js +354 -354
  51. package/src/watermark.js +1 -2
package/src/enterprise.js CHANGED
@@ -1,405 +1,405 @@
1
- 'use strict';
2
-
3
- /**
4
- * Agent Shield — Enterprise Features
5
- *
6
- * - Multi-tenant support
7
- * - Role-based policies
8
- * - Debug mode with detailed traces
9
- * - Policy inheritance and overrides
10
- */
11
-
12
- const { AgentShield } = require('./index');
13
- const { loadPolicy } = require('./policy');
14
-
15
- // =========================================================================
16
- // Multi-Tenant Shield
17
- // =========================================================================
18
-
19
- class MultiTenantShield {
20
- constructor(options = {}) {
21
- this.tenants = new Map();
22
- this.defaultPolicy = options.defaultPolicy || { sensitivity: 'high', blockOnThreat: true };
23
- this.globalOverrides = options.globalOverrides || {};
24
- this.onTenantCreated = options.onTenantCreated || null;
25
- }
26
-
27
- /**
28
- * Register a tenant with its own policy.
29
- */
30
- registerTenant(tenantId, policy = {}) {
31
- const mergedPolicy = { ...this.defaultPolicy, ...policy, ...this.globalOverrides };
32
- const shield = new AgentShield(mergedPolicy);
33
-
34
- this.tenants.set(tenantId, {
35
- id: tenantId,
36
- policy: mergedPolicy,
37
- shield,
38
- stats: { scans: 0, threats: 0, blocked: 0 },
39
- createdAt: new Date().toISOString()
40
- });
41
-
42
- if (this.onTenantCreated) {
43
- this.onTenantCreated(tenantId, mergedPolicy);
44
- }
45
-
46
- return this;
47
- }
48
-
49
- /**
50
- * Get or auto-create a tenant shield.
51
- */
52
- getTenant(tenantId) {
53
- if (!this.tenants.has(tenantId)) {
54
- this.registerTenant(tenantId);
55
- }
56
- return this.tenants.get(tenantId);
57
- }
58
-
59
- /**
60
- * Scan input for a specific tenant.
61
- */
62
- scan(tenantId, text, options = {}) {
63
- const tenant = this.getTenant(tenantId);
64
- tenant.stats.scans++;
65
-
66
- const result = tenant.shield.scan(text, options);
67
-
68
- if (result.threats.length > 0) {
69
- tenant.stats.threats += result.threats.length;
70
- }
71
- if (result.blocked) {
72
- tenant.stats.blocked++;
73
- }
74
-
75
- return { ...result, tenantId };
76
- }
77
-
78
- /**
79
- * Scan input for a specific tenant.
80
- */
81
- scanInput(tenantId, text) {
82
- return this.scan(tenantId, text);
83
- }
84
-
85
- /**
86
- * Scan output for a specific tenant.
87
- */
88
- scanOutput(tenantId, text) {
89
- const tenant = this.getTenant(tenantId);
90
- return tenant.shield.scanOutput(text);
91
- }
92
-
93
- /**
94
- * Update a tenant's policy.
95
- */
96
- updatePolicy(tenantId, policy) {
97
- const tenant = this.getTenant(tenantId);
98
- tenant.policy = { ...tenant.policy, ...policy, ...this.globalOverrides };
99
- tenant.shield = new AgentShield(tenant.policy);
100
- return tenant.policy;
101
- }
102
-
103
- /**
104
- * Get stats for all tenants.
105
- */
106
- getAllStats() {
107
- const stats = {};
108
- for (const [id, tenant] of this.tenants) {
109
- stats[id] = { ...tenant.stats, policy: tenant.policy };
110
- }
111
- return stats;
112
- }
113
-
114
- /**
115
- * Remove a tenant.
116
- */
117
- removeTenant(tenantId) {
118
- return this.tenants.delete(tenantId);
119
- }
120
-
121
- /**
122
- * Get tenant count.
123
- */
124
- get size() {
125
- return this.tenants.size;
126
- }
127
- }
128
-
129
- // =========================================================================
130
- // Role-Based Policies
131
- // =========================================================================
132
-
133
- const DEFAULT_ROLES = {
134
- admin: {
135
- name: 'Administrator',
136
- sensitivity: 'medium',
137
- blockOnThreat: false,
138
- allowedTools: '*',
139
- blockedTools: [],
140
- bypassCircuitBreaker: true,
141
- canViewAuditTrail: true,
142
- canModifyPolicy: true
143
- },
144
- operator: {
145
- name: 'Operator',
146
- sensitivity: 'high',
147
- blockOnThreat: true,
148
- allowedTools: ['search', 'readFile', 'calculator'],
149
- blockedTools: ['bash', 'shell', 'exec'],
150
- bypassCircuitBreaker: false,
151
- canViewAuditTrail: true,
152
- canModifyPolicy: false
153
- },
154
- user: {
155
- name: 'Standard User',
156
- sensitivity: 'high',
157
- blockOnThreat: true,
158
- allowedTools: ['search', 'calculator'],
159
- blockedTools: ['bash', 'shell', 'exec', 'readFile', 'writeFile'],
160
- bypassCircuitBreaker: false,
161
- canViewAuditTrail: false,
162
- canModifyPolicy: false
163
- },
164
- restricted: {
165
- name: 'Restricted User',
166
- sensitivity: 'high',
167
- blockOnThreat: true,
168
- blockThreshold: 'low',
169
- allowedTools: [],
170
- blockedTools: '*',
171
- bypassCircuitBreaker: false,
172
- canViewAuditTrail: false,
173
- canModifyPolicy: false
174
- }
175
- };
176
-
177
- class RoleBasedPolicy {
178
- constructor(options = {}) {
179
- this.roles = { ...DEFAULT_ROLES, ...(options.customRoles || {}) };
180
- this.userRoles = new Map();
181
- this.shields = new Map();
182
- }
183
-
184
- /**
185
- * Assign a role to a user.
186
- */
187
- assignRole(userId, role) {
188
- if (!this.roles[role]) {
189
- throw new Error(`Unknown role: ${role}. Available: ${Object.keys(this.roles).join(', ')}`);
190
- }
191
- this.userRoles.set(userId, role);
192
-
193
- // Create a shield for this role if not exists
194
- if (!this.shields.has(role)) {
195
- const roleConfig = this.roles[role];
196
- this.shields.set(role, new AgentShield({
197
- sensitivity: roleConfig.sensitivity,
198
- blockOnThreat: roleConfig.blockOnThreat,
199
- blockThreshold: roleConfig.blockThreshold
200
- }));
201
- }
202
-
203
- return this;
204
- }
205
-
206
- /**
207
- * Get the effective policy for a user.
208
- */
209
- getPolicy(userId) {
210
- const role = this.userRoles.get(userId) || 'user';
211
- return { role, ...this.roles[role] };
212
- }
213
-
214
- /**
215
- * Scan input with the user's role-based policy.
216
- */
217
- scan(userId, text, options = {}) {
218
- const role = this.userRoles.get(userId) || 'user';
219
-
220
- // Reuse shield created in assignRole, or create lazily
221
- if (!this.shields.has(role)) {
222
- this.assignRole(userId, role);
223
- }
224
-
225
- const result = this.shields.get(role).scan(text, options);
226
- return { ...result, userId, role };
227
- }
228
-
229
- /**
230
- * Check if a user can use a specific tool.
231
- */
232
- checkToolAccess(userId, toolName) {
233
- const role = this.userRoles.get(userId) || 'user';
234
- const roleConfig = this.roles[role];
235
-
236
- if (roleConfig.blockedTools === '*') return { allowed: false, reason: 'All tools blocked for this role' };
237
- if (Array.isArray(roleConfig.blockedTools) && roleConfig.blockedTools.includes(toolName)) {
238
- return { allowed: false, reason: `Tool "${toolName}" is blocked for role "${role}"` };
239
- }
240
-
241
- if (roleConfig.allowedTools === '*') return { allowed: true };
242
- if (Array.isArray(roleConfig.allowedTools) && roleConfig.allowedTools.includes(toolName)) {
243
- return { allowed: true };
244
- }
245
-
246
- return { allowed: false, reason: `Tool "${toolName}" is not in the allowed list for role "${role}"` };
247
- }
248
-
249
- /**
250
- * Define a custom role.
251
- */
252
- defineRole(name, config) {
253
- this.roles[name] = { name: config.name || name, ...config };
254
- return this;
255
- }
256
-
257
- /**
258
- * Get all available roles.
259
- */
260
- getRoles() {
261
- return Object.entries(this.roles).map(([key, val]) => ({ key, ...val }));
262
- }
263
- }
264
-
265
- // =========================================================================
266
- // Debug Mode
267
- // =========================================================================
268
-
269
- class DebugShield {
270
- constructor(options = {}) {
271
- this.shield = new AgentShield(options);
272
- this.traces = [];
273
- this.enabled = options.debug !== false;
274
- this.maxTraces = options.maxTraces || 1000;
275
- this.verbose = options.verbose || false;
276
- }
277
-
278
- /**
279
- * Scan with full debug trace.
280
- */
281
- scan(text, options = {}) {
282
- const startTime = this.enabled ? process.hrtime.bigint() : null;
283
-
284
- // Scan
285
- const result = this.shield.scan(text, options);
286
-
287
- // Only build trace if debug is enabled
288
- let trace = null;
289
- if (this.enabled) {
290
- const endTime = process.hrtime.bigint();
291
- const elapsedMs = Number(endTime - startTime) / 1e6;
292
-
293
- trace = {
294
- id: `trace_${Date.now()}_${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}`,
295
- timestamp: new Date().toISOString(),
296
- input: text.substring(0, 500),
297
- inputLength: text.length,
298
- options,
299
- steps: [
300
- {
301
- step: 'input_received',
302
- time: 0,
303
- detail: { length: text.length, hasUnicode: /[^\x00-\x7F]/.test(text) }
304
- },
305
- {
306
- step: 'pattern_matching',
307
- time: elapsedMs,
308
- detail: {
309
- patternsChecked: this.shield.getPatterns().length,
310
- threatsFound: result.threats.length,
311
- threats: result.threats.map(t => ({
312
- severity: t.severity,
313
- category: t.category,
314
- description: t.description,
315
- confidence: t.confidence
316
- }))
317
- }
318
- },
319
- {
320
- step: 'decision',
321
- time: elapsedMs,
322
- detail: {
323
- status: result.status,
324
- blocked: result.blocked,
325
- threatCount: result.threats.length
326
- }
327
- }
328
- ],
329
- totalTimeMs: parseFloat(elapsedMs.toFixed(3)),
330
- result: {
331
- status: result.status,
332
- blocked: result.blocked,
333
- threatCount: result.threats.length
334
- }
335
- };
336
-
337
- this.traces.push(trace);
338
- while (this.traces.length > this.maxTraces) {
339
- this.traces.shift();
340
- }
341
- }
342
-
343
- if (this.verbose) {
344
- const ms = trace ? trace.totalTimeMs : 0;
345
- console.log(`[Agent Shield] DEBUG Scan: ${text.substring(0, 50)}... → ${result.status} (${ms.toFixed(1)}ms, ${result.threats.length} threats)`);
346
- }
347
-
348
- return { ...result, _trace: trace };
349
- }
350
-
351
- /**
352
- * Get all traces.
353
- */
354
- getTraces() {
355
- return this.traces;
356
- }
357
-
358
- /**
359
- * Get the last N traces.
360
- */
361
- getRecentTraces(n = 10) {
362
- return this.traces.slice(-n);
363
- }
364
-
365
- /**
366
- * Export traces as JSON.
367
- */
368
- exportTraces() {
369
- return JSON.stringify(this.traces, null, 2);
370
- }
371
-
372
- /**
373
- * Clear traces.
374
- */
375
- clearTraces() {
376
- this.traces = [];
377
- }
378
-
379
- /**
380
- * Get timing statistics across all traces.
381
- */
382
- getTimingStats() {
383
- if (this.traces.length === 0) return null;
384
-
385
- const times = this.traces.map(t => t.totalTimeMs);
386
- times.sort((a, b) => a - b);
387
-
388
- return {
389
- count: times.length,
390
- min: times[0],
391
- max: times[times.length - 1],
392
- avg: parseFloat((times.reduce((a, b) => a + b, 0) / times.length).toFixed(3)),
393
- median: times[Math.floor(times.length / 2)],
394
- p95: times[Math.min(Math.floor(times.length * 0.95), times.length - 1)],
395
- p99: times[Math.min(Math.floor(times.length * 0.99), times.length - 1)]
396
- };
397
- }
398
- }
399
-
400
- module.exports = {
401
- MultiTenantShield,
402
- RoleBasedPolicy,
403
- DebugShield,
404
- DEFAULT_ROLES
405
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Enterprise Features
5
+ *
6
+ * - Multi-tenant support
7
+ * - Role-based policies
8
+ * - Debug mode with detailed traces
9
+ * - Policy inheritance and overrides
10
+ */
11
+
12
+ const { AgentShield } = require('./index');
13
+ const { loadPolicy } = require('./policy');
14
+
15
+ // =========================================================================
16
+ // Multi-Tenant Shield
17
+ // =========================================================================
18
+
19
+ class MultiTenantShield {
20
+ constructor(options = {}) {
21
+ this.tenants = new Map();
22
+ this.defaultPolicy = options.defaultPolicy || { sensitivity: 'high', blockOnThreat: true };
23
+ this.globalOverrides = options.globalOverrides || {};
24
+ this.onTenantCreated = options.onTenantCreated || null;
25
+ }
26
+
27
+ /**
28
+ * Register a tenant with its own policy.
29
+ */
30
+ registerTenant(tenantId, policy = {}) {
31
+ const mergedPolicy = { ...this.defaultPolicy, ...policy, ...this.globalOverrides };
32
+ const shield = new AgentShield(mergedPolicy);
33
+
34
+ this.tenants.set(tenantId, {
35
+ id: tenantId,
36
+ policy: mergedPolicy,
37
+ shield,
38
+ stats: { scans: 0, threats: 0, blocked: 0 },
39
+ createdAt: new Date().toISOString()
40
+ });
41
+
42
+ if (this.onTenantCreated) {
43
+ this.onTenantCreated(tenantId, mergedPolicy);
44
+ }
45
+
46
+ return this;
47
+ }
48
+
49
+ /**
50
+ * Get or auto-create a tenant shield.
51
+ */
52
+ getTenant(tenantId) {
53
+ if (!this.tenants.has(tenantId)) {
54
+ this.registerTenant(tenantId);
55
+ }
56
+ return this.tenants.get(tenantId);
57
+ }
58
+
59
+ /**
60
+ * Scan input for a specific tenant.
61
+ */
62
+ scan(tenantId, text, options = {}) {
63
+ const tenant = this.getTenant(tenantId);
64
+ tenant.stats.scans++;
65
+
66
+ const result = tenant.shield.scan(text, options);
67
+
68
+ if (result.threats.length > 0) {
69
+ tenant.stats.threats += result.threats.length;
70
+ }
71
+ if (result.blocked) {
72
+ tenant.stats.blocked++;
73
+ }
74
+
75
+ return { ...result, tenantId };
76
+ }
77
+
78
+ /**
79
+ * Scan input for a specific tenant.
80
+ */
81
+ scanInput(tenantId, text) {
82
+ return this.scan(tenantId, text);
83
+ }
84
+
85
+ /**
86
+ * Scan output for a specific tenant.
87
+ */
88
+ scanOutput(tenantId, text) {
89
+ const tenant = this.getTenant(tenantId);
90
+ return tenant.shield.scanOutput(text);
91
+ }
92
+
93
+ /**
94
+ * Update a tenant's policy.
95
+ */
96
+ updatePolicy(tenantId, policy) {
97
+ const tenant = this.getTenant(tenantId);
98
+ tenant.policy = { ...tenant.policy, ...policy, ...this.globalOverrides };
99
+ tenant.shield = new AgentShield(tenant.policy);
100
+ return tenant.policy;
101
+ }
102
+
103
+ /**
104
+ * Get stats for all tenants.
105
+ */
106
+ getAllStats() {
107
+ const stats = {};
108
+ for (const [id, tenant] of this.tenants) {
109
+ stats[id] = { ...tenant.stats, policy: tenant.policy };
110
+ }
111
+ return stats;
112
+ }
113
+
114
+ /**
115
+ * Remove a tenant.
116
+ */
117
+ removeTenant(tenantId) {
118
+ return this.tenants.delete(tenantId);
119
+ }
120
+
121
+ /**
122
+ * Get tenant count.
123
+ */
124
+ get size() {
125
+ return this.tenants.size;
126
+ }
127
+ }
128
+
129
+ // =========================================================================
130
+ // Role-Based Policies
131
+ // =========================================================================
132
+
133
+ const DEFAULT_ROLES = {
134
+ admin: {
135
+ name: 'Administrator',
136
+ sensitivity: 'medium',
137
+ blockOnThreat: false,
138
+ allowedTools: '*',
139
+ blockedTools: [],
140
+ bypassCircuitBreaker: true,
141
+ canViewAuditTrail: true,
142
+ canModifyPolicy: true
143
+ },
144
+ operator: {
145
+ name: 'Operator',
146
+ sensitivity: 'high',
147
+ blockOnThreat: true,
148
+ allowedTools: ['search', 'readFile', 'calculator'],
149
+ blockedTools: ['bash', 'shell', 'exec'],
150
+ bypassCircuitBreaker: false,
151
+ canViewAuditTrail: true,
152
+ canModifyPolicy: false
153
+ },
154
+ user: {
155
+ name: 'Standard User',
156
+ sensitivity: 'high',
157
+ blockOnThreat: true,
158
+ allowedTools: ['search', 'calculator'],
159
+ blockedTools: ['bash', 'shell', 'exec', 'readFile', 'writeFile'],
160
+ bypassCircuitBreaker: false,
161
+ canViewAuditTrail: false,
162
+ canModifyPolicy: false
163
+ },
164
+ restricted: {
165
+ name: 'Restricted User',
166
+ sensitivity: 'high',
167
+ blockOnThreat: true,
168
+ blockThreshold: 'low',
169
+ allowedTools: [],
170
+ blockedTools: '*',
171
+ bypassCircuitBreaker: false,
172
+ canViewAuditTrail: false,
173
+ canModifyPolicy: false
174
+ }
175
+ };
176
+
177
+ class RoleBasedPolicy {
178
+ constructor(options = {}) {
179
+ this.roles = { ...DEFAULT_ROLES, ...(options.customRoles || {}) };
180
+ this.userRoles = new Map();
181
+ this.shields = new Map();
182
+ }
183
+
184
+ /**
185
+ * Assign a role to a user.
186
+ */
187
+ assignRole(userId, role) {
188
+ if (!this.roles[role]) {
189
+ throw new Error(`Unknown role: ${role}. Available: ${Object.keys(this.roles).join(', ')}`);
190
+ }
191
+ this.userRoles.set(userId, role);
192
+
193
+ // Create a shield for this role if not exists
194
+ if (!this.shields.has(role)) {
195
+ const roleConfig = this.roles[role];
196
+ this.shields.set(role, new AgentShield({
197
+ sensitivity: roleConfig.sensitivity,
198
+ blockOnThreat: roleConfig.blockOnThreat,
199
+ blockThreshold: roleConfig.blockThreshold
200
+ }));
201
+ }
202
+
203
+ return this;
204
+ }
205
+
206
+ /**
207
+ * Get the effective policy for a user.
208
+ */
209
+ getPolicy(userId) {
210
+ const role = this.userRoles.get(userId) || 'user';
211
+ return { role, ...this.roles[role] };
212
+ }
213
+
214
+ /**
215
+ * Scan input with the user's role-based policy.
216
+ */
217
+ scan(userId, text, options = {}) {
218
+ const role = this.userRoles.get(userId) || 'user';
219
+
220
+ // Reuse shield created in assignRole, or create lazily
221
+ if (!this.shields.has(role)) {
222
+ this.assignRole(userId, role);
223
+ }
224
+
225
+ const result = this.shields.get(role).scan(text, options);
226
+ return { ...result, userId, role };
227
+ }
228
+
229
+ /**
230
+ * Check if a user can use a specific tool.
231
+ */
232
+ checkToolAccess(userId, toolName) {
233
+ const role = this.userRoles.get(userId) || 'user';
234
+ const roleConfig = this.roles[role];
235
+
236
+ if (roleConfig.blockedTools === '*') return { allowed: false, reason: 'All tools blocked for this role' };
237
+ if (Array.isArray(roleConfig.blockedTools) && roleConfig.blockedTools.includes(toolName)) {
238
+ return { allowed: false, reason: `Tool "${toolName}" is blocked for role "${role}"` };
239
+ }
240
+
241
+ if (roleConfig.allowedTools === '*') return { allowed: true };
242
+ if (Array.isArray(roleConfig.allowedTools) && roleConfig.allowedTools.includes(toolName)) {
243
+ return { allowed: true };
244
+ }
245
+
246
+ return { allowed: false, reason: `Tool "${toolName}" is not in the allowed list for role "${role}"` };
247
+ }
248
+
249
+ /**
250
+ * Define a custom role.
251
+ */
252
+ defineRole(name, config) {
253
+ this.roles[name] = { name: config.name || name, ...config };
254
+ return this;
255
+ }
256
+
257
+ /**
258
+ * Get all available roles.
259
+ */
260
+ getRoles() {
261
+ return Object.entries(this.roles).map(([key, val]) => ({ key, ...val }));
262
+ }
263
+ }
264
+
265
+ // =========================================================================
266
+ // Debug Mode
267
+ // =========================================================================
268
+
269
+ class DebugShield {
270
+ constructor(options = {}) {
271
+ this.shield = new AgentShield(options);
272
+ this.traces = [];
273
+ this.enabled = options.debug !== false;
274
+ this.maxTraces = options.maxTraces || 1000;
275
+ this.verbose = options.verbose || false;
276
+ }
277
+
278
+ /**
279
+ * Scan with full debug trace.
280
+ */
281
+ scan(text, options = {}) {
282
+ const startTime = this.enabled ? process.hrtime.bigint() : null;
283
+
284
+ // Scan
285
+ const result = this.shield.scan(text, options);
286
+
287
+ // Only build trace if debug is enabled
288
+ let trace = null;
289
+ if (this.enabled) {
290
+ const endTime = process.hrtime.bigint();
291
+ const elapsedMs = Number(endTime - startTime) / 1e6;
292
+
293
+ trace = {
294
+ id: `trace_${Date.now()}_${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}`,
295
+ timestamp: new Date().toISOString(),
296
+ input: text.substring(0, 500),
297
+ inputLength: text.length,
298
+ options,
299
+ steps: [
300
+ {
301
+ step: 'input_received',
302
+ time: 0,
303
+ detail: { length: text.length, hasUnicode: /[^\x00-\x7F]/.test(text) }
304
+ },
305
+ {
306
+ step: 'pattern_matching',
307
+ time: elapsedMs,
308
+ detail: {
309
+ patternsChecked: this.shield.getPatterns().length,
310
+ threatsFound: result.threats.length,
311
+ threats: result.threats.map(t => ({
312
+ severity: t.severity,
313
+ category: t.category,
314
+ description: t.description,
315
+ confidence: t.confidence
316
+ }))
317
+ }
318
+ },
319
+ {
320
+ step: 'decision',
321
+ time: elapsedMs,
322
+ detail: {
323
+ status: result.status,
324
+ blocked: result.blocked,
325
+ threatCount: result.threats.length
326
+ }
327
+ }
328
+ ],
329
+ totalTimeMs: parseFloat(elapsedMs.toFixed(3)),
330
+ result: {
331
+ status: result.status,
332
+ blocked: result.blocked,
333
+ threatCount: result.threats.length
334
+ }
335
+ };
336
+
337
+ this.traces.push(trace);
338
+ while (this.traces.length > this.maxTraces) {
339
+ this.traces.shift();
340
+ }
341
+ }
342
+
343
+ if (this.verbose) {
344
+ const ms = trace ? trace.totalTimeMs : 0;
345
+ console.log(`[Agent Shield] DEBUG Scan: ${text.substring(0, 50)}... → ${result.status} (${ms.toFixed(1)}ms, ${result.threats.length} threats)`);
346
+ }
347
+
348
+ return { ...result, _trace: trace };
349
+ }
350
+
351
+ /**
352
+ * Get all traces.
353
+ */
354
+ getTraces() {
355
+ return this.traces;
356
+ }
357
+
358
+ /**
359
+ * Get the last N traces.
360
+ */
361
+ getRecentTraces(n = 10) {
362
+ return this.traces.slice(-n);
363
+ }
364
+
365
+ /**
366
+ * Export traces as JSON.
367
+ */
368
+ exportTraces() {
369
+ return JSON.stringify(this.traces, null, 2);
370
+ }
371
+
372
+ /**
373
+ * Clear traces.
374
+ */
375
+ clearTraces() {
376
+ this.traces = [];
377
+ }
378
+
379
+ /**
380
+ * Get timing statistics across all traces.
381
+ */
382
+ getTimingStats() {
383
+ if (this.traces.length === 0) return null;
384
+
385
+ const times = this.traces.map(t => t.totalTimeMs);
386
+ times.sort((a, b) => a - b);
387
+
388
+ return {
389
+ count: times.length,
390
+ min: times[0],
391
+ max: times[times.length - 1],
392
+ avg: parseFloat((times.reduce((a, b) => a + b, 0) / times.length).toFixed(3)),
393
+ median: times[Math.floor(times.length / 2)],
394
+ p95: times[Math.min(Math.floor(times.length * 0.95), times.length - 1)],
395
+ p99: times[Math.min(Math.floor(times.length * 0.99), times.length - 1)]
396
+ };
397
+ }
398
+ }
399
+
400
+ module.exports = {
401
+ MultiTenantShield,
402
+ RoleBasedPolicy,
403
+ DebugShield,
404
+ DEFAULT_ROLES
405
+ };