@vpdeva/blackwall-llm-shield-js 0.2.0 → 0.2.4
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 +43 -4
- package/index.d.ts +47 -0
- package/package.json +4 -1
- package/src/index.js +322 -1
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
|
|
@@ -99,6 +100,10 @@ Enterprise deployments can also enrich emitted events with SSO/user context and
|
|
|
99
100
|
|
|
100
101
|
Use `createExpressMiddleware()`, `createLangChainCallbacks()`, or `createLlamaIndexCallback()` to drop Blackwall into existing app and orchestration flows faster.
|
|
101
102
|
|
|
103
|
+
### Example guide
|
|
104
|
+
|
|
105
|
+
Use the wiki-ready examples page at [`wiki/Running-Examples.md`](/Users/vishnu/Documents/blackwall-llm-shield/blackwall-llm-shield-js/wiki/Running-Examples.md) for copy-paste setup and run commands.
|
|
106
|
+
|
|
102
107
|
### Subpath modules
|
|
103
108
|
|
|
104
109
|
Use `require('@vpdeva/blackwall-llm-shield-js/integrations')` for callback wrappers and `require('@vpdeva/blackwall-llm-shield-js/semantic')` for optional local semantic scoring adapters.
|
|
@@ -129,7 +134,7 @@ Use it before injecting retrieved documents into context so hostile instructions
|
|
|
129
134
|
|
|
130
135
|
### Contract Stability
|
|
131
136
|
|
|
132
|
-
The 0.
|
|
137
|
+
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.
|
|
133
138
|
|
|
134
139
|
Recommended presets:
|
|
135
140
|
|
|
@@ -141,6 +146,22 @@ Recommended presets:
|
|
|
141
146
|
- `documentReview` for classification and document-review pipelines
|
|
142
147
|
- `ragSearch` for search-heavy retrieval endpoints
|
|
143
148
|
- `toolCalling` for routes that broker external actions
|
|
149
|
+
- `governmentStrict` for highly regulated public-sector and records-sensitive workflows
|
|
150
|
+
- `bankingPayments` for high-value payment and financial action routes
|
|
151
|
+
- `documentIntake` for upload-heavy intake and review flows
|
|
152
|
+
- `citizenServices` for identity-aware service delivery workflows
|
|
153
|
+
- `internalOpsAgent` for internal operational assistants with shadow-first defaults
|
|
154
|
+
|
|
155
|
+
### Global Governance Pack
|
|
156
|
+
|
|
157
|
+
The 0.2.2 line also adds globally applicable enterprise controls that are useful across regulated industries, not just one country or sector:
|
|
158
|
+
|
|
159
|
+
- `DataClassificationGate` to classify traffic as `public`, `internal`, `confidential`, or `restricted`
|
|
160
|
+
- `ProviderRoutingPolicy` to keep sensitive classes on approved providers
|
|
161
|
+
- `ApprovalInboxModel` and `UploadQuarantineWorkflow` for quarantine and review-first intake
|
|
162
|
+
- `buildComplianceEventBundle()` and `sanitizeAuditEvent()` for audit-safe event export
|
|
163
|
+
- `RetrievalTrustScorer` and `OutboundCommunicationGuard` for retrieval trust and outbound checks
|
|
164
|
+
- `detectOperationalDrift()` for release-over-release noise monitoring
|
|
144
165
|
|
|
145
166
|
### `AuditTrail`
|
|
146
167
|
|
|
@@ -150,9 +171,10 @@ Use it to record signed events, summarize security activity, and power dashboard
|
|
|
150
171
|
|
|
151
172
|
- `ValueAtRiskCircuitBreaker` for financial or high-value operational actions
|
|
152
173
|
- `ShadowConsensusAuditor` for second-model or secondary-review logic conflict checks
|
|
174
|
+
- `CrossModelConsensusWrapper` for automatic cross-model verification of high-impact actions
|
|
153
175
|
- `DigitalTwinOrchestrator` for mock tool environments and sandbox simulations
|
|
154
|
-
- `suggestPolicyOverride()` for narrow false-positive tuning suggestions after HITL approvals
|
|
155
|
-
- `AgentIdentityRegistry.issueSignedPassport()` for signed agent identity exchange
|
|
176
|
+
- `PolicyLearningLoop` plus `suggestPolicyOverride()` for narrow false-positive tuning suggestions after HITL approvals
|
|
177
|
+
- `AgentIdentityRegistry.issueSignedPassport()` and `issuePassportToken()` for signed agent identity exchange
|
|
156
178
|
|
|
157
179
|
## Example Workflows
|
|
158
180
|
|
|
@@ -243,6 +265,20 @@ const firewall = new ToolPermissionFirewall({
|
|
|
243
265
|
});
|
|
244
266
|
```
|
|
245
267
|
|
|
268
|
+
### Add automatic cross-model consensus
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
const consensus = new CrossModelConsensusWrapper({
|
|
272
|
+
auditorAdapter: geminiAuditorAdapter,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const firewall = new ToolPermissionFirewall({
|
|
276
|
+
allowedTools: ['issueRefund'],
|
|
277
|
+
crossModelConsensus: consensus,
|
|
278
|
+
consensusRequiredFor: ['issueRefund'],
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
246
282
|
### Generate a digital twin for sandbox testing
|
|
247
283
|
|
|
248
284
|
```js
|
|
@@ -255,6 +291,8 @@ const twin = new DigitalTwinOrchestrator({
|
|
|
255
291
|
await twin.simulateCall('lookupOrder', { orderId: 'ord_1' });
|
|
256
292
|
```
|
|
257
293
|
|
|
294
|
+
You can also derive a digital twin from `ToolPermissionFirewall` tool schemas with `DigitalTwinOrchestrator.fromToolPermissionFirewall(firewall)`.
|
|
295
|
+
|
|
258
296
|
### Protect a strict JSON workflow
|
|
259
297
|
|
|
260
298
|
```js
|
|
@@ -418,6 +456,7 @@ console.log(tools.inspectCall({ tool: 'lookupCustomer', args: { id: 'cus_123' }
|
|
|
418
456
|
|
|
419
457
|
- [`examples/nextjs-app-router/app/api/chat/route.js`](/Users/vishnu/Documents/blackwall-llm-shield/blackwall-llm-shield-js/examples/nextjs-app-router/app/api/chat/route.js) shows guarded request handling in a Next.js route
|
|
420
458
|
- [`examples/admin-dashboard/index.html`](/Users/vishnu/Documents/blackwall-llm-shield/blackwall-llm-shield-js/examples/admin-dashboard/index.html) shows a polished security command center demo
|
|
459
|
+
- [`wiki/Running-Examples.md`](/Users/vishnu/Documents/blackwall-llm-shield/blackwall-llm-shield-js/wiki/Running-Examples.md) shows how to run the available examples end to end
|
|
421
460
|
|
|
422
461
|
For Next.js, the most production-real patterns are App Router route handlers, server actions for trusted internal mutations, and streaming endpoints that apply output review to assembled or final chunks instead of raw intermediate tokens.
|
|
423
462
|
|
package/index.d.ts
CHANGED
|
@@ -92,9 +92,53 @@ export class ShadowConsensusAuditor {
|
|
|
92
92
|
inspect(input?: Record<string, unknown>): Record<string, unknown>;
|
|
93
93
|
}
|
|
94
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
|
+
|
|
95
100
|
export class DigitalTwinOrchestrator {
|
|
96
101
|
constructor(options?: Record<string, unknown>);
|
|
97
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>>;
|
|
98
142
|
}
|
|
99
143
|
|
|
100
144
|
export class RetrievalSanitizer {
|
|
@@ -118,6 +162,9 @@ export function parseJsonOutput(output: unknown): unknown;
|
|
|
118
162
|
export function normalizeIdentityMetadata(metadata?: Record<string, unknown>, resolver?: ((metadata: Record<string, unknown>) => Record<string, unknown> | null) | null): Record<string, unknown>;
|
|
119
163
|
export function buildEnterpriseTelemetryEvent(event?: Record<string, unknown>, resolver?: ((metadata: Record<string, unknown>) => Record<string, unknown> | null) | null): Record<string, unknown>;
|
|
120
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>;
|
|
121
168
|
export function suggestPolicyOverride(input?: Record<string, unknown>): Record<string, unknown> | null;
|
|
122
169
|
export class PowerBIExporter {
|
|
123
170
|
constructor(options?: Record<string, unknown>);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vpdeva/blackwall-llm-shield-js",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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';
|
|
@@ -449,6 +484,173 @@ class PowerBIExporter {
|
|
|
449
484
|
}
|
|
450
485
|
}
|
|
451
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
|
+
|
|
452
654
|
function summarizeOperationalTelemetry(events = []) {
|
|
453
655
|
const summary = {
|
|
454
656
|
totalEvents: 0,
|
|
@@ -1719,6 +1921,25 @@ class AgentIdentityRegistry {
|
|
|
1719
1921
|
blackwallProtected: !!unsigned.blackwallProtected,
|
|
1720
1922
|
};
|
|
1721
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
|
+
}
|
|
1722
1943
|
}
|
|
1723
1944
|
|
|
1724
1945
|
class ValueAtRiskCircuitBreaker {
|
|
@@ -1727,6 +1948,7 @@ class ValueAtRiskCircuitBreaker {
|
|
|
1727
1948
|
this.windowMs = options.windowMs || (60 * 60 * 1000);
|
|
1728
1949
|
this.revocationMs = options.revocationMs || (30 * 60 * 1000);
|
|
1729
1950
|
this.valueExtractor = options.valueExtractor || ((args = {}, context = {}) => Number(context.actionValue != null ? context.actionValue : (args.amount != null ? args.amount : 0)));
|
|
1951
|
+
this.toolSchemas = options.toolSchemas || [];
|
|
1730
1952
|
this.entries = [];
|
|
1731
1953
|
this.revocations = new Map();
|
|
1732
1954
|
}
|
|
@@ -1754,7 +1976,10 @@ class ValueAtRiskCircuitBreaker {
|
|
|
1754
1976
|
};
|
|
1755
1977
|
}
|
|
1756
1978
|
this.entries = this.entries.filter((entry) => (now - entry.at) <= this.windowMs);
|
|
1757
|
-
const
|
|
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));
|
|
1758
1983
|
const key = sessionId || context.agentId || context.agent_id || context.userId || context.user_id || 'default';
|
|
1759
1984
|
const relevant = this.entries.filter((entry) => entry.key === key);
|
|
1760
1985
|
const riskWindowValue = relevant.reduce((sum, entry) => sum + entry.value, 0) + actionValue;
|
|
@@ -1809,6 +2034,40 @@ class ShadowConsensusAuditor {
|
|
|
1809
2034
|
}
|
|
1810
2035
|
}
|
|
1811
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
|
+
|
|
1812
2071
|
class DigitalTwinOrchestrator {
|
|
1813
2072
|
constructor(options = {}) {
|
|
1814
2073
|
this.toolSchemas = options.toolSchemas || [];
|
|
@@ -1834,6 +2093,13 @@ class DigitalTwinOrchestrator {
|
|
|
1834
2093
|
invocations: this.invocations,
|
|
1835
2094
|
};
|
|
1836
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
|
+
}
|
|
1837
2103
|
}
|
|
1838
2104
|
|
|
1839
2105
|
function suggestPolicyOverride({ route = null, approval = null, guardResult = null, toolDecision = null } = {}) {
|
|
@@ -1861,6 +2127,21 @@ function suggestPolicyOverride({ route = null, approval = null, guardResult = nu
|
|
|
1861
2127
|
return null;
|
|
1862
2128
|
}
|
|
1863
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
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
|
|
1864
2145
|
class AgenticCapabilityGater {
|
|
1865
2146
|
constructor(options = {}) {
|
|
1866
2147
|
this.registry = options.registry || new AgentIdentityRegistry();
|
|
@@ -1999,10 +2280,12 @@ class ToolPermissionFirewall {
|
|
|
1999
2280
|
allowedTools: [],
|
|
2000
2281
|
blockedTools: [],
|
|
2001
2282
|
validators: {},
|
|
2283
|
+
toolSchemas: [],
|
|
2002
2284
|
requireHumanApprovalFor: [],
|
|
2003
2285
|
capabilityGater: null,
|
|
2004
2286
|
valueAtRiskCircuitBreaker: null,
|
|
2005
2287
|
consensusAuditor: null,
|
|
2288
|
+
crossModelConsensus: null,
|
|
2006
2289
|
consensusRequiredFor: [],
|
|
2007
2290
|
onApprovalRequest: null,
|
|
2008
2291
|
approvalWebhookUrl: null,
|
|
@@ -2064,6 +2347,15 @@ class ToolPermissionFirewall {
|
|
|
2064
2347
|
};
|
|
2065
2348
|
}
|
|
2066
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
|
+
}
|
|
2067
2359
|
const requiresApproval = this.options.requireHumanApprovalFor.includes(tool);
|
|
2068
2360
|
return {
|
|
2069
2361
|
allowed: !requiresApproval,
|
|
@@ -2075,6 +2367,24 @@ class ToolPermissionFirewall {
|
|
|
2075
2367
|
|
|
2076
2368
|
async inspectCallAsync(input = {}) {
|
|
2077
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
|
+
}
|
|
2078
2388
|
if (result.requiresApproval) {
|
|
2079
2389
|
if (typeof this.options.onApprovalRequest === 'function') {
|
|
2080
2390
|
await this.options.onApprovalRequest(result.approvalRequest);
|
|
@@ -2431,12 +2741,14 @@ module.exports = {
|
|
|
2431
2741
|
AuditTrail,
|
|
2432
2742
|
BlackwallShield,
|
|
2433
2743
|
CoTScanner,
|
|
2744
|
+
CrossModelConsensusWrapper,
|
|
2434
2745
|
DigitalTwinOrchestrator,
|
|
2435
2746
|
ImageMetadataScanner,
|
|
2436
2747
|
LightweightIntentScorer,
|
|
2437
2748
|
MCPSecurityProxy,
|
|
2438
2749
|
OutputFirewall,
|
|
2439
2750
|
PowerBIExporter,
|
|
2751
|
+
PolicyLearningLoop,
|
|
2440
2752
|
RetrievalSanitizer,
|
|
2441
2753
|
SessionBuffer,
|
|
2442
2754
|
ShadowConsensusAuditor,
|
|
@@ -2476,6 +2788,15 @@ module.exports = {
|
|
|
2476
2788
|
normalizeIdentityMetadata,
|
|
2477
2789
|
buildEnterpriseTelemetryEvent,
|
|
2478
2790
|
buildPowerBIRecord,
|
|
2791
|
+
buildComplianceEventBundle,
|
|
2792
|
+
sanitizeAuditEvent,
|
|
2793
|
+
detectOperationalDrift,
|
|
2794
|
+
DataClassificationGate,
|
|
2795
|
+
ProviderRoutingPolicy,
|
|
2796
|
+
ApprovalInboxModel,
|
|
2797
|
+
RetrievalTrustScorer,
|
|
2798
|
+
OutboundCommunicationGuard,
|
|
2799
|
+
UploadQuarantineWorkflow,
|
|
2479
2800
|
parseJsonOutput,
|
|
2480
2801
|
createOpenAIAdapter,
|
|
2481
2802
|
createAnthropicAdapter,
|