@vpdeva/blackwall-llm-shield-js 0.1.8 → 0.2.3

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/README.md CHANGED
@@ -26,9 +26,10 @@ JavaScript security middleware for LLM applications in Node.js and Next.js. Blac
26
26
 
27
27
  ```bash
28
28
  npm install @vpdeva/blackwall-llm-shield-js
29
- npm install @xenova/transformers
30
29
  ```
31
30
 
31
+ The package now ships with local Transformers support wired as a first-class dependency, so teams do not need a second install step just to enable semantic scoring.
32
+
32
33
  ## Fast Start
33
34
 
34
35
  ```js
@@ -89,6 +90,8 @@ The current recommendation for enterprise teams is a controlled pilot first: sta
89
90
 
90
91
  Use `summarizeOperationalTelemetry()` with emitted telemetry events when you want route-level summaries, blocked-event counts, and rollout visibility for operators.
91
92
 
93
+ Enterprise deployments can also enrich emitted events with SSO/user context and forward flattened records to Power BI or other downstream reporting systems.
94
+
92
95
  ### Output grounding and tone review
93
96
 
94
97
  `OutputFirewall` can compare responses against retrieved documents and flag hallucination-style unsupported claims or unprofessional tone.
@@ -119,13 +122,15 @@ Use it after the model responds to catch leaked secrets, dangerous code patterns
119
122
 
120
123
  Use it to allowlist tools, block disallowed tools, validate arguments, and require approval for risky operations.
121
124
 
125
+ It can also integrate with `ValueAtRiskCircuitBreaker` for high-value actions and `ShadowConsensusAuditor` for secondary logic review before sensitive tools execute.
126
+
122
127
  ### `RetrievalSanitizer`
123
128
 
124
129
  Use it before injecting retrieved documents into context so hostile instructions in your RAG data store do not quietly become model instructions.
125
130
 
126
131
  ### Contract Stability
127
132
 
128
- The 0.1.x line treats `guardModelRequest()`, `protectWithAdapter()`, `reviewModelResponse()`, `ToolPermissionFirewall`, and `RetrievalSanitizer` as the long-term integration contracts. The exported `CORE_INTERFACES` map can be logged or asserted by applications that want to pin expected behavior.
133
+ The 0.2.x line treats `guardModelRequest()`, `protectWithAdapter()`, `reviewModelResponse()`, `ToolPermissionFirewall`, and `RetrievalSanitizer` as the long-term integration contracts. The exported `CORE_INTERFACES` map can be logged or asserted by applications that want to pin expected behavior.
129
134
 
130
135
  Recommended presets:
131
136
 
@@ -137,11 +142,36 @@ Recommended presets:
137
142
  - `documentReview` for classification and document-review pipelines
138
143
  - `ragSearch` for search-heavy retrieval endpoints
139
144
  - `toolCalling` for routes that broker external actions
145
+ - `governmentStrict` for highly regulated public-sector and records-sensitive workflows
146
+ - `bankingPayments` for high-value payment and financial action routes
147
+ - `documentIntake` for upload-heavy intake and review flows
148
+ - `citizenServices` for identity-aware service delivery workflows
149
+ - `internalOpsAgent` for internal operational assistants with shadow-first defaults
150
+
151
+ ### Global Governance Pack
152
+
153
+ The 0.2.2 line also adds globally applicable enterprise controls that are useful across regulated industries, not just one country or sector:
154
+
155
+ - `DataClassificationGate` to classify traffic as `public`, `internal`, `confidential`, or `restricted`
156
+ - `ProviderRoutingPolicy` to keep sensitive classes on approved providers
157
+ - `ApprovalInboxModel` and `UploadQuarantineWorkflow` for quarantine and review-first intake
158
+ - `buildComplianceEventBundle()` and `sanitizeAuditEvent()` for audit-safe event export
159
+ - `RetrievalTrustScorer` and `OutboundCommunicationGuard` for retrieval trust and outbound checks
160
+ - `detectOperationalDrift()` for release-over-release noise monitoring
140
161
 
141
162
  ### `AuditTrail`
142
163
 
143
164
  Use it to record signed events, summarize security activity, and power dashboards or downstream analysis.
144
165
 
166
+ ### Advanced Agent Controls
167
+
168
+ - `ValueAtRiskCircuitBreaker` for financial or high-value operational actions
169
+ - `ShadowConsensusAuditor` for second-model or secondary-review logic conflict checks
170
+ - `CrossModelConsensusWrapper` for automatic cross-model verification of high-impact actions
171
+ - `DigitalTwinOrchestrator` for mock tool environments and sandbox simulations
172
+ - `PolicyLearningLoop` plus `suggestPolicyOverride()` for narrow false-positive tuning suggestions after HITL approvals
173
+ - `AgentIdentityRegistry.issueSignedPassport()` and `issuePassportToken()` for signed agent identity exchange
174
+
145
175
  ## Example Workflows
146
176
 
147
177
  ### Guard a request before calling the model
@@ -201,6 +231,64 @@ function createModelShield(shield) {
201
231
  }
202
232
  ```
203
233
 
234
+ ### Add SSO-aware telemetry and Power BI export
235
+
236
+ ```js
237
+ const { BlackwallShield, PowerBIExporter } = require('@vpdeva/blackwall-llm-shield-js');
238
+
239
+ const shield = new BlackwallShield({
240
+ identityResolver: (metadata) => ({
241
+ userId: metadata.sso?.subject,
242
+ userEmail: metadata.sso?.email,
243
+ userName: metadata.sso?.displayName,
244
+ identityProvider: metadata.sso?.provider,
245
+ groups: metadata.sso?.groups,
246
+ }),
247
+ telemetryExporters: [
248
+ new PowerBIExporter({ endpointUrl: process.env.POWER_BI_PUSH_URL }),
249
+ ],
250
+ });
251
+ ```
252
+
253
+ ### Protect high-value actions with a VaR breaker and consensus auditor
254
+
255
+ ```js
256
+ const firewall = new ToolPermissionFirewall({
257
+ allowedTools: ['issueRefund'],
258
+ valueAtRiskCircuitBreaker: new ValueAtRiskCircuitBreaker({ maxValuePerWindow: 5000 }),
259
+ consensusAuditor: new ShadowConsensusAuditor(),
260
+ consensusRequiredFor: ['issueRefund'],
261
+ });
262
+ ```
263
+
264
+ ### Add automatic cross-model consensus
265
+
266
+ ```js
267
+ const consensus = new CrossModelConsensusWrapper({
268
+ auditorAdapter: geminiAuditorAdapter,
269
+ });
270
+
271
+ const firewall = new ToolPermissionFirewall({
272
+ allowedTools: ['issueRefund'],
273
+ crossModelConsensus: consensus,
274
+ consensusRequiredFor: ['issueRefund'],
275
+ });
276
+ ```
277
+
278
+ ### Generate a digital twin for sandbox testing
279
+
280
+ ```js
281
+ const twin = new DigitalTwinOrchestrator({
282
+ toolSchemas: [
283
+ { name: 'lookupOrder', mockResponse: { orderId: 'ord_1', status: 'mocked' } },
284
+ ],
285
+ }).generate();
286
+
287
+ await twin.simulateCall('lookupOrder', { orderId: 'ord_1' });
288
+ ```
289
+
290
+ You can also derive a digital twin from `ToolPermissionFirewall` tool schemas with `DigitalTwinOrchestrator.fromToolPermissionFirewall(firewall)`.
291
+
204
292
  ### Protect a strict JSON workflow
205
293
 
206
294
  ```js
@@ -319,6 +407,8 @@ const { summarizeOperationalTelemetry } = require('@vpdeva/blackwall-llm-shield-
319
407
  const summary = summarizeOperationalTelemetry(events);
320
408
  console.log(summary.byRoute);
321
409
  console.log(summary.byFeature);
410
+ console.log(summary.byUser);
411
+ console.log(summary.byIdentityProvider);
322
412
  console.log(summary.noisiestRoutes);
323
413
  console.log(summary.weeklyBlockEstimate);
324
414
  console.log(summary.highestSeverity);
@@ -372,6 +462,8 @@ For Gemini-heavy apps, the bundled adapter now preserves system instructions plu
372
462
  - A controlled pilot is a good fit today when you want shadow-mode prompt and output protection without forcing hard blocking on every route immediately.
373
463
  - If you prefer not to depend on Blackwall directly everywhere, wrap it behind your own internal model-security abstraction and expose only the contract your app teams need.
374
464
  - For broader approval, focus rollout reviews on false-positive rates, noisiest routes, and latency budgets alongside jailbreak coverage.
465
+ - For executive or staff-facing workflows, always attach authenticated identity metadata so telemetry can answer which user triggered which risky request or output event.
466
+ - For high-impact agentic workflows, combine tool approval, VaR limits, digital-twin tests, and signed agent passports instead of relying on a single detector.
375
467
 
376
468
  ## Release Commands
377
469
 
package/index.d.ts CHANGED
@@ -55,6 +55,8 @@ export interface ShieldOptions {
55
55
  routePolicies?: Array<{ route: string | RegExp | ((route: string, metadata: Record<string, unknown>) => boolean); options: Record<string, unknown> }>;
56
56
  customPromptDetectors?: Array<(payload: Record<string, unknown>) => Record<string, unknown> | Array<Record<string, unknown>> | null>;
57
57
  onTelemetry?: (event: Record<string, unknown>) => void | Promise<void>;
58
+ telemetryExporters?: Array<{ send(events: Array<Record<string, unknown>>): unknown }>;
59
+ identityResolver?: (metadata: Record<string, unknown>) => Record<string, unknown> | null;
58
60
  [key: string]: unknown;
59
61
  }
60
62
 
@@ -79,6 +81,66 @@ export class ToolPermissionFirewall {
79
81
  inspectCallAsync?(input: Record<string, unknown>): Promise<Record<string, unknown>>;
80
82
  }
81
83
 
84
+ export class ValueAtRiskCircuitBreaker {
85
+ constructor(options?: Record<string, unknown>);
86
+ inspect(input?: Record<string, unknown>): Record<string, unknown>;
87
+ revokeSession(sessionId: string, durationMs?: number): Record<string, unknown> | null;
88
+ }
89
+
90
+ export class ShadowConsensusAuditor {
91
+ constructor(options?: Record<string, unknown>);
92
+ inspect(input?: Record<string, unknown>): Record<string, unknown>;
93
+ }
94
+
95
+ export class CrossModelConsensusWrapper {
96
+ constructor(options?: Record<string, unknown>);
97
+ evaluate(input?: Record<string, unknown>): Promise<Record<string, unknown>> | Record<string, unknown>;
98
+ }
99
+
100
+ export class DigitalTwinOrchestrator {
101
+ constructor(options?: Record<string, unknown>);
102
+ generate(): Record<string, unknown>;
103
+ static fromToolPermissionFirewall(firewall: unknown): DigitalTwinOrchestrator;
104
+ }
105
+
106
+ export class DataClassificationGate {
107
+ constructor(options?: Record<string, unknown>);
108
+ classify(input?: Record<string, unknown>): string;
109
+ inspect(input?: Record<string, unknown>): Record<string, unknown>;
110
+ }
111
+
112
+ export class ProviderRoutingPolicy {
113
+ constructor(options?: Record<string, unknown>);
114
+ choose(input?: Record<string, unknown>): Record<string, unknown>;
115
+ }
116
+
117
+ export class ApprovalInboxModel {
118
+ constructor(options?: Record<string, unknown>);
119
+ createRequest(input?: Record<string, unknown>): Record<string, unknown>;
120
+ approve(id: string, approver: string): Record<string, unknown> | null;
121
+ summarize(): Record<string, unknown>;
122
+ }
123
+
124
+ export class RetrievalTrustScorer {
125
+ score(documents?: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
126
+ }
127
+
128
+ export class OutboundCommunicationGuard {
129
+ constructor(options?: Record<string, unknown>);
130
+ inspect(input?: Record<string, unknown>): Record<string, unknown>;
131
+ }
132
+
133
+ export class UploadQuarantineWorkflow {
134
+ constructor(options?: Record<string, unknown>);
135
+ inspectUpload(input?: Record<string, unknown>): Promise<Record<string, unknown>>;
136
+ }
137
+
138
+ export class PolicyLearningLoop {
139
+ constructor();
140
+ recordDecision(input?: Record<string, unknown>): Record<string, unknown> | null;
141
+ suggestOverrides(): Array<Record<string, unknown>>;
142
+ }
143
+
82
144
  export class RetrievalSanitizer {
83
145
  constructor(options?: Record<string, unknown>);
84
146
  sanitizeDocuments(documents: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
@@ -97,6 +159,17 @@ export const POLICY_PACKS: Record<string, Record<string, unknown>>;
97
159
  export function buildShieldOptions(options?: Record<string, unknown>): Record<string, unknown>;
98
160
  export function summarizeOperationalTelemetry(events?: Array<Record<string, unknown>>): Record<string, unknown>;
99
161
  export function parseJsonOutput(output: unknown): unknown;
162
+ export function normalizeIdentityMetadata(metadata?: Record<string, unknown>, resolver?: ((metadata: Record<string, unknown>) => Record<string, unknown> | null) | null): Record<string, unknown>;
163
+ export function buildEnterpriseTelemetryEvent(event?: Record<string, unknown>, resolver?: ((metadata: Record<string, unknown>) => Record<string, unknown> | null) | null): Record<string, unknown>;
164
+ export function buildPowerBIRecord(event?: Record<string, unknown>): Record<string, unknown>;
165
+ export function buildComplianceEventBundle(event?: Record<string, unknown>): Record<string, unknown>;
166
+ export function sanitizeAuditEvent(event?: Record<string, unknown>, options?: Record<string, unknown>): Record<string, unknown>;
167
+ export function detectOperationalDrift(previousSummary?: Record<string, unknown>, currentSummary?: Record<string, unknown>): Record<string, unknown>;
168
+ export function suggestPolicyOverride(input?: Record<string, unknown>): Record<string, unknown> | null;
169
+ export class PowerBIExporter {
170
+ constructor(options?: Record<string, unknown>);
171
+ send(events?: Array<Record<string, unknown>> | Record<string, unknown>): Promise<Array<Record<string, unknown>>>;
172
+ }
100
173
 
101
174
  export function createOpenAIAdapter(input: Record<string, unknown>): ProviderAdapter;
102
175
  export function createAnthropicAdapter(input: Record<string, unknown>): ProviderAdapter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vpdeva/blackwall-llm-shield-js",
3
- "version": "0.1.8",
3
+ "version": "0.2.3",
4
4
  "description": "Open-source JavaScript enterprise LLM protection toolkit for Node.js and Next.js",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Vish <hello@vish.au> (https://vish.au)",
@@ -71,6 +71,9 @@
71
71
  "nextjs",
72
72
  "node"
73
73
  ],
74
+ "dependencies": {
75
+ "@xenova/transformers": "^2.17.2"
76
+ },
74
77
  "devDependencies": {
75
78
  "@changesets/cli": "^2.29.6"
76
79
  }
package/src/index.js CHANGED
@@ -173,6 +173,41 @@ const SHIELD_PRESETS = {
173
173
  shadowMode: false,
174
174
  policyPack: 'finance',
175
175
  },
176
+ governmentStrict: {
177
+ blockOnPromptInjection: true,
178
+ promptInjectionThreshold: 'medium',
179
+ notifyOnRiskLevel: 'medium',
180
+ shadowMode: false,
181
+ policyPack: 'government',
182
+ },
183
+ bankingPayments: {
184
+ blockOnPromptInjection: true,
185
+ promptInjectionThreshold: 'medium',
186
+ notifyOnRiskLevel: 'medium',
187
+ shadowMode: false,
188
+ policyPack: 'finance',
189
+ },
190
+ documentIntake: {
191
+ blockOnPromptInjection: true,
192
+ promptInjectionThreshold: 'high',
193
+ notifyOnRiskLevel: 'medium',
194
+ shadowMode: true,
195
+ policyPack: 'government',
196
+ },
197
+ citizenServices: {
198
+ blockOnPromptInjection: true,
199
+ promptInjectionThreshold: 'medium',
200
+ notifyOnRiskLevel: 'medium',
201
+ shadowMode: true,
202
+ policyPack: 'government',
203
+ },
204
+ internalOpsAgent: {
205
+ blockOnPromptInjection: true,
206
+ promptInjectionThreshold: 'medium',
207
+ notifyOnRiskLevel: 'medium',
208
+ shadowMode: true,
209
+ policyPack: 'finance',
210
+ },
176
211
  };
177
212
 
178
213
  const CORE_INTERFACE_VERSION = '1.0';
@@ -372,6 +407,250 @@ function createTelemetryEvent(type, payload = {}) {
372
407
  };
373
408
  }
374
409
 
410
+ function normalizeIdentityMetadata(metadata = {}, resolver = null) {
411
+ const resolved = typeof resolver === 'function' ? resolver(metadata) || {} : {};
412
+ const source = { ...metadata, ...resolved };
413
+ const groups = Array.isArray(source.groups)
414
+ ? source.groups
415
+ : (Array.isArray(source.ssoGroups) ? source.ssoGroups : (typeof source.groups === 'string' ? source.groups.split(',').map((item) => item.trim()).filter(Boolean) : []));
416
+ return {
417
+ userId: source.userId || source.user_id || source.subject || source.sub || 'anonymous',
418
+ userEmail: source.userEmail || source.user_email || source.email || source.upn || null,
419
+ userName: source.userName || source.user_name || source.displayName || source.display_name || source.name || null,
420
+ tenantId: source.tenantId || source.tenant_id || source.orgId || source.org_id || 'default',
421
+ identityProvider: source.identityProvider || source.identity_provider || source.ssoProvider || source.sso_provider || source.idp || null,
422
+ authMethod: source.authMethod || source.auth_method || source.authType || source.auth_type || null,
423
+ sessionId: source.sessionId || source.session_id || null,
424
+ groups,
425
+ };
426
+ }
427
+
428
+ function buildEnterpriseTelemetryEvent(event = {}, resolver = null) {
429
+ const metadata = event && event.metadata ? event.metadata : {};
430
+ const actor = normalizeIdentityMetadata(metadata, resolver);
431
+ return {
432
+ ...event,
433
+ actor,
434
+ metadata: {
435
+ ...metadata,
436
+ userId: metadata.userId || metadata.user_id || actor.userId,
437
+ tenantId: metadata.tenantId || metadata.tenant_id || actor.tenantId,
438
+ identityProvider: metadata.identityProvider || metadata.identity_provider || actor.identityProvider,
439
+ sessionId: metadata.sessionId || metadata.session_id || actor.sessionId,
440
+ },
441
+ };
442
+ }
443
+
444
+ function buildPowerBIRecord(event = {}) {
445
+ const actor = event.actor || normalizeIdentityMetadata(event.metadata || {});
446
+ const metadata = event.metadata || {};
447
+ return {
448
+ eventType: event.type || 'unknown',
449
+ createdAt: event.createdAt || new Date().toISOString(),
450
+ route: metadata.route || metadata.path || 'unknown',
451
+ feature: metadata.feature || metadata.capability || metadata.route || 'unknown',
452
+ model: metadata.model || metadata.modelName || 'unknown',
453
+ tenantId: actor.tenantId || 'default',
454
+ userId: actor.userId || 'anonymous',
455
+ userEmail: actor.userEmail || null,
456
+ userName: actor.userName || null,
457
+ identityProvider: actor.identityProvider || null,
458
+ authMethod: actor.authMethod || null,
459
+ sessionId: actor.sessionId || null,
460
+ blocked: !!event.blocked,
461
+ shadowMode: !!event.shadowMode,
462
+ severity: (event.report && event.report.outputReview && event.report.outputReview.severity)
463
+ || (event.report && event.report.promptInjection && event.report.promptInjection.level)
464
+ || 'low',
465
+ topRule: (event.report && event.report.promptInjection && Array.isArray(event.report.promptInjection.matches) && event.report.promptInjection.matches[0] && event.report.promptInjection.matches[0].id) || null,
466
+ };
467
+ }
468
+
469
+ class PowerBIExporter {
470
+ constructor(options = {}) {
471
+ this.endpointUrl = options.endpointUrl || options.webhookUrl || null;
472
+ this.fetchImpl = options.fetchImpl || (typeof fetch === 'function' ? fetch : null);
473
+ }
474
+
475
+ async send(events = []) {
476
+ const records = (Array.isArray(events) ? events : [events]).filter(Boolean).map((event) => buildPowerBIRecord(event));
477
+ if (!this.endpointUrl || !this.fetchImpl) return records;
478
+ await this.fetchImpl(this.endpointUrl, {
479
+ method: 'POST',
480
+ headers: { 'Content-Type': 'application/json' },
481
+ body: JSON.stringify(records),
482
+ });
483
+ return records;
484
+ }
485
+ }
486
+
487
+ class DataClassificationGate {
488
+ constructor(options = {}) {
489
+ this.defaultLevel = options.defaultLevel || 'internal';
490
+ this.providerAllowMap = options.providerAllowMap || {};
491
+ this.levelOrder = ['public', 'internal', 'confidential', 'restricted'];
492
+ }
493
+
494
+ classify({ metadata = {}, findings = [], messages = [] } = {}) {
495
+ if (metadata.classification) return metadata.classification;
496
+ const types = findings.map((item) => item.type).filter(Boolean);
497
+ if (types.some((type) => ['creditCard', 'tfn', 'passport', 'license', 'apiKey', 'jwt', 'bearerToken'].includes(type))) return 'restricted';
498
+ if (types.length) return 'confidential';
499
+ const text = JSON.stringify(messages || []).toLowerCase();
500
+ if (/\bconfidential|restricted|secret\b/.test(text)) return 'confidential';
501
+ return this.defaultLevel;
502
+ }
503
+
504
+ inspect({ metadata = {}, findings = [], messages = [], provider = null } = {}) {
505
+ const classification = this.classify({ metadata, findings, messages });
506
+ const allowedProviders = this.providerAllowMap[classification] || null;
507
+ const allowed = !provider || !allowedProviders || allowedProviders.includes(provider);
508
+ return {
509
+ allowed,
510
+ classification,
511
+ provider,
512
+ allowedProviders,
513
+ reason: allowed ? null : `Provider ${provider} is not allowed for ${classification} data`,
514
+ };
515
+ }
516
+ }
517
+
518
+ class ProviderRoutingPolicy {
519
+ constructor(options = {}) {
520
+ this.routes = options.routes || {};
521
+ this.fallbackProvider = options.fallbackProvider || null;
522
+ }
523
+
524
+ choose({ route = '', classification = 'internal', requestedProvider = null, candidates = [] } = {}) {
525
+ const routeConfig = this.routes[route] || this.routes.default || {};
526
+ const preferred = routeConfig[classification] || routeConfig.default || requestedProvider || this.fallbackProvider || candidates[0] || null;
527
+ const allowedCandidates = candidates.length ? candidates : [preferred].filter(Boolean);
528
+ const chosen = allowedCandidates.includes(preferred) ? preferred : allowedCandidates[0] || null;
529
+ return { provider: chosen, route, classification, requestedProvider, candidates: allowedCandidates };
530
+ }
531
+ }
532
+
533
+ class ApprovalInboxModel {
534
+ constructor(options = {}) {
535
+ this.requests = [];
536
+ this.requiredApprovers = options.requiredApprovers || 1;
537
+ }
538
+
539
+ createRequest(request = {}) {
540
+ const id = request.id || `apr_${crypto.randomBytes(6).toString('hex')}`;
541
+ const record = {
542
+ id,
543
+ status: 'pending',
544
+ requiredApprovers: request.requiredApprovers || this.requiredApprovers,
545
+ approvals: [],
546
+ createdAt: new Date().toISOString(),
547
+ ...request,
548
+ };
549
+ this.requests.push(record);
550
+ return record;
551
+ }
552
+
553
+ approve(id, approver) {
554
+ const request = this.requests.find((item) => item.id === id);
555
+ if (!request) return null;
556
+ if (!request.approvals.includes(approver)) request.approvals.push(approver);
557
+ request.status = request.approvals.length >= request.requiredApprovers ? 'approved' : 'pending';
558
+ return request;
559
+ }
560
+
561
+ summarize() {
562
+ return {
563
+ total: this.requests.length,
564
+ pending: this.requests.filter((item) => item.status === 'pending').length,
565
+ approved: this.requests.filter((item) => item.status === 'approved').length,
566
+ };
567
+ }
568
+ }
569
+
570
+ function buildComplianceEventBundle(event = {}) {
571
+ const payload = JSON.stringify(event);
572
+ const evidenceHash = crypto.createHash('sha256').update(payload).digest('hex');
573
+ return {
574
+ schemaVersion: '1.0',
575
+ generatedAt: new Date().toISOString(),
576
+ evidenceHash,
577
+ event,
578
+ };
579
+ }
580
+
581
+ function sanitizeAuditEvent(event = {}, options = {}) {
582
+ const keepEvidence = !!options.keepEvidence;
583
+ const clone = JSON.parse(JSON.stringify(event || {}));
584
+ if (!keepEvidence && clone.report && clone.report.sensitiveData) {
585
+ clone.report.sensitiveData.findings = (clone.report.sensitiveData.findings || []).map((finding) => ({ type: finding.type }));
586
+ }
587
+ return clone;
588
+ }
589
+
590
+ class RetrievalTrustScorer {
591
+ score(documents = []) {
592
+ return (documents || []).map((doc, index) => {
593
+ const metadata = doc && doc.metadata ? doc.metadata : {};
594
+ const sourceTrust = metadata.approved ? 0.4 : 0.1;
595
+ const freshness = metadata.fresh ? 0.3 : 0.1;
596
+ const origin = metadata.origin === 'trusted' ? 0.3 : 0.1;
597
+ const score = Number(Math.min(1, sourceTrust + freshness + origin).toFixed(2));
598
+ return {
599
+ id: doc && doc.id ? doc.id : `doc_${index + 1}`,
600
+ trustScore: score,
601
+ trusted: score >= 0.7,
602
+ metadata,
603
+ };
604
+ });
605
+ }
606
+ }
607
+
608
+ class OutboundCommunicationGuard {
609
+ constructor(options = {}) {
610
+ this.outputFirewall = options.outputFirewall || new OutputFirewall({ riskThreshold: 'high', enforceProfessionalTone: true });
611
+ }
612
+
613
+ inspect({ message, metadata = {} } = {}) {
614
+ const review = this.outputFirewall.inspect(message, {});
615
+ return {
616
+ allowed: review.allowed,
617
+ review,
618
+ channel: metadata.channel || 'outbound',
619
+ recipient: metadata.recipient || null,
620
+ };
621
+ }
622
+ }
623
+
624
+ class UploadQuarantineWorkflow {
625
+ constructor(options = {}) {
626
+ this.shield = options.shield || new BlackwallShield({ preset: 'documentIntake' });
627
+ this.inbox = options.approvalInbox || new ApprovalInboxModel({ requiredApprovers: 1 });
628
+ }
629
+
630
+ async inspectUpload({ content, metadata = {} } = {}) {
631
+ const guarded = await this.shield.guardModelRequest({
632
+ messages: [{ role: 'user', content }],
633
+ metadata: { ...metadata, feature: metadata.feature || 'upload_intake' },
634
+ });
635
+ const quarantined = !guarded.allowed || guarded.report.sensitiveData.hasSensitiveData;
636
+ const approvalRequest = quarantined ? this.inbox.createRequest({ route: metadata.route || '/uploads', reason: guarded.reason || 'Upload requires review', metadata }) : null;
637
+ return { quarantined, approvalRequest, guard: guarded };
638
+ }
639
+ }
640
+
641
+ function detectOperationalDrift(previousSummary = {}, currentSummary = {}) {
642
+ const previousBlocked = previousSummary.weeklyBlockEstimate || 0;
643
+ const currentBlocked = currentSummary.weeklyBlockEstimate || 0;
644
+ const delta = currentBlocked - previousBlocked;
645
+ return {
646
+ driftDetected: Math.abs(delta) > 0,
647
+ blockedDelta: delta,
648
+ previousBlocked,
649
+ currentBlocked,
650
+ severity: delta > 10 ? 'high' : delta > 0 ? 'medium' : 'low',
651
+ };
652
+ }
653
+
375
654
  function summarizeOperationalTelemetry(events = []) {
376
655
  const summary = {
377
656
  totalEvents: 0,
@@ -380,6 +659,8 @@ function summarizeOperationalTelemetry(events = []) {
380
659
  byType: {},
381
660
  byRoute: {},
382
661
  byFeature: {},
662
+ byUser: {},
663
+ byIdentityProvider: {},
383
664
  byTenant: {},
384
665
  byModel: {},
385
666
  byPolicyOutcome: {
@@ -398,6 +679,8 @@ function summarizeOperationalTelemetry(events = []) {
398
679
  const route = metadata.route || metadata.path || 'unknown';
399
680
  const feature = metadata.feature || metadata.capability || route;
400
681
  const tenant = metadata.tenantId || metadata.tenant_id || 'unknown';
682
+ const user = metadata.userId || metadata.user_id || (event.actor && event.actor.userId) || 'unknown';
683
+ const idp = metadata.identityProvider || metadata.identity_provider || (event.actor && event.actor.identityProvider) || 'unknown';
401
684
  const model = metadata.model || metadata.modelName || 'unknown';
402
685
  const severity = event && event.report && event.report.outputReview
403
686
  ? event.report.outputReview.severity
@@ -406,6 +689,8 @@ function summarizeOperationalTelemetry(events = []) {
406
689
  summary.byType[type] = (summary.byType[type] || 0) + 1;
407
690
  summary.byRoute[route] = (summary.byRoute[route] || 0) + 1;
408
691
  summary.byFeature[feature] = (summary.byFeature[feature] || 0) + 1;
692
+ summary.byUser[user] = (summary.byUser[user] || 0) + 1;
693
+ summary.byIdentityProvider[idp] = (summary.byIdentityProvider[idp] || 0) + 1;
409
694
  summary.byTenant[tenant] = (summary.byTenant[tenant] || 0) + 1;
410
695
  summary.byModel[model] = (summary.byModel[model] || 0) + 1;
411
696
  if (event && event.blocked) summary.blockedEvents += 1;
@@ -1112,6 +1397,8 @@ class BlackwallShield {
1112
1397
  outputFirewallDefaults: {},
1113
1398
  onAlert: null,
1114
1399
  onTelemetry: null,
1400
+ telemetryExporters: [],
1401
+ identityResolver: null,
1115
1402
  webhookUrl: null,
1116
1403
  ...options,
1117
1404
  };
@@ -1143,8 +1430,15 @@ class BlackwallShield {
1143
1430
  }
1144
1431
 
1145
1432
  async emitTelemetry(event) {
1433
+ const enriched = buildEnterpriseTelemetryEvent(event, this.options.identityResolver);
1146
1434
  if (typeof this.options.onTelemetry === 'function') {
1147
- await this.options.onTelemetry(event);
1435
+ await this.options.onTelemetry(enriched);
1436
+ }
1437
+ const exporters = Array.isArray(this.options.telemetryExporters) ? this.options.telemetryExporters : [];
1438
+ for (const exporter of exporters) {
1439
+ if (exporter && typeof exporter.send === 'function') {
1440
+ await exporter.send([enriched]);
1441
+ }
1148
1442
  }
1149
1443
  }
1150
1444
 
@@ -1563,9 +1857,10 @@ class CoTScanner {
1563
1857
  }
1564
1858
 
1565
1859
  class AgentIdentityRegistry {
1566
- constructor() {
1860
+ constructor(options = {}) {
1567
1861
  this.identities = new Map();
1568
1862
  this.ephemeralTokens = new Map();
1863
+ this.secret = options.secret || 'blackwall-agent-passport-secret';
1569
1864
  }
1570
1865
 
1571
1866
  register(agentId, profile = {}) {
@@ -1595,6 +1890,256 @@ class AgentIdentityRegistry {
1595
1890
  }
1596
1891
  return { valid: true, agentId: record.agentId };
1597
1892
  }
1893
+
1894
+ issueSignedPassport(agentId, options = {}) {
1895
+ const identity = this.get(agentId) || this.register(agentId, options.profile || {});
1896
+ const securityScore = options.securityScore != null
1897
+ ? options.securityScore
1898
+ : Math.max(0, 100 - (Object.values(identity.capabilities || {}).filter(Boolean).length * 10));
1899
+ const passport = {
1900
+ agentId,
1901
+ issuedAt: new Date().toISOString(),
1902
+ issuer: options.issuer || 'blackwall-llm-shield-js',
1903
+ blackwallProtected: options.blackwallProtected !== false,
1904
+ securityScore,
1905
+ scopes: identity.scopes || [],
1906
+ persona: identity.persona || 'default',
1907
+ environment: options.environment || 'production',
1908
+ };
1909
+ const signature = crypto.createHmac('sha256', this.secret).update(JSON.stringify(passport)).digest('hex');
1910
+ return { ...passport, signature };
1911
+ }
1912
+
1913
+ verifySignedPassport(passport = {}) {
1914
+ const { signature, ...unsigned } = passport || {};
1915
+ if (!signature) return { valid: false, reason: 'Passport signature is required' };
1916
+ const expected = crypto.createHmac('sha256', this.secret).update(JSON.stringify(unsigned)).digest('hex');
1917
+ return {
1918
+ valid: crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)),
1919
+ agentId: unsigned.agentId || null,
1920
+ securityScore: unsigned.securityScore || null,
1921
+ blackwallProtected: !!unsigned.blackwallProtected,
1922
+ };
1923
+ }
1924
+
1925
+ issuePassportToken(agentId, options = {}) {
1926
+ const passport = this.issueSignedPassport(agentId, options);
1927
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
1928
+ const payload = Buffer.from(JSON.stringify(passport)).toString('base64url');
1929
+ const signature = crypto.createHmac('sha256', this.secret).update(`${header}.${payload}`).digest('base64url');
1930
+ return `${header}.${payload}.${signature}`;
1931
+ }
1932
+
1933
+ verifyPassportToken(token) {
1934
+ if (!token || typeof token !== 'string' || token.split('.').length !== 3) {
1935
+ return { valid: false, reason: 'Malformed passport token' };
1936
+ }
1937
+ const [header, payload, signature] = token.split('.');
1938
+ const expected = crypto.createHmac('sha256', this.secret).update(`${header}.${payload}`).digest('base64url');
1939
+ if (signature !== expected) return { valid: false, reason: 'Invalid passport token signature' };
1940
+ const passport = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
1941
+ return { valid: true, passport, ...this.verifySignedPassport(passport) };
1942
+ }
1943
+ }
1944
+
1945
+ class ValueAtRiskCircuitBreaker {
1946
+ constructor(options = {}) {
1947
+ this.maxValuePerWindow = options.maxValuePerWindow || 5000;
1948
+ this.windowMs = options.windowMs || (60 * 60 * 1000);
1949
+ this.revocationMs = options.revocationMs || (30 * 60 * 1000);
1950
+ this.valueExtractor = options.valueExtractor || ((args = {}, context = {}) => Number(context.actionValue != null ? context.actionValue : (args.amount != null ? args.amount : 0)));
1951
+ this.toolSchemas = options.toolSchemas || [];
1952
+ this.entries = [];
1953
+ this.revocations = new Map();
1954
+ }
1955
+
1956
+ revokeSession(sessionId, durationMs = this.revocationMs) {
1957
+ if (!sessionId) return null;
1958
+ const expiresAt = Date.now() + durationMs;
1959
+ this.revocations.set(sessionId, expiresAt);
1960
+ return { sessionId, revokedUntil: new Date(expiresAt).toISOString() };
1961
+ }
1962
+
1963
+ inspect({ tool, args = {}, context = {} } = {}) {
1964
+ const sessionId = context.sessionId || context.session_id || null;
1965
+ const now = Date.now();
1966
+ const revokedUntil = sessionId ? this.revocations.get(sessionId) : null;
1967
+ if (revokedUntil && revokedUntil > now) {
1968
+ return {
1969
+ allowed: false,
1970
+ triggered: true,
1971
+ requiresMfa: true,
1972
+ reason: 'Session is revoked until MFA or human review completes',
1973
+ revokedSession: sessionId,
1974
+ revokedUntil: new Date(revokedUntil).toISOString(),
1975
+ riskWindowValue: null,
1976
+ };
1977
+ }
1978
+ this.entries = this.entries.filter((entry) => (now - entry.at) <= this.windowMs);
1979
+ const schema = this.toolSchemas.find((item) => item && item.name === tool) || {};
1980
+ const field = schema.monetaryValueField || schema.valueField || null;
1981
+ const schemaValue = field ? Number(args && args[field]) : 0;
1982
+ const actionValue = Math.max(0, Number((schemaValue || this.valueExtractor(args, context)) || 0));
1983
+ const key = sessionId || context.agentId || context.agent_id || context.userId || context.user_id || 'default';
1984
+ const relevant = this.entries.filter((entry) => entry.key === key);
1985
+ const riskWindowValue = relevant.reduce((sum, entry) => sum + entry.value, 0) + actionValue;
1986
+ const triggered = riskWindowValue > this.maxValuePerWindow;
1987
+ if (triggered) {
1988
+ const revocation = this.revokeSession(sessionId);
1989
+ return {
1990
+ allowed: false,
1991
+ triggered: true,
1992
+ requiresMfa: true,
1993
+ reason: `Value-at-risk threshold exceeded for ${tool || 'action'}`,
1994
+ riskWindowValue,
1995
+ threshold: this.maxValuePerWindow,
1996
+ actionValue,
1997
+ revokedSession: revocation && revocation.sessionId,
1998
+ revokedUntil: revocation && revocation.revokedUntil,
1999
+ };
2000
+ }
2001
+ this.entries.push({ key, tool: tool || 'unknown', value: actionValue, at: now });
2002
+ return {
2003
+ allowed: true,
2004
+ triggered: false,
2005
+ requiresMfa: false,
2006
+ riskWindowValue,
2007
+ threshold: this.maxValuePerWindow,
2008
+ actionValue,
2009
+ };
2010
+ }
2011
+ }
2012
+
2013
+ class ShadowConsensusAuditor {
2014
+ constructor(options = {}) {
2015
+ this.review = options.review || ((payload = {}) => {
2016
+ const text = JSON.stringify({ tool: payload.tool, args: payload.args, sessionContext: payload.sessionContext || '' }).toLowerCase();
2017
+ const disagreement = /\b(ignore previous|bypass|override|secret|reveal)\b/i.test(text);
2018
+ return {
2019
+ agreed: !disagreement,
2020
+ disagreement,
2021
+ reason: disagreement ? 'Logic Conflict: shadow auditor found risky reasoning drift' : null,
2022
+ };
2023
+ });
2024
+ }
2025
+
2026
+ inspect(payload = {}) {
2027
+ const result = this.review(payload) || {};
2028
+ return {
2029
+ agreed: result.agreed !== false,
2030
+ disagreement: !!result.disagreement || result.agreed === false,
2031
+ reason: result.reason || (result.agreed === false ? 'Logic Conflict detected by shadow auditor' : null),
2032
+ auditor: result.auditor || 'shadow',
2033
+ };
2034
+ }
2035
+ }
2036
+
2037
+ class CrossModelConsensusWrapper {
2038
+ constructor(options = {}) {
2039
+ this.primaryAdapter = options.primaryAdapter || null;
2040
+ this.auditorAdapter = options.auditorAdapter || null;
2041
+ this.decisionParser = options.decisionParser || ((output) => {
2042
+ const text = typeof output === 'string' ? output : JSON.stringify(output || '');
2043
+ if (/\b(block|unsafe|deny|disagree)\b/i.test(text)) return 'block';
2044
+ return 'allow';
2045
+ });
2046
+ }
2047
+
2048
+ async evaluate({ messages = [], metadata = {}, primaryResult = null } = {}) {
2049
+ if (!this.auditorAdapter || typeof this.auditorAdapter.invoke !== 'function') {
2050
+ return { agreed: true, disagreement: false, reason: null, primaryDecision: 'allow', auditorDecision: 'allow' };
2051
+ }
2052
+ const primaryDecision = primaryResult && primaryResult.blocked ? 'block' : 'allow';
2053
+ const response = await this.auditorAdapter.invoke({ messages, metadata, primaryResult });
2054
+ const output = typeof this.auditorAdapter.extractOutput === 'function'
2055
+ ? this.auditorAdapter.extractOutput(response && Object.prototype.hasOwnProperty.call(response, 'response') ? response.response : response)
2056
+ : (response && response.output) || response;
2057
+ const auditorDecision = this.decisionParser(output);
2058
+ const disagreement = auditorDecision !== primaryDecision;
2059
+ return {
2060
+ agreed: !disagreement,
2061
+ disagreement,
2062
+ primaryDecision,
2063
+ auditorDecision,
2064
+ reason: disagreement ? 'Logic Conflict: cross-model auditor disagreed with the primary decision' : null,
2065
+ auditorResponse: response,
2066
+ auditorOutput: output,
2067
+ };
2068
+ }
2069
+ }
2070
+
2071
+ class DigitalTwinOrchestrator {
2072
+ constructor(options = {}) {
2073
+ this.toolSchemas = options.toolSchemas || [];
2074
+ this.invocations = [];
2075
+ }
2076
+
2077
+ generate() {
2078
+ const handlers = {};
2079
+ for (const schema of this.toolSchemas) {
2080
+ if (!schema || !schema.name) continue;
2081
+ handlers[schema.name] = async (args = {}) => {
2082
+ const response = schema.mockResponse || schema.sampleResponse || { ok: true, tool: schema.name, args };
2083
+ this.invocations.push({ tool: schema.name, args, response, at: new Date().toISOString() });
2084
+ return response;
2085
+ };
2086
+ }
2087
+ return {
2088
+ handlers,
2089
+ simulateCall: async (tool, args = {}) => {
2090
+ if (!handlers[tool]) throw new Error(`No digital twin registered for ${tool}`);
2091
+ return handlers[tool](args);
2092
+ },
2093
+ invocations: this.invocations,
2094
+ };
2095
+ }
2096
+
2097
+ static fromToolPermissionFirewall(firewall) {
2098
+ const schemas = Array.isArray(firewall && firewall.options && firewall.options.toolSchemas)
2099
+ ? firewall.options.toolSchemas
2100
+ : [];
2101
+ return new DigitalTwinOrchestrator({ toolSchemas: schemas });
2102
+ }
2103
+ }
2104
+
2105
+ function suggestPolicyOverride({ route = null, approval = null, guardResult = null, toolDecision = null } = {}) {
2106
+ if (approval !== true) return null;
2107
+ if (guardResult && guardResult.report && guardResult.report.promptInjection) {
2108
+ const rules = (guardResult.report.promptInjection.matches || []).map((item) => item.id).filter(Boolean);
2109
+ return {
2110
+ route: route || (guardResult.report.metadata && (guardResult.report.metadata.route || guardResult.report.metadata.path)) || '*',
2111
+ options: {
2112
+ shadowMode: true,
2113
+ suppressPromptRules: [...new Set(rules)],
2114
+ },
2115
+ rationale: 'Suggested from approved false positive',
2116
+ };
2117
+ }
2118
+ if (toolDecision && toolDecision.approvalRequest) {
2119
+ return {
2120
+ route: route || ((toolDecision.approvalRequest.context || {}).route) || '*',
2121
+ options: {
2122
+ requireHumanApprovalFor: [toolDecision.approvalRequest.tool],
2123
+ },
2124
+ rationale: 'Suggested from approved high-impact tool action',
2125
+ };
2126
+ }
2127
+ return null;
2128
+ }
2129
+
2130
+ class PolicyLearningLoop {
2131
+ constructor() {
2132
+ this.decisions = [];
2133
+ }
2134
+
2135
+ recordDecision(input = {}) {
2136
+ this.decisions.push({ ...input, recordedAt: new Date().toISOString() });
2137
+ return suggestPolicyOverride(input);
2138
+ }
2139
+
2140
+ suggestOverrides() {
2141
+ return this.decisions.map((entry) => suggestPolicyOverride(entry)).filter(Boolean);
2142
+ }
1598
2143
  }
1599
2144
 
1600
2145
  class AgenticCapabilityGater {
@@ -1735,8 +2280,13 @@ class ToolPermissionFirewall {
1735
2280
  allowedTools: [],
1736
2281
  blockedTools: [],
1737
2282
  validators: {},
2283
+ toolSchemas: [],
1738
2284
  requireHumanApprovalFor: [],
1739
2285
  capabilityGater: null,
2286
+ valueAtRiskCircuitBreaker: null,
2287
+ consensusAuditor: null,
2288
+ crossModelConsensus: null,
2289
+ consensusRequiredFor: [],
1740
2290
  onApprovalRequest: null,
1741
2291
  approvalWebhookUrl: null,
1742
2292
  ...options,
@@ -1766,6 +2316,46 @@ class ToolPermissionFirewall {
1766
2316
  return { allowed: false, reason: gate.reason, requiresApproval: false, agentGate: gate };
1767
2317
  }
1768
2318
  }
2319
+ if (this.options.valueAtRiskCircuitBreaker) {
2320
+ const breaker = this.options.valueAtRiskCircuitBreaker.inspect({ tool, args, context });
2321
+ if (!breaker.allowed) {
2322
+ return {
2323
+ allowed: false,
2324
+ reason: breaker.reason,
2325
+ requiresApproval: true,
2326
+ requiresMfa: !!breaker.requiresMfa,
2327
+ circuitBreaker: breaker,
2328
+ approvalRequest: { tool, args, context, breaker },
2329
+ };
2330
+ }
2331
+ }
2332
+ if (this.options.consensusAuditor && (context.highImpact || this.options.consensusRequiredFor.includes(tool))) {
2333
+ const consensus = this.options.consensusAuditor.inspect({
2334
+ tool,
2335
+ args,
2336
+ context,
2337
+ sessionContext: context.sessionContext || context.session_buffer || null,
2338
+ });
2339
+ if (consensus.disagreement) {
2340
+ return {
2341
+ allowed: false,
2342
+ reason: consensus.reason || 'Logic Conflict detected by shadow auditor',
2343
+ requiresApproval: true,
2344
+ logicConflict: true,
2345
+ consensus,
2346
+ approvalRequest: { tool, args, context, consensus },
2347
+ };
2348
+ }
2349
+ }
2350
+ if (this.options.crossModelConsensus && (context.highImpact || this.options.consensusRequiredFor.includes(tool))) {
2351
+ return {
2352
+ allowed: false,
2353
+ reason: 'Cross-model consensus requires async inspection',
2354
+ requiresApproval: true,
2355
+ requiresAsyncConsensus: true,
2356
+ approvalRequest: { tool, args, context },
2357
+ };
2358
+ }
1769
2359
  const requiresApproval = this.options.requireHumanApprovalFor.includes(tool);
1770
2360
  return {
1771
2361
  allowed: !requiresApproval,
@@ -1777,6 +2367,24 @@ class ToolPermissionFirewall {
1777
2367
 
1778
2368
  async inspectCallAsync(input = {}) {
1779
2369
  const result = this.inspectCall(input);
2370
+ if (result.requiresAsyncConsensus && this.options.crossModelConsensus) {
2371
+ const consensus = await this.options.crossModelConsensus.evaluate({
2372
+ messages: input.context && input.context.consensusMessages ? input.context.consensusMessages : [{ role: 'user', content: JSON.stringify({ tool: input.tool, args: input.args, context: input.context }) }],
2373
+ metadata: input.context || {},
2374
+ primaryResult: result,
2375
+ });
2376
+ if (consensus.disagreement) {
2377
+ return {
2378
+ allowed: false,
2379
+ reason: consensus.reason,
2380
+ requiresApproval: true,
2381
+ logicConflict: true,
2382
+ consensus,
2383
+ approvalRequest: { tool: input.tool, args: input.args || {}, context: input.context || {}, consensus },
2384
+ };
2385
+ }
2386
+ return { allowed: true, reason: null, requiresApproval: false, consensus };
2387
+ }
1780
2388
  if (result.requiresApproval) {
1781
2389
  if (typeof this.options.onApprovalRequest === 'function') {
1782
2390
  await this.options.onApprovalRequest(result.approvalRequest);
@@ -1860,11 +2468,14 @@ class AuditTrail {
1860
2468
  constructor(options = {}) {
1861
2469
  this.secret = options.secret || 'blackwall-default-secret';
1862
2470
  this.events = [];
2471
+ this.identityResolver = options.identityResolver || null;
1863
2472
  }
1864
2473
 
1865
2474
  record(event = {}) {
2475
+ const actor = event.actor || normalizeIdentityMetadata(event.metadata || event, this.identityResolver);
1866
2476
  const payload = {
1867
2477
  ...event,
2478
+ actor,
1868
2479
  complianceMap: event.complianceMap || mapCompliance([
1869
2480
  ...(event.ruleIds || []),
1870
2481
  event.type === 'retrieval_poisoning_detected' ? 'retrieval_poisoning' : null,
@@ -2130,14 +2741,20 @@ module.exports = {
2130
2741
  AuditTrail,
2131
2742
  BlackwallShield,
2132
2743
  CoTScanner,
2744
+ CrossModelConsensusWrapper,
2745
+ DigitalTwinOrchestrator,
2133
2746
  ImageMetadataScanner,
2134
2747
  LightweightIntentScorer,
2135
2748
  MCPSecurityProxy,
2136
2749
  OutputFirewall,
2750
+ PowerBIExporter,
2751
+ PolicyLearningLoop,
2137
2752
  RetrievalSanitizer,
2138
2753
  SessionBuffer,
2754
+ ShadowConsensusAuditor,
2139
2755
  TokenBudgetFirewall,
2140
2756
  ToolPermissionFirewall,
2757
+ ValueAtRiskCircuitBreaker,
2141
2758
  VisualInstructionDetector,
2142
2759
  SENSITIVE_PATTERNS,
2143
2760
  PROMPT_INJECTION_RULES,
@@ -2167,6 +2784,19 @@ module.exports = {
2167
2784
  runRedTeamSuite,
2168
2785
  buildShieldOptions,
2169
2786
  summarizeOperationalTelemetry,
2787
+ suggestPolicyOverride,
2788
+ normalizeIdentityMetadata,
2789
+ buildEnterpriseTelemetryEvent,
2790
+ buildPowerBIRecord,
2791
+ buildComplianceEventBundle,
2792
+ sanitizeAuditEvent,
2793
+ detectOperationalDrift,
2794
+ DataClassificationGate,
2795
+ ProviderRoutingPolicy,
2796
+ ApprovalInboxModel,
2797
+ RetrievalTrustScorer,
2798
+ OutboundCommunicationGuard,
2799
+ UploadQuarantineWorkflow,
2170
2800
  parseJsonOutput,
2171
2801
  createOpenAIAdapter,
2172
2802
  createAnthropicAdapter,