@sanctuary-framework/mcp-server 0.3.1 → 0.4.1
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/dist/cli.cjs +2085 -50
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2086 -52
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2056 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +267 -6
- package/dist/index.d.ts +267 -6
- package/dist/index.js +2049 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var hmac = require('@noble/hashes/hmac');
|
|
|
5
5
|
var promises = require('fs/promises');
|
|
6
6
|
var path = require('path');
|
|
7
7
|
var os = require('os');
|
|
8
|
+
var module$1 = require('module');
|
|
8
9
|
var crypto = require('crypto');
|
|
9
10
|
var aes_js = require('@noble/ciphers/aes.js');
|
|
10
11
|
var ed25519 = require('@noble/curves/ed25519');
|
|
@@ -15,7 +16,9 @@ var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
|
15
16
|
var http = require('http');
|
|
16
17
|
var https = require('https');
|
|
17
18
|
var fs = require('fs');
|
|
19
|
+
var child_process = require('child_process');
|
|
18
20
|
|
|
21
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
19
22
|
var __defProp = Object.defineProperty;
|
|
20
23
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
21
24
|
var __esm = (fn, res) => function __init() {
|
|
@@ -204,9 +207,12 @@ var init_hashing = __esm({
|
|
|
204
207
|
init_encoding();
|
|
205
208
|
}
|
|
206
209
|
});
|
|
210
|
+
var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
211
|
+
var { version: PKG_VERSION } = require2("../package.json");
|
|
212
|
+
var SANCTUARY_VERSION = PKG_VERSION;
|
|
207
213
|
function defaultConfig() {
|
|
208
214
|
return {
|
|
209
|
-
version:
|
|
215
|
+
version: PKG_VERSION,
|
|
210
216
|
storage_path: path.join(os.homedir(), ".sanctuary"),
|
|
211
217
|
state: {
|
|
212
218
|
encryption: "aes-256-gcm",
|
|
@@ -332,6 +338,18 @@ function validateConfig(config) {
|
|
|
332
338
|
`Unimplemented config value: disclosure.proof_system = "${config.disclosure.proof_system}". Only ${[...implementedProofSystem].map((v) => `"${v}"`).join(", ")} is currently implemented. Using an unimplemented proof system would silently degrade security.`
|
|
333
339
|
);
|
|
334
340
|
}
|
|
341
|
+
const implementedDisclosurePolicy = /* @__PURE__ */ new Set(["minimum-necessary"]);
|
|
342
|
+
if (!implementedDisclosurePolicy.has(config.disclosure.default_policy)) {
|
|
343
|
+
errors.push(
|
|
344
|
+
`Unimplemented config value: disclosure.default_policy = "${config.disclosure.default_policy}". Only ${[...implementedDisclosurePolicy].map((v) => `"${v}"`).join(", ")} is currently implemented. Using an unimplemented disclosure policy would silently skip disclosure controls.`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
const implementedReputationMode = /* @__PURE__ */ new Set(["self-custodied"]);
|
|
348
|
+
if (!implementedReputationMode.has(config.reputation.mode)) {
|
|
349
|
+
errors.push(
|
|
350
|
+
`Unimplemented config value: reputation.mode = "${config.reputation.mode}". Only ${[...implementedReputationMode].map((v) => `"${v}"`).join(", ")} is currently implemented. Using an unimplemented reputation mode would silently skip reputation verification.`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
335
353
|
if (errors.length > 0) {
|
|
336
354
|
throw new Error(
|
|
337
355
|
`Sanctuary configuration references unimplemented features:
|
|
@@ -1037,6 +1055,8 @@ var StateStore = class {
|
|
|
1037
1055
|
};
|
|
1038
1056
|
}
|
|
1039
1057
|
};
|
|
1058
|
+
var require3 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
1059
|
+
var { version: PKG_VERSION2 } = require3("../package.json");
|
|
1040
1060
|
var MAX_STRING_BYTES = 1048576;
|
|
1041
1061
|
var MAX_BUNDLE_BYTES = 5242880;
|
|
1042
1062
|
var BUNDLE_FIELDS = /* @__PURE__ */ new Set(["bundle"]);
|
|
@@ -1119,7 +1139,7 @@ function createServer(tools, options) {
|
|
|
1119
1139
|
const server = new index_js.Server(
|
|
1120
1140
|
{
|
|
1121
1141
|
name: "sanctuary-mcp-server",
|
|
1122
|
-
version:
|
|
1142
|
+
version: PKG_VERSION2
|
|
1123
1143
|
},
|
|
1124
1144
|
{
|
|
1125
1145
|
capabilities: {
|
|
@@ -3573,7 +3593,9 @@ var DEFAULT_POLICY = {
|
|
|
3573
3593
|
"state_delete",
|
|
3574
3594
|
"identity_rotate",
|
|
3575
3595
|
"reputation_import",
|
|
3576
|
-
"
|
|
3596
|
+
"reputation_export",
|
|
3597
|
+
"bootstrap_provide_guarantee",
|
|
3598
|
+
"decommission_certificate"
|
|
3577
3599
|
],
|
|
3578
3600
|
tier2_anomaly: DEFAULT_TIER2,
|
|
3579
3601
|
tier3_always_allow: [
|
|
@@ -3590,7 +3612,6 @@ var DEFAULT_POLICY = {
|
|
|
3590
3612
|
"disclosure_evaluate",
|
|
3591
3613
|
"reputation_record",
|
|
3592
3614
|
"reputation_query",
|
|
3593
|
-
"reputation_export",
|
|
3594
3615
|
"bootstrap_create_escrow",
|
|
3595
3616
|
"exec_attest",
|
|
3596
3617
|
"monitor_health",
|
|
@@ -3612,7 +3633,19 @@ var DEFAULT_POLICY = {
|
|
|
3612
3633
|
"zk_prove",
|
|
3613
3634
|
"zk_verify",
|
|
3614
3635
|
"zk_range_prove",
|
|
3615
|
-
"zk_range_verify"
|
|
3636
|
+
"zk_range_verify",
|
|
3637
|
+
"context_gate_set_policy",
|
|
3638
|
+
"context_gate_apply_template",
|
|
3639
|
+
"context_gate_recommend",
|
|
3640
|
+
"context_gate_filter",
|
|
3641
|
+
"context_gate_list_policies",
|
|
3642
|
+
"l2_hardening_status",
|
|
3643
|
+
"l2_verify_isolation",
|
|
3644
|
+
"sovereignty_audit",
|
|
3645
|
+
"shr_gateway_export",
|
|
3646
|
+
"bridge_commit",
|
|
3647
|
+
"bridge_verify",
|
|
3648
|
+
"bridge_attest"
|
|
3616
3649
|
],
|
|
3617
3650
|
approval_channel: DEFAULT_CHANNEL
|
|
3618
3651
|
};
|
|
@@ -3714,6 +3747,7 @@ tier1_always_approve:
|
|
|
3714
3747
|
- state_delete
|
|
3715
3748
|
- identity_rotate
|
|
3716
3749
|
- reputation_import
|
|
3750
|
+
- reputation_export
|
|
3717
3751
|
- bootstrap_provide_guarantee
|
|
3718
3752
|
|
|
3719
3753
|
# \u2500\u2500\u2500 Tier 2: Behavioral Anomaly Detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -3743,7 +3777,6 @@ tier3_always_allow:
|
|
|
3743
3777
|
- disclosure_evaluate
|
|
3744
3778
|
- reputation_record
|
|
3745
3779
|
- reputation_query
|
|
3746
|
-
- reputation_export
|
|
3747
3780
|
- bootstrap_create_escrow
|
|
3748
3781
|
- exec_attest
|
|
3749
3782
|
- monitor_health
|
|
@@ -3766,6 +3799,16 @@ tier3_always_allow:
|
|
|
3766
3799
|
- zk_verify
|
|
3767
3800
|
- zk_range_prove
|
|
3768
3801
|
- zk_range_verify
|
|
3802
|
+
- context_gate_set_policy
|
|
3803
|
+
- context_gate_apply_template
|
|
3804
|
+
- context_gate_recommend
|
|
3805
|
+
- context_gate_filter
|
|
3806
|
+
- context_gate_list_policies
|
|
3807
|
+
- sovereignty_audit
|
|
3808
|
+
- shr_gateway_export
|
|
3809
|
+
- bridge_commit
|
|
3810
|
+
- bridge_verify
|
|
3811
|
+
- bridge_attest
|
|
3769
3812
|
|
|
3770
3813
|
# \u2500\u2500\u2500 Approval Channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3771
3814
|
# How Sanctuary reaches you when approval is needed.
|
|
@@ -4562,6 +4605,10 @@ function generateDashboardHTML(options) {
|
|
|
4562
4605
|
// src/principal-policy/dashboard.ts
|
|
4563
4606
|
var SESSION_TTL_MS = 5 * 60 * 1e3;
|
|
4564
4607
|
var MAX_SESSIONS = 1e3;
|
|
4608
|
+
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
4609
|
+
var RATE_LIMIT_GENERAL = 120;
|
|
4610
|
+
var RATE_LIMIT_DECISIONS = 20;
|
|
4611
|
+
var MAX_RATE_LIMIT_ENTRIES = 1e4;
|
|
4565
4612
|
var DashboardApprovalChannel = class {
|
|
4566
4613
|
config;
|
|
4567
4614
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -4576,13 +4623,15 @@ var DashboardApprovalChannel = class {
|
|
|
4576
4623
|
/** SEC-012: Short-lived session store. Sessions replace URL query tokens. */
|
|
4577
4624
|
sessions = /* @__PURE__ */ new Map();
|
|
4578
4625
|
sessionCleanupTimer = null;
|
|
4626
|
+
/** Rate limiting: per-IP request tracking */
|
|
4627
|
+
rateLimits = /* @__PURE__ */ new Map();
|
|
4579
4628
|
constructor(config) {
|
|
4580
4629
|
this.config = config;
|
|
4581
4630
|
this.authToken = config.auth_token;
|
|
4582
4631
|
this.useTLS = !!(config.tls?.cert_path && config.tls?.key_path);
|
|
4583
4632
|
this.dashboardHTML = generateDashboardHTML({
|
|
4584
4633
|
timeoutSeconds: config.timeout_seconds,
|
|
4585
|
-
serverVersion:
|
|
4634
|
+
serverVersion: SANCTUARY_VERSION,
|
|
4586
4635
|
authToken: this.authToken
|
|
4587
4636
|
});
|
|
4588
4637
|
this.sessionCleanupTimer = setInterval(() => this.cleanupSessions(), 6e4);
|
|
@@ -4661,6 +4710,7 @@ var DashboardApprovalChannel = class {
|
|
|
4661
4710
|
clearInterval(this.sessionCleanupTimer);
|
|
4662
4711
|
this.sessionCleanupTimer = null;
|
|
4663
4712
|
}
|
|
4713
|
+
this.rateLimits.clear();
|
|
4664
4714
|
if (this.httpServer) {
|
|
4665
4715
|
return new Promise((resolve) => {
|
|
4666
4716
|
this.httpServer.close(() => resolve());
|
|
@@ -4786,6 +4836,61 @@ var DashboardApprovalChannel = class {
|
|
|
4786
4836
|
}
|
|
4787
4837
|
}
|
|
4788
4838
|
}
|
|
4839
|
+
// ── Rate Limiting ─────────────────────────────────────────────────
|
|
4840
|
+
/**
|
|
4841
|
+
* Get the remote address from a request, normalizing IPv6-mapped IPv4.
|
|
4842
|
+
*/
|
|
4843
|
+
getRemoteAddr(req) {
|
|
4844
|
+
const addr = req.socket.remoteAddress ?? "unknown";
|
|
4845
|
+
return addr.startsWith("::ffff:") ? addr.slice(7) : addr;
|
|
4846
|
+
}
|
|
4847
|
+
/**
|
|
4848
|
+
* Check rate limit for a request. Returns true if allowed, false if rate-limited.
|
|
4849
|
+
* When rate-limited, sends a 429 response.
|
|
4850
|
+
*/
|
|
4851
|
+
checkRateLimit(req, res, type) {
|
|
4852
|
+
const addr = this.getRemoteAddr(req);
|
|
4853
|
+
const now = Date.now();
|
|
4854
|
+
const windowStart = now - RATE_LIMIT_WINDOW_MS;
|
|
4855
|
+
let entry = this.rateLimits.get(addr);
|
|
4856
|
+
if (!entry) {
|
|
4857
|
+
if (this.rateLimits.size >= MAX_RATE_LIMIT_ENTRIES) {
|
|
4858
|
+
this.pruneRateLimits(now);
|
|
4859
|
+
}
|
|
4860
|
+
entry = { general: [], decisions: [] };
|
|
4861
|
+
this.rateLimits.set(addr, entry);
|
|
4862
|
+
}
|
|
4863
|
+
entry.general = entry.general.filter((t) => t > windowStart);
|
|
4864
|
+
entry.decisions = entry.decisions.filter((t) => t > windowStart);
|
|
4865
|
+
const limit = type === "decisions" ? RATE_LIMIT_DECISIONS : RATE_LIMIT_GENERAL;
|
|
4866
|
+
const timestamps = entry[type];
|
|
4867
|
+
if (timestamps.length >= limit) {
|
|
4868
|
+
const retryAfter = Math.ceil((timestamps[0] + RATE_LIMIT_WINDOW_MS - now) / 1e3);
|
|
4869
|
+
res.writeHead(429, {
|
|
4870
|
+
"Content-Type": "application/json",
|
|
4871
|
+
"Retry-After": String(Math.max(1, retryAfter))
|
|
4872
|
+
});
|
|
4873
|
+
res.end(JSON.stringify({
|
|
4874
|
+
error: "Rate limit exceeded",
|
|
4875
|
+
retry_after_seconds: Math.max(1, retryAfter)
|
|
4876
|
+
}));
|
|
4877
|
+
return false;
|
|
4878
|
+
}
|
|
4879
|
+
timestamps.push(now);
|
|
4880
|
+
return true;
|
|
4881
|
+
}
|
|
4882
|
+
/**
|
|
4883
|
+
* Remove stale entries from the rate limit map.
|
|
4884
|
+
*/
|
|
4885
|
+
pruneRateLimits(now) {
|
|
4886
|
+
const windowStart = now - RATE_LIMIT_WINDOW_MS;
|
|
4887
|
+
for (const [addr, entry] of this.rateLimits) {
|
|
4888
|
+
const hasRecent = entry.general.some((t) => t > windowStart) || entry.decisions.some((t) => t > windowStart);
|
|
4889
|
+
if (!hasRecent) {
|
|
4890
|
+
this.rateLimits.delete(addr);
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4789
4894
|
// ── HTTP Request Handler ────────────────────────────────────────────
|
|
4790
4895
|
handleRequest(req, res) {
|
|
4791
4896
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -4804,6 +4909,7 @@ var DashboardApprovalChannel = class {
|
|
|
4804
4909
|
return;
|
|
4805
4910
|
}
|
|
4806
4911
|
if (!this.checkAuth(req, url, res)) return;
|
|
4912
|
+
if (!this.checkRateLimit(req, res, "general")) return;
|
|
4807
4913
|
try {
|
|
4808
4914
|
if (method === "POST" && url.pathname === "/auth/session") {
|
|
4809
4915
|
this.handleSessionExchange(req, res);
|
|
@@ -4820,9 +4926,11 @@ var DashboardApprovalChannel = class {
|
|
|
4820
4926
|
} else if (method === "GET" && url.pathname === "/api/audit-log") {
|
|
4821
4927
|
this.handleAuditLog(url, res);
|
|
4822
4928
|
} else if (method === "POST" && url.pathname.startsWith("/api/approve/")) {
|
|
4929
|
+
if (!this.checkRateLimit(req, res, "decisions")) return;
|
|
4823
4930
|
const id = url.pathname.slice("/api/approve/".length);
|
|
4824
4931
|
this.handleDecision(id, "approve", res);
|
|
4825
4932
|
} else if (method === "POST" && url.pathname.startsWith("/api/deny/")) {
|
|
4933
|
+
if (!this.checkRateLimit(req, res, "decisions")) return;
|
|
4826
4934
|
const id = url.pathname.slice("/api/deny/".length);
|
|
4827
4935
|
this.handleDecision(id, "deny", res);
|
|
4828
4936
|
} else {
|
|
@@ -5565,14 +5673,14 @@ function generateSHR(identityId, opts) {
|
|
|
5565
5673
|
code: "PROCESS_ISOLATION_ONLY",
|
|
5566
5674
|
severity: "warning",
|
|
5567
5675
|
description: "Process-level isolation only (no TEE)",
|
|
5568
|
-
mitigation: "TEE support planned for
|
|
5676
|
+
mitigation: "TEE support planned for a future release"
|
|
5569
5677
|
});
|
|
5570
5678
|
degradations.push({
|
|
5571
5679
|
layer: "l2",
|
|
5572
5680
|
code: "SELF_REPORTED_ATTESTATION",
|
|
5573
5681
|
severity: "warning",
|
|
5574
5682
|
description: "Attestation is self-reported (no hardware root of trust)",
|
|
5575
|
-
mitigation: "TEE attestation planned for
|
|
5683
|
+
mitigation: "TEE attestation planned for a future release"
|
|
5576
5684
|
});
|
|
5577
5685
|
}
|
|
5578
5686
|
if (config.disclosure.proof_system === "commitment-only") {
|
|
@@ -5586,6 +5694,11 @@ function generateSHR(identityId, opts) {
|
|
|
5586
5694
|
}
|
|
5587
5695
|
const body = {
|
|
5588
5696
|
shr_version: "1.0",
|
|
5697
|
+
implementation: {
|
|
5698
|
+
sanctuary_version: config.version,
|
|
5699
|
+
node_version: process.versions.node,
|
|
5700
|
+
generated_by: "sanctuary-mcp-server"
|
|
5701
|
+
},
|
|
5589
5702
|
instance_id: identity.identity_id,
|
|
5590
5703
|
generated_at: now.toISOString(),
|
|
5591
5704
|
expires_at: expiresAt.toISOString(),
|
|
@@ -5716,6 +5829,245 @@ function assessSovereigntyLevel(body) {
|
|
|
5716
5829
|
return "minimal";
|
|
5717
5830
|
}
|
|
5718
5831
|
|
|
5832
|
+
// src/shr/gateway-adapter.ts
|
|
5833
|
+
var LAYER_WEIGHTS = {
|
|
5834
|
+
l1: 100,
|
|
5835
|
+
l2: 100,
|
|
5836
|
+
l3: 100,
|
|
5837
|
+
l4: 100
|
|
5838
|
+
};
|
|
5839
|
+
var DEGRADATION_IMPACT = {
|
|
5840
|
+
critical: 40,
|
|
5841
|
+
warning: 25,
|
|
5842
|
+
info: 10
|
|
5843
|
+
};
|
|
5844
|
+
function transformSHRForGateway(shr) {
|
|
5845
|
+
const { body, signed_by, signature } = shr;
|
|
5846
|
+
const layerScores = calculateLayerScores(body);
|
|
5847
|
+
const overallScore = calculateOverallScore(layerScores);
|
|
5848
|
+
const trustLevel = determineTrustLevel(overallScore);
|
|
5849
|
+
const signals = extractAuthorizationSignals(body);
|
|
5850
|
+
const degradations = transformDegradations(body.degradations);
|
|
5851
|
+
const constraints = generateAuthorizationConstraints(body);
|
|
5852
|
+
return {
|
|
5853
|
+
shr_version: body.shr_version,
|
|
5854
|
+
agent_identity: signed_by,
|
|
5855
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5856
|
+
context_expires_at: body.expires_at,
|
|
5857
|
+
overall_score: overallScore,
|
|
5858
|
+
recommended_trust_level: trustLevel,
|
|
5859
|
+
layer_scores: {
|
|
5860
|
+
l1_cognitive: layerScores.l1,
|
|
5861
|
+
l2_operational: layerScores.l2,
|
|
5862
|
+
l3_disclosure: layerScores.l3,
|
|
5863
|
+
l4_reputation: layerScores.l4
|
|
5864
|
+
},
|
|
5865
|
+
layer_status: {
|
|
5866
|
+
l1_cognitive: body.layers.l1.status,
|
|
5867
|
+
l2_operational: body.layers.l2.status,
|
|
5868
|
+
l3_disclosure: body.layers.l3.status,
|
|
5869
|
+
l4_reputation: body.layers.l4.status
|
|
5870
|
+
},
|
|
5871
|
+
authorization_signals: signals,
|
|
5872
|
+
degradations,
|
|
5873
|
+
recommended_constraints: constraints,
|
|
5874
|
+
shr_signature: signature,
|
|
5875
|
+
shr_signed_by: signed_by
|
|
5876
|
+
};
|
|
5877
|
+
}
|
|
5878
|
+
function calculateLayerScores(body) {
|
|
5879
|
+
const layers = body.layers;
|
|
5880
|
+
const degradations = body.degradations;
|
|
5881
|
+
let l1Score = LAYER_WEIGHTS.l1;
|
|
5882
|
+
let l2Score = LAYER_WEIGHTS.l2;
|
|
5883
|
+
let l3Score = LAYER_WEIGHTS.l3;
|
|
5884
|
+
let l4Score = LAYER_WEIGHTS.l4;
|
|
5885
|
+
for (const deg of degradations) {
|
|
5886
|
+
const impact = DEGRADATION_IMPACT[deg.severity] || 10;
|
|
5887
|
+
if (deg.layer === "l1") {
|
|
5888
|
+
l1Score = Math.max(0, l1Score - impact);
|
|
5889
|
+
} else if (deg.layer === "l2") {
|
|
5890
|
+
l2Score = Math.max(0, l2Score - impact);
|
|
5891
|
+
} else if (deg.layer === "l3") {
|
|
5892
|
+
l3Score = Math.max(0, l3Score - impact);
|
|
5893
|
+
} else if (deg.layer === "l4") {
|
|
5894
|
+
l4Score = Math.max(0, l4Score - impact);
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
if (layers.l1.status === "active" && l1Score > 50) l1Score = Math.min(100, l1Score + 5);
|
|
5898
|
+
if (layers.l2.status === "active" && l2Score > 50) l2Score = Math.min(100, l2Score + 5);
|
|
5899
|
+
if (layers.l3.status === "active" && l3Score > 50) l3Score = Math.min(100, l3Score + 5);
|
|
5900
|
+
if (layers.l4.status === "active" && l4Score > 50) l4Score = Math.min(100, l4Score + 5);
|
|
5901
|
+
if (layers.l1.status === "inactive") l1Score = 0;
|
|
5902
|
+
if (layers.l2.status === "inactive") l2Score = 0;
|
|
5903
|
+
if (layers.l3.status === "inactive") l3Score = 0;
|
|
5904
|
+
if (layers.l4.status === "inactive") l4Score = 0;
|
|
5905
|
+
return {
|
|
5906
|
+
l1: Math.round(l1Score),
|
|
5907
|
+
l2: Math.round(l2Score),
|
|
5908
|
+
l3: Math.round(l3Score),
|
|
5909
|
+
l4: Math.round(l4Score)
|
|
5910
|
+
};
|
|
5911
|
+
}
|
|
5912
|
+
function calculateOverallScore(layerScores) {
|
|
5913
|
+
const average = (layerScores.l1 + layerScores.l2 + layerScores.l3 + layerScores.l4) / 4;
|
|
5914
|
+
return Math.round(average);
|
|
5915
|
+
}
|
|
5916
|
+
function determineTrustLevel(score) {
|
|
5917
|
+
if (score >= 80) return "full";
|
|
5918
|
+
if (score >= 60) return "elevated";
|
|
5919
|
+
if (score >= 40) return "standard";
|
|
5920
|
+
return "restricted";
|
|
5921
|
+
}
|
|
5922
|
+
function extractAuthorizationSignals(body) {
|
|
5923
|
+
const l1 = body.layers.l1;
|
|
5924
|
+
const l3 = body.layers.l3;
|
|
5925
|
+
const l4 = body.layers.l4;
|
|
5926
|
+
return {
|
|
5927
|
+
approval_gate_active: body.capabilities.handshake,
|
|
5928
|
+
// Handshake implies human loop capability
|
|
5929
|
+
context_gating_active: body.capabilities.encrypted_channel,
|
|
5930
|
+
// Proxy for gating capability
|
|
5931
|
+
encryption_at_rest: l1.encryption !== "none" && l1.encryption !== "unencrypted",
|
|
5932
|
+
behavioral_baseline_active: false,
|
|
5933
|
+
// Would need explicit field in SHR v1.1
|
|
5934
|
+
identity_verified: l1.identity_type === "ed25519" || l1.identity_type !== "none",
|
|
5935
|
+
zero_knowledge_capable: l3.status === "active" && l3.proof_system !== "commitment-only",
|
|
5936
|
+
selective_disclosure_active: l3.selective_disclosure,
|
|
5937
|
+
reputation_portable: l4.reputation_portable,
|
|
5938
|
+
handshake_capable: body.capabilities.handshake
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5941
|
+
function transformDegradations(degradations) {
|
|
5942
|
+
return degradations.map((deg) => {
|
|
5943
|
+
let authzImpact = "";
|
|
5944
|
+
if (deg.code === "NO_TEE") {
|
|
5945
|
+
authzImpact = "Restricted to read-only operations until TEE available";
|
|
5946
|
+
} else if (deg.code === "PROCESS_ISOLATION_ONLY") {
|
|
5947
|
+
authzImpact = "Requires additional identity verification";
|
|
5948
|
+
} else if (deg.code === "COMMITMENT_ONLY") {
|
|
5949
|
+
authzImpact = "Limited data sharing scope \u2014 no zero-knowledge proofs";
|
|
5950
|
+
} else if (deg.code === "NO_ZK_PROOFS") {
|
|
5951
|
+
authzImpact = "Cannot perform confidential disclosures";
|
|
5952
|
+
} else if (deg.code === "SELF_REPORTED_ATTESTATION") {
|
|
5953
|
+
authzImpact = "Attestation trust degraded \u2014 human verification recommended";
|
|
5954
|
+
} else if (deg.code === "NO_SELECTIVE_DISCLOSURE") {
|
|
5955
|
+
authzImpact = "Must share entire data context, cannot redact";
|
|
5956
|
+
} else if (deg.code === "BASIC_SYBIL_ONLY") {
|
|
5957
|
+
authzImpact = "Restrict to interactions with known agents only";
|
|
5958
|
+
} else {
|
|
5959
|
+
authzImpact = "Unknown authorization impact";
|
|
5960
|
+
}
|
|
5961
|
+
return {
|
|
5962
|
+
layer: deg.layer,
|
|
5963
|
+
code: deg.code,
|
|
5964
|
+
severity: deg.severity,
|
|
5965
|
+
description: deg.description,
|
|
5966
|
+
authorization_impact: authzImpact
|
|
5967
|
+
};
|
|
5968
|
+
});
|
|
5969
|
+
}
|
|
5970
|
+
function generateAuthorizationConstraints(body, _degradations) {
|
|
5971
|
+
const constraints = [];
|
|
5972
|
+
const layers = body.layers;
|
|
5973
|
+
if (layers.l1.status === "degraded" || layers.l1.key_custody !== "self") {
|
|
5974
|
+
constraints.push({
|
|
5975
|
+
type: "identity_verification_required",
|
|
5976
|
+
description: "Additional identity verification required for sensitive operations",
|
|
5977
|
+
rationale: "L1 is degraded or key custody is not self-managed",
|
|
5978
|
+
priority: "high"
|
|
5979
|
+
});
|
|
5980
|
+
}
|
|
5981
|
+
if (!layers.l1.state_portable) {
|
|
5982
|
+
constraints.push({
|
|
5983
|
+
type: "location_bound",
|
|
5984
|
+
description: "Agent state is not portable \u2014 restrict to home environment",
|
|
5985
|
+
rationale: "State cannot be safely migrated across boundaries",
|
|
5986
|
+
priority: "medium"
|
|
5987
|
+
});
|
|
5988
|
+
}
|
|
5989
|
+
if (layers.l2.status === "degraded" || layers.l2.isolation_type === "local-process") {
|
|
5990
|
+
constraints.push({
|
|
5991
|
+
type: "read_only",
|
|
5992
|
+
description: "Restrict to read-only operations until operational isolation improves",
|
|
5993
|
+
rationale: "L2 isolation is process-level only (no TEE)",
|
|
5994
|
+
priority: "high"
|
|
5995
|
+
});
|
|
5996
|
+
}
|
|
5997
|
+
if (!layers.l2.attestation_available) {
|
|
5998
|
+
constraints.push({
|
|
5999
|
+
type: "requires_approval",
|
|
6000
|
+
description: "Human approval required for writes and sensitive reads",
|
|
6001
|
+
rationale: "No attestation available \u2014 self-reported integrity only",
|
|
6002
|
+
priority: "high"
|
|
6003
|
+
});
|
|
6004
|
+
}
|
|
6005
|
+
if (layers.l3.status === "degraded" || !layers.l3.selective_disclosure) {
|
|
6006
|
+
constraints.push({
|
|
6007
|
+
type: "restricted_scope",
|
|
6008
|
+
description: "Limit data sharing to minimal required scope \u2014 no selective disclosure",
|
|
6009
|
+
rationale: "Agent cannot redact data or prove predicates without revealing all context",
|
|
6010
|
+
priority: "high"
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
6013
|
+
if (layers.l3.proof_system === "commitment-only") {
|
|
6014
|
+
constraints.push({
|
|
6015
|
+
type: "restricted_scope",
|
|
6016
|
+
description: "No zero-knowledge proofs available \u2014 entire state context may be visible",
|
|
6017
|
+
rationale: "Proof system is commitment-only (no ZK)",
|
|
6018
|
+
priority: "medium"
|
|
6019
|
+
});
|
|
6020
|
+
}
|
|
6021
|
+
if (layers.l4.status === "degraded") {
|
|
6022
|
+
constraints.push({
|
|
6023
|
+
type: "known_agents_only",
|
|
6024
|
+
description: "Restrict interactions to known, pre-approved agents",
|
|
6025
|
+
rationale: "Reputation layer is degraded",
|
|
6026
|
+
priority: "medium"
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
if (!layers.l4.reputation_portable) {
|
|
6030
|
+
constraints.push({
|
|
6031
|
+
type: "location_bound",
|
|
6032
|
+
description: "Reputation is not portable \u2014 restrict to home environment",
|
|
6033
|
+
rationale: "Cannot present reputation to external parties",
|
|
6034
|
+
priority: "low"
|
|
6035
|
+
});
|
|
6036
|
+
}
|
|
6037
|
+
const layerScores = calculateLayerScores(body);
|
|
6038
|
+
const overallScore = calculateOverallScore(layerScores);
|
|
6039
|
+
if (overallScore < 40) {
|
|
6040
|
+
constraints.push({
|
|
6041
|
+
type: "restricted_scope",
|
|
6042
|
+
description: "Overall sovereignty score below threshold \u2014 restrict to non-sensitive operations",
|
|
6043
|
+
rationale: `Overall sovereignty score is ${overallScore}/100`,
|
|
6044
|
+
priority: "high"
|
|
6045
|
+
});
|
|
6046
|
+
}
|
|
6047
|
+
return constraints;
|
|
6048
|
+
}
|
|
6049
|
+
function transformSHRGeneric(shr) {
|
|
6050
|
+
const context = transformSHRForGateway(shr);
|
|
6051
|
+
return {
|
|
6052
|
+
agent_id: context.agent_identity,
|
|
6053
|
+
sovereignty_score: context.overall_score,
|
|
6054
|
+
trust_level: context.recommended_trust_level,
|
|
6055
|
+
layer_scores: {
|
|
6056
|
+
l1: context.layer_scores.l1_cognitive,
|
|
6057
|
+
l2: context.layer_scores.l2_operational,
|
|
6058
|
+
l3: context.layer_scores.l3_disclosure,
|
|
6059
|
+
l4: context.layer_scores.l4_reputation
|
|
6060
|
+
},
|
|
6061
|
+
capabilities: context.authorization_signals,
|
|
6062
|
+
constraints: context.recommended_constraints.map((c) => ({
|
|
6063
|
+
type: c.type,
|
|
6064
|
+
description: c.description
|
|
6065
|
+
})),
|
|
6066
|
+
expires_at: context.context_expires_at,
|
|
6067
|
+
signature: context.shr_signature
|
|
6068
|
+
};
|
|
6069
|
+
}
|
|
6070
|
+
|
|
5719
6071
|
// src/shr/tools.ts
|
|
5720
6072
|
function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
5721
6073
|
const generatorOpts = {
|
|
@@ -5778,6 +6130,53 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
5778
6130
|
);
|
|
5779
6131
|
return toolResult(result);
|
|
5780
6132
|
}
|
|
6133
|
+
},
|
|
6134
|
+
{
|
|
6135
|
+
name: "sanctuary/shr_gateway_export",
|
|
6136
|
+
description: "Export this instance's Sovereignty Health Report formatted for Ping Identity's Agent Gateway or other identity providers. Transforms the SHR into an authorization context with sovereignty scores, capability flags, and recommended access constraints.",
|
|
6137
|
+
inputSchema: {
|
|
6138
|
+
type: "object",
|
|
6139
|
+
properties: {
|
|
6140
|
+
format: {
|
|
6141
|
+
type: "string",
|
|
6142
|
+
enum: ["ping", "generic"],
|
|
6143
|
+
description: "Output format: 'ping' (Ping Identity Gateway format) or 'generic' (format-agnostic). Default: 'ping'."
|
|
6144
|
+
},
|
|
6145
|
+
identity_id: {
|
|
6146
|
+
type: "string",
|
|
6147
|
+
description: "Identity to sign the SHR with. Defaults to primary identity."
|
|
6148
|
+
},
|
|
6149
|
+
validity_minutes: {
|
|
6150
|
+
type: "number",
|
|
6151
|
+
description: "How long the SHR is valid (minutes). Default: 60."
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
},
|
|
6155
|
+
handler: async (args) => {
|
|
6156
|
+
const format = args.format || "ping";
|
|
6157
|
+
const validityMs = args.validity_minutes ? args.validity_minutes * 60 * 1e3 : void 0;
|
|
6158
|
+
const shrResult = generateSHR(args.identity_id, {
|
|
6159
|
+
...generatorOpts,
|
|
6160
|
+
validityMs
|
|
6161
|
+
});
|
|
6162
|
+
if (typeof shrResult === "string") {
|
|
6163
|
+
return toolResult({ error: shrResult });
|
|
6164
|
+
}
|
|
6165
|
+
let context;
|
|
6166
|
+
if (format === "generic") {
|
|
6167
|
+
context = transformSHRGeneric(shrResult);
|
|
6168
|
+
} else {
|
|
6169
|
+
context = transformSHRForGateway(shrResult);
|
|
6170
|
+
}
|
|
6171
|
+
auditLog.append(
|
|
6172
|
+
"l2",
|
|
6173
|
+
"shr_gateway_export",
|
|
6174
|
+
shrResult.body.instance_id,
|
|
6175
|
+
void 0,
|
|
6176
|
+
"success"
|
|
6177
|
+
);
|
|
6178
|
+
return toolResult(context);
|
|
6179
|
+
}
|
|
5781
6180
|
}
|
|
5782
6181
|
];
|
|
5783
6182
|
return { tools };
|
|
@@ -7056,9 +7455,11 @@ var L1_INTEGRITY_VERIFICATION = 8;
|
|
|
7056
7455
|
var L1_STATE_PORTABLE = 7;
|
|
7057
7456
|
var L2_THREE_TIER_GATE = 10;
|
|
7058
7457
|
var L2_BINARY_GATE = 3;
|
|
7059
|
-
var L2_ANOMALY_DETECTION =
|
|
7060
|
-
var L2_ENCRYPTED_AUDIT =
|
|
7061
|
-
var L2_TOOL_SANDBOXING =
|
|
7458
|
+
var L2_ANOMALY_DETECTION = 5;
|
|
7459
|
+
var L2_ENCRYPTED_AUDIT = 4;
|
|
7460
|
+
var L2_TOOL_SANDBOXING = 2;
|
|
7461
|
+
var L2_CONTEXT_GATING = 4;
|
|
7462
|
+
var L2_PROCESS_HARDENING = 5;
|
|
7062
7463
|
var L3_COMMITMENT_SCHEME = 8;
|
|
7063
7464
|
var L3_ZK_PROOFS = 7;
|
|
7064
7465
|
var L3_DISCLOSURE_POLICIES = 5;
|
|
@@ -7072,6 +7473,35 @@ var SEVERITY_ORDER = {
|
|
|
7072
7473
|
medium: 2,
|
|
7073
7474
|
low: 3
|
|
7074
7475
|
};
|
|
7476
|
+
var INCIDENT_META_SEV1 = {
|
|
7477
|
+
id: "META-SEV1-2026",
|
|
7478
|
+
name: "Meta Sev 1: Unauthorized autonomous data exposure",
|
|
7479
|
+
date: "2026-03-18",
|
|
7480
|
+
description: "AI agent autonomously posted proprietary code, business strategies, and user datasets to an internal forum without human approval. Two-hour exposure window."
|
|
7481
|
+
};
|
|
7482
|
+
var INCIDENT_OPENCLAW_SANDBOX = {
|
|
7483
|
+
id: "OPENCLAW-CVE-2026",
|
|
7484
|
+
name: "OpenClaw sandbox escape via privilege inheritance",
|
|
7485
|
+
date: "2026-03-18",
|
|
7486
|
+
description: "Nine CVEs in four days. Child processes inherited sandbox.mode=off from parent, bypassing runtime confinement. 42,900+ internet-exposed instances, 15,200 vulnerable to RCE.",
|
|
7487
|
+
cves: [
|
|
7488
|
+
"CVE-2026-32048",
|
|
7489
|
+
"CVE-2026-32915",
|
|
7490
|
+
"CVE-2026-32918"
|
|
7491
|
+
]
|
|
7492
|
+
};
|
|
7493
|
+
var INCIDENT_CONTEXT_LEAKAGE = {
|
|
7494
|
+
id: "CONTEXT-LEAK-CLASS",
|
|
7495
|
+
name: "Context leakage: Full state exposure to inference providers",
|
|
7496
|
+
date: "2026-03",
|
|
7497
|
+
description: "Agents send full context \u2014 conversation history, memory, secrets, internal reasoning \u2014 to remote LLM providers on every inference call with no filtering mechanism."
|
|
7498
|
+
};
|
|
7499
|
+
var INCIDENT_CLAUDE_CODE_LEAK = {
|
|
7500
|
+
id: "CLAUDE-CODE-LEAK-2026",
|
|
7501
|
+
name: "Claude Code source leak: 512K lines exposed via npm source map",
|
|
7502
|
+
date: "2026-03-31",
|
|
7503
|
+
description: "Anthropic accidentally shipped a 59.8 MB source map in npm package v2.1.88, exposing the full Claude Code TypeScript source \u2014 1,900 files, internal model codenames, unreleased features, OAuth flows, and multi-agent coordination logic."
|
|
7504
|
+
};
|
|
7075
7505
|
function analyzeSovereignty(env, config) {
|
|
7076
7506
|
const l1 = assessL1(env, config);
|
|
7077
7507
|
const l2 = assessL2(env);
|
|
@@ -7144,14 +7574,18 @@ function assessL2(env, _config) {
|
|
|
7144
7574
|
let auditTrailEncrypted = false;
|
|
7145
7575
|
let auditTrailExists = false;
|
|
7146
7576
|
let toolSandboxing = "none";
|
|
7577
|
+
let contextGating = false;
|
|
7578
|
+
let processIsolationHardening = "none";
|
|
7147
7579
|
if (sanctuaryActive) {
|
|
7148
7580
|
approvalGate = "three-tier";
|
|
7149
7581
|
behavioralAnomalyDetection = true;
|
|
7150
7582
|
auditTrailEncrypted = true;
|
|
7151
7583
|
auditTrailExists = true;
|
|
7584
|
+
contextGating = true;
|
|
7152
7585
|
findings.push("Three-tier Principal Policy gate active");
|
|
7153
7586
|
findings.push("Behavioral anomaly detection (BaselineTracker) enabled");
|
|
7154
7587
|
findings.push("Encrypted audit trail active");
|
|
7588
|
+
findings.push("Context gating available (sanctuary/context_gate_set_policy)");
|
|
7155
7589
|
}
|
|
7156
7590
|
if (env.openclaw_detected && env.openclaw_config) {
|
|
7157
7591
|
if (env.openclaw_config.require_approval_enabled) {
|
|
@@ -7169,6 +7603,7 @@ function assessL2(env, _config) {
|
|
|
7169
7603
|
);
|
|
7170
7604
|
}
|
|
7171
7605
|
}
|
|
7606
|
+
processIsolationHardening = "none";
|
|
7172
7607
|
const status = approvalGate === "three-tier" && auditTrailEncrypted ? "active" : approvalGate !== "none" || auditTrailExists ? "partial" : "inactive";
|
|
7173
7608
|
return {
|
|
7174
7609
|
status,
|
|
@@ -7177,6 +7612,8 @@ function assessL2(env, _config) {
|
|
|
7177
7612
|
audit_trail_encrypted: auditTrailEncrypted,
|
|
7178
7613
|
audit_trail_exists: auditTrailExists,
|
|
7179
7614
|
tool_sandboxing: sanctuaryActive ? "policy-enforced" : toolSandboxing,
|
|
7615
|
+
context_gating: contextGating,
|
|
7616
|
+
process_isolation_hardening: processIsolationHardening,
|
|
7180
7617
|
findings
|
|
7181
7618
|
};
|
|
7182
7619
|
}
|
|
@@ -7191,8 +7628,10 @@ function assessL3(env, _config) {
|
|
|
7191
7628
|
zkProofs = true;
|
|
7192
7629
|
selectiveDisclosurePolicy = true;
|
|
7193
7630
|
findings.push("SHA-256 + Pedersen commitment schemes active");
|
|
7194
|
-
findings.push("Schnorr
|
|
7631
|
+
findings.push("Schnorr zero-knowledge proofs (Fiat-Shamir) enabled \u2014 genuine ZK proofs");
|
|
7632
|
+
findings.push("Range proofs (bit-decomposition + OR-proofs) enabled \u2014 genuine ZK proofs");
|
|
7195
7633
|
findings.push("Selective disclosure policies configurable");
|
|
7634
|
+
findings.push("Non-interactive proofs with replay-resistant domain separation");
|
|
7196
7635
|
}
|
|
7197
7636
|
const status = commitmentScheme === "pedersen+sha256" && zkProofs ? "active" : commitmentScheme !== "none" ? "partial" : "inactive";
|
|
7198
7637
|
return {
|
|
@@ -7244,6 +7683,9 @@ function scoreL2(l2) {
|
|
|
7244
7683
|
if (l2.audit_trail_encrypted) score += L2_ENCRYPTED_AUDIT;
|
|
7245
7684
|
if (l2.tool_sandboxing === "policy-enforced") score += L2_TOOL_SANDBOXING;
|
|
7246
7685
|
else if (l2.tool_sandboxing === "basic") score += 1;
|
|
7686
|
+
if (l2.context_gating) score += L2_CONTEXT_GATING;
|
|
7687
|
+
if (l2.process_isolation_hardening === "hardened") score += L2_PROCESS_HARDENING;
|
|
7688
|
+
else if (l2.process_isolation_hardening === "basic") score += 2;
|
|
7247
7689
|
return score;
|
|
7248
7690
|
}
|
|
7249
7691
|
function scoreL3(l3) {
|
|
@@ -7273,7 +7715,8 @@ function generateGaps(env, l1, l2, l3, l4) {
|
|
|
7273
7715
|
title: "Agent memory stored in plaintext",
|
|
7274
7716
|
description: "Your agent's memory (MEMORY.md, daily notes, SQLite index) is stored in plaintext at ~/.openclaw/workspace/. Any process with file access can read your agent's full context \u2014 preferences, decisions, conversation history.",
|
|
7275
7717
|
openclaw_relevance: "Stock OpenClaw stores all agent memory in plaintext files. There is no built-in encryption for agent state.",
|
|
7276
|
-
sanctuary_solution: "Sanctuary encrypts all state at rest with AES-256-GCM using a key derived from Argon2id, making state opaque to any process that doesn't hold the master key. Use sanctuary/state_write to migrate sensitive state to the encrypted store."
|
|
7718
|
+
sanctuary_solution: "Sanctuary encrypts all state at rest with AES-256-GCM using a key derived from Argon2id, making state opaque to any process that doesn't hold the master key. Use sanctuary/state_write to migrate sensitive state to the encrypted store.",
|
|
7719
|
+
incident_class: INCIDENT_META_SEV1
|
|
7277
7720
|
});
|
|
7278
7721
|
}
|
|
7279
7722
|
if (oc && oc.env_file_exposed) {
|
|
@@ -7306,7 +7749,8 @@ function generateGaps(env, l1, l2, l3, l4) {
|
|
|
7306
7749
|
title: "Binary approval gate (no anomaly detection)",
|
|
7307
7750
|
description: "Your approval gate provides binary approve/deny gating without behavioral anomaly detection. Routine operations require the same manual approval as sensitive ones.",
|
|
7308
7751
|
openclaw_relevance: env.openclaw_detected ? "OpenClaw's requireApproval hook provides binary approve/deny gating. Sanctuary's three-tier Principal Policy adds behavioral anomaly detection (auto-escalation when agent behavior deviates from baseline), encrypted audit trails, and graduated approval tiers \u2014 so routine operations auto-proceed while sensitive operations require explicit consent." : null,
|
|
7309
|
-
sanctuary_solution: "Sanctuary's three-tier Principal Policy gate auto-allows routine operations (Tier 3), escalates anomalous behavior (Tier 2), and always requires human approval for irreversible operations (Tier 1). Use sanctuary/principal_policy_view to inspect."
|
|
7752
|
+
sanctuary_solution: "Sanctuary's three-tier Principal Policy gate auto-allows routine operations (Tier 3), escalates anomalous behavior (Tier 2), and always requires human approval for irreversible operations (Tier 1). Use sanctuary/principal_policy_view to inspect.",
|
|
7753
|
+
incident_class: INCIDENT_META_SEV1
|
|
7310
7754
|
});
|
|
7311
7755
|
} else if (l2.approval_gate === "none") {
|
|
7312
7756
|
gaps.push({
|
|
@@ -7316,7 +7760,8 @@ function generateGaps(env, l1, l2, l3, l4) {
|
|
|
7316
7760
|
title: "No approval gate",
|
|
7317
7761
|
description: "No approval gate is configured. All tool calls execute without oversight.",
|
|
7318
7762
|
openclaw_relevance: null,
|
|
7319
|
-
sanctuary_solution: "Sanctuary's Principal Policy evaluates every tool call before execution. Enable it to get three-tier approval gating with behavioral anomaly detection."
|
|
7763
|
+
sanctuary_solution: "Sanctuary's Principal Policy evaluates every tool call before execution. Enable it to get three-tier approval gating with behavioral anomaly detection.",
|
|
7764
|
+
incident_class: INCIDENT_META_SEV1
|
|
7320
7765
|
});
|
|
7321
7766
|
}
|
|
7322
7767
|
if (l2.tool_sandboxing === "basic") {
|
|
@@ -7327,18 +7772,32 @@ function generateGaps(env, l1, l2, l3, l4) {
|
|
|
7327
7772
|
title: "Basic tool sandboxing (no cryptographic attestation)",
|
|
7328
7773
|
description: "Your tool sandbox enforces allow/deny lists but provides no cryptographic attestation of execution context.",
|
|
7329
7774
|
openclaw_relevance: env.openclaw_detected ? "OpenClaw's sandbox tool policy (tools.sandbox.tools) enforces allow/deny lists. Sanctuary adds cryptographic attestation of execution context \u2014 a verifiable proof that an operation ran within policy, not just that a policy was configured." : null,
|
|
7330
|
-
sanctuary_solution: "Sanctuary provides cryptographic execution attestation via sanctuary/exec_attest and policy-enforced sandboxing with encrypted audit trails."
|
|
7775
|
+
sanctuary_solution: "Sanctuary provides cryptographic execution attestation via sanctuary/exec_attest and policy-enforced sandboxing with encrypted audit trails.",
|
|
7776
|
+
incident_class: INCIDENT_OPENCLAW_SANDBOX
|
|
7331
7777
|
});
|
|
7332
7778
|
}
|
|
7333
|
-
if (!l2.
|
|
7779
|
+
if (!l2.context_gating) {
|
|
7334
7780
|
gaps.push({
|
|
7335
7781
|
id: "GAP-L2-003",
|
|
7336
7782
|
layer: "L2",
|
|
7337
7783
|
severity: "high",
|
|
7784
|
+
title: "No context gating for outbound inference calls",
|
|
7785
|
+
description: "Your agent sends its full context \u2014 conversation history, memory, preferences, internal reasoning \u2014 to remote LLM providers on every inference call. There is no mechanism to filter what leaves the sovereignty boundary. The provider sees everything the agent knows.",
|
|
7786
|
+
openclaw_relevance: env.openclaw_detected ? "OpenClaw sends full agent context (including MEMORY.md, tool results, and conversation history) to the configured LLM provider with every API call. There is no built-in context filtering." : null,
|
|
7787
|
+
sanctuary_solution: "Sanctuary's context gating (sanctuary/context_gate_set_policy + sanctuary/context_gate_filter) lets you define per-provider policies that control exactly what context flows outbound. Redact secrets, hash identifiers, and send only minimum-necessary context for each call.",
|
|
7788
|
+
incident_class: INCIDENT_CONTEXT_LEAKAGE
|
|
7789
|
+
});
|
|
7790
|
+
}
|
|
7791
|
+
if (!l2.audit_trail_exists) {
|
|
7792
|
+
gaps.push({
|
|
7793
|
+
id: "GAP-L2-004",
|
|
7794
|
+
layer: "L2",
|
|
7795
|
+
severity: "high",
|
|
7338
7796
|
title: "No audit trail",
|
|
7339
7797
|
description: "No audit trail exists for tool call history. There is no record of what operations were executed, when, or by whom.",
|
|
7340
7798
|
openclaw_relevance: null,
|
|
7341
|
-
sanctuary_solution: "Sanctuary maintains an encrypted audit log of all operations, queryable via sanctuary/monitor_audit_log."
|
|
7799
|
+
sanctuary_solution: "Sanctuary maintains an encrypted audit log of all operations, queryable via sanctuary/monitor_audit_log.",
|
|
7800
|
+
incident_class: INCIDENT_CLAUDE_CODE_LEAK
|
|
7342
7801
|
});
|
|
7343
7802
|
}
|
|
7344
7803
|
if (l3.commitment_scheme === "none") {
|
|
@@ -7347,9 +7806,10 @@ function generateGaps(env, l1, l2, l3, l4) {
|
|
|
7347
7806
|
layer: "L3",
|
|
7348
7807
|
severity: "high",
|
|
7349
7808
|
title: "No selective disclosure capability",
|
|
7350
|
-
description: "Your agent has no
|
|
7809
|
+
description: "Your agent has no cryptographic mechanism to prove facts about its state without revealing the state itself. Every disclosure is all-or-nothing: no commitments, no zero-knowledge proofs, no selective disclosure policies.",
|
|
7351
7810
|
openclaw_relevance: env.openclaw_detected ? "OpenClaw has no selective disclosure mechanism. When your agent shares information, it shares everything or nothing \u2014 there is no way to prove a claim without revealing the underlying data." : null,
|
|
7352
|
-
sanctuary_solution: "Sanctuary's L3 provides SHA-256 + Pedersen commitments
|
|
7811
|
+
sanctuary_solution: "Sanctuary's L3 provides SHA-256 + Pedersen commitments with genuine zero-knowledge proofs (Schnorr + range proofs via Fiat-Shamir transform). Your agent can prove it has a valid credential, sufficient reputation, or a completed transaction without exposing the underlying data. Use sanctuary/zk_commit and sanctuary/zk_prove.",
|
|
7812
|
+
incident_class: INCIDENT_META_SEV1
|
|
7353
7813
|
});
|
|
7354
7814
|
}
|
|
7355
7815
|
if (!l4.reputation_portable) {
|
|
@@ -7401,9 +7861,18 @@ function generateRecommendations(env, l1, l2, l3, l4) {
|
|
|
7401
7861
|
impact: "high"
|
|
7402
7862
|
});
|
|
7403
7863
|
}
|
|
7404
|
-
if (!
|
|
7864
|
+
if (!l2.context_gating) {
|
|
7405
7865
|
recs.push({
|
|
7406
7866
|
priority: 5,
|
|
7867
|
+
action: "Configure context gating to control what flows to LLM providers",
|
|
7868
|
+
tool: "sanctuary/context_gate_set_policy",
|
|
7869
|
+
effort: "minutes",
|
|
7870
|
+
impact: "high"
|
|
7871
|
+
});
|
|
7872
|
+
}
|
|
7873
|
+
if (!l4.reputation_signed) {
|
|
7874
|
+
recs.push({
|
|
7875
|
+
priority: 6,
|
|
7407
7876
|
action: "Start recording reputation attestations from completed interactions",
|
|
7408
7877
|
tool: "sanctuary/reputation_record",
|
|
7409
7878
|
effort: "minutes",
|
|
@@ -7412,7 +7881,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
|
|
|
7412
7881
|
}
|
|
7413
7882
|
if (!l3.selective_disclosure_policy) {
|
|
7414
7883
|
recs.push({
|
|
7415
|
-
priority:
|
|
7884
|
+
priority: 7,
|
|
7416
7885
|
action: "Configure selective disclosure policies for data sharing",
|
|
7417
7886
|
tool: "sanctuary/disclosure_set_policy",
|
|
7418
7887
|
effort: "hours",
|
|
@@ -7461,6 +7930,10 @@ function formatAuditReport(result) {
|
|
|
7461
7930
|
`;
|
|
7462
7931
|
report += ` \u2502 L2 Operational Isolation \u2502 ${padStatus(layers.l2_operational.status)} \u2502 ${padScore(l2Score, 25)} \u2502
|
|
7463
7932
|
`;
|
|
7933
|
+
if (layers.l2_operational.context_gating) {
|
|
7934
|
+
report += ` \u2502 \u2514 Context Gating \u2502 ACTIVE \u2502 \u2502
|
|
7935
|
+
`;
|
|
7936
|
+
}
|
|
7464
7937
|
report += ` \u2502 L3 Selective Disclosure \u2502 ${padStatus(layers.l3_selective_disclosure.status)} \u2502 ${padScore(l3Score, 20)} \u2502
|
|
7465
7938
|
`;
|
|
7466
7939
|
report += ` \u2502 L4 Verifiable Reputation \u2502 ${padStatus(layers.l4_reputation.status)} \u2502 ${padScore(l4Score, 20)} \u2502
|
|
@@ -7478,6 +7951,12 @@ function formatAuditReport(result) {
|
|
|
7478
7951
|
const descLines = wordWrap(gap.description, 66);
|
|
7479
7952
|
for (const line of descLines) {
|
|
7480
7953
|
report += ` ${line}
|
|
7954
|
+
`;
|
|
7955
|
+
}
|
|
7956
|
+
if (gap.incident_class) {
|
|
7957
|
+
const ic = gap.incident_class;
|
|
7958
|
+
const cveStr = ic.cves?.length ? ` (${ic.cves.join(", ")})` : "";
|
|
7959
|
+
report += ` \u2192 Incident precedent: ${ic.name}${cveStr} [${ic.date}]
|
|
7481
7960
|
`;
|
|
7482
7961
|
}
|
|
7483
7962
|
report += ` \u2192 Fix: ${gap.sanctuary_solution.split(".")[0]}.
|
|
@@ -7572,76 +8051,1552 @@ function createAuditTools(config) {
|
|
|
7572
8051
|
return { tools };
|
|
7573
8052
|
}
|
|
7574
8053
|
|
|
7575
|
-
// src/
|
|
8054
|
+
// src/l2-operational/context-gate.ts
|
|
7576
8055
|
init_encoding();
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
var
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
8056
|
+
init_hashing();
|
|
8057
|
+
var MAX_CONTEXT_FIELDS = 1e3;
|
|
8058
|
+
var MAX_POLICY_RULES = 50;
|
|
8059
|
+
var MAX_PATTERNS_PER_ARRAY = 500;
|
|
8060
|
+
function evaluateField(policy, provider, field) {
|
|
8061
|
+
const exactRule = policy.rules.find((r) => r.provider === provider);
|
|
8062
|
+
const wildcardRule = policy.rules.find((r) => r.provider === "*");
|
|
8063
|
+
const matchedRule = exactRule ?? wildcardRule;
|
|
8064
|
+
if (!matchedRule) {
|
|
8065
|
+
return {
|
|
8066
|
+
field,
|
|
8067
|
+
action: policy.default_action === "deny" ? "deny" : "redact",
|
|
8068
|
+
reason: `No rule matches provider "${provider}"; applying default (${policy.default_action})`
|
|
8069
|
+
};
|
|
7583
8070
|
}
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
}
|
|
8071
|
+
if (matchesPattern(field, matchedRule.redact)) {
|
|
8072
|
+
return {
|
|
8073
|
+
field,
|
|
8074
|
+
action: "redact",
|
|
8075
|
+
reason: `Field "${field}" is explicitly redacted for ${matchedRule.provider} provider`
|
|
8076
|
+
};
|
|
7590
8077
|
}
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
8078
|
+
if (matchesPattern(field, matchedRule.hash)) {
|
|
8079
|
+
return {
|
|
8080
|
+
field,
|
|
8081
|
+
action: "hash",
|
|
8082
|
+
reason: `Field "${field}" is hashed for ${matchedRule.provider} provider`
|
|
8083
|
+
};
|
|
7595
8084
|
}
|
|
7596
|
-
|
|
7597
|
-
return
|
|
8085
|
+
if (matchesPattern(field, matchedRule.summarize)) {
|
|
8086
|
+
return {
|
|
8087
|
+
field,
|
|
8088
|
+
action: "summarize",
|
|
8089
|
+
reason: `Field "${field}" should be summarized for ${matchedRule.provider} provider`
|
|
8090
|
+
};
|
|
7598
8091
|
}
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
if (prefix && !key.startsWith(prefix)) continue;
|
|
7606
|
-
entries.push({
|
|
7607
|
-
key,
|
|
7608
|
-
namespace,
|
|
7609
|
-
size_bytes: entry.data.length,
|
|
7610
|
-
modified_at: entry.modified_at
|
|
7611
|
-
});
|
|
7612
|
-
}
|
|
7613
|
-
return entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
8092
|
+
if (matchesPattern(field, matchedRule.allow)) {
|
|
8093
|
+
return {
|
|
8094
|
+
field,
|
|
8095
|
+
action: "allow",
|
|
8096
|
+
reason: `Field "${field}" is allowed for ${matchedRule.provider} provider`
|
|
8097
|
+
};
|
|
7614
8098
|
}
|
|
7615
|
-
|
|
7616
|
-
|
|
8099
|
+
return {
|
|
8100
|
+
field,
|
|
8101
|
+
action: policy.default_action === "deny" ? "deny" : "redact",
|
|
8102
|
+
reason: `Field "${field}" not addressed in ${matchedRule.provider} rule; applying default (${policy.default_action})`
|
|
8103
|
+
};
|
|
8104
|
+
}
|
|
8105
|
+
function filterContext(policy, provider, context) {
|
|
8106
|
+
const fields = Object.keys(context);
|
|
8107
|
+
if (fields.length > MAX_CONTEXT_FIELDS) {
|
|
8108
|
+
throw new Error(
|
|
8109
|
+
`Context object has ${fields.length} fields, exceeding limit of ${MAX_CONTEXT_FIELDS}`
|
|
8110
|
+
);
|
|
7617
8111
|
}
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
8112
|
+
const decisions = [];
|
|
8113
|
+
let allowed = 0;
|
|
8114
|
+
let redacted = 0;
|
|
8115
|
+
let hashed = 0;
|
|
8116
|
+
let summarized = 0;
|
|
8117
|
+
let denied = 0;
|
|
8118
|
+
for (const field of fields) {
|
|
8119
|
+
const result = evaluateField(policy, provider, field);
|
|
8120
|
+
if (result.action === "hash") {
|
|
8121
|
+
const value = typeof context[field] === "string" ? context[field] : JSON.stringify(context[field]);
|
|
8122
|
+
result.hash_value = hashToString(stringToBytes(value));
|
|
8123
|
+
}
|
|
8124
|
+
decisions.push(result);
|
|
8125
|
+
switch (result.action) {
|
|
8126
|
+
case "allow":
|
|
8127
|
+
allowed++;
|
|
8128
|
+
break;
|
|
8129
|
+
case "redact":
|
|
8130
|
+
redacted++;
|
|
8131
|
+
break;
|
|
8132
|
+
case "hash":
|
|
8133
|
+
hashed++;
|
|
8134
|
+
break;
|
|
8135
|
+
case "summarize":
|
|
8136
|
+
summarized++;
|
|
8137
|
+
break;
|
|
8138
|
+
case "deny":
|
|
8139
|
+
denied++;
|
|
8140
|
+
break;
|
|
7622
8141
|
}
|
|
7623
|
-
return total;
|
|
7624
8142
|
}
|
|
7625
|
-
|
|
7626
|
-
|
|
7627
|
-
|
|
8143
|
+
const originalHash = hashToString(
|
|
8144
|
+
stringToBytes(JSON.stringify(context))
|
|
8145
|
+
);
|
|
8146
|
+
const filteredOutput = {};
|
|
8147
|
+
for (const decision of decisions) {
|
|
8148
|
+
switch (decision.action) {
|
|
8149
|
+
case "allow":
|
|
8150
|
+
filteredOutput[decision.field] = context[decision.field];
|
|
8151
|
+
break;
|
|
8152
|
+
case "redact":
|
|
8153
|
+
filteredOutput[decision.field] = "[REDACTED]";
|
|
8154
|
+
break;
|
|
8155
|
+
case "hash":
|
|
8156
|
+
filteredOutput[decision.field] = `[HASH:${decision.hash_value}]`;
|
|
8157
|
+
break;
|
|
8158
|
+
case "summarize":
|
|
8159
|
+
filteredOutput[decision.field] = "[SUMMARIZE]";
|
|
8160
|
+
break;
|
|
8161
|
+
}
|
|
7628
8162
|
}
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
// src/index.ts
|
|
7632
|
-
async function createSanctuaryServer(options) {
|
|
7633
|
-
const config = await loadConfig(options?.configPath);
|
|
7634
|
-
await promises.mkdir(config.storage_path, { recursive: true, mode: 448 });
|
|
7635
|
-
const storage = options?.storage ?? new FilesystemStorage(
|
|
7636
|
-
`${config.storage_path}/state`
|
|
8163
|
+
const filteredHash = hashToString(
|
|
8164
|
+
stringToBytes(JSON.stringify(filteredOutput))
|
|
7637
8165
|
);
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
8166
|
+
return {
|
|
8167
|
+
policy_id: policy.policy_id,
|
|
8168
|
+
provider,
|
|
8169
|
+
fields_allowed: allowed,
|
|
8170
|
+
fields_redacted: redacted,
|
|
8171
|
+
fields_hashed: hashed,
|
|
8172
|
+
fields_summarized: summarized,
|
|
8173
|
+
fields_denied: denied,
|
|
8174
|
+
decisions,
|
|
8175
|
+
original_context_hash: originalHash,
|
|
8176
|
+
filtered_context_hash: filteredHash,
|
|
8177
|
+
filtered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
8178
|
+
};
|
|
8179
|
+
}
|
|
8180
|
+
function matchesPattern(field, patterns) {
|
|
8181
|
+
const normalizedField = field.toLowerCase();
|
|
8182
|
+
for (const pattern of patterns) {
|
|
8183
|
+
if (pattern === "*") return true;
|
|
8184
|
+
const normalizedPattern = pattern.toLowerCase();
|
|
8185
|
+
if (normalizedPattern === normalizedField) return true;
|
|
8186
|
+
if (normalizedPattern.endsWith("*") && normalizedField.startsWith(normalizedPattern.slice(0, -1))) return true;
|
|
8187
|
+
if (normalizedPattern.startsWith("*") && normalizedField.endsWith(normalizedPattern.slice(1))) return true;
|
|
8188
|
+
}
|
|
8189
|
+
return false;
|
|
8190
|
+
}
|
|
8191
|
+
var ContextGatePolicyStore = class {
|
|
8192
|
+
storage;
|
|
8193
|
+
encryptionKey;
|
|
8194
|
+
policies = /* @__PURE__ */ new Map();
|
|
8195
|
+
constructor(storage, masterKey) {
|
|
8196
|
+
this.storage = storage;
|
|
8197
|
+
this.encryptionKey = derivePurposeKey(masterKey, "l2-context-gate");
|
|
8198
|
+
}
|
|
8199
|
+
/**
|
|
8200
|
+
* Create and store a new context-gating policy.
|
|
8201
|
+
*/
|
|
8202
|
+
async create(policyName, rules, defaultAction, identityId) {
|
|
8203
|
+
const policyId = `cg-${Date.now()}-${toBase64url(randomBytes(8))}`;
|
|
8204
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8205
|
+
const policy = {
|
|
8206
|
+
policy_id: policyId,
|
|
8207
|
+
policy_name: policyName,
|
|
8208
|
+
rules,
|
|
8209
|
+
default_action: defaultAction,
|
|
8210
|
+
identity_id: identityId,
|
|
8211
|
+
created_at: now,
|
|
8212
|
+
updated_at: now
|
|
8213
|
+
};
|
|
8214
|
+
await this.persist(policy);
|
|
8215
|
+
this.policies.set(policyId, policy);
|
|
8216
|
+
return policy;
|
|
8217
|
+
}
|
|
8218
|
+
/**
|
|
8219
|
+
* Get a policy by ID.
|
|
8220
|
+
*/
|
|
8221
|
+
async get(policyId) {
|
|
8222
|
+
if (this.policies.has(policyId)) {
|
|
8223
|
+
return this.policies.get(policyId);
|
|
8224
|
+
}
|
|
8225
|
+
const raw = await this.storage.read("_context_gate_policies", policyId);
|
|
8226
|
+
if (!raw) return null;
|
|
8227
|
+
try {
|
|
8228
|
+
const encrypted = JSON.parse(bytesToString(raw));
|
|
8229
|
+
const decrypted = decrypt(encrypted, this.encryptionKey);
|
|
8230
|
+
const policy = JSON.parse(bytesToString(decrypted));
|
|
8231
|
+
this.policies.set(policyId, policy);
|
|
8232
|
+
return policy;
|
|
8233
|
+
} catch {
|
|
8234
|
+
return null;
|
|
8235
|
+
}
|
|
8236
|
+
}
|
|
8237
|
+
/**
|
|
8238
|
+
* List all context-gating policies.
|
|
8239
|
+
*/
|
|
8240
|
+
async list() {
|
|
8241
|
+
await this.loadAll();
|
|
8242
|
+
return Array.from(this.policies.values());
|
|
8243
|
+
}
|
|
8244
|
+
/**
|
|
8245
|
+
* Load all persisted policies into memory.
|
|
8246
|
+
*/
|
|
8247
|
+
async loadAll() {
|
|
8248
|
+
try {
|
|
8249
|
+
const entries = await this.storage.list("_context_gate_policies");
|
|
8250
|
+
for (const meta of entries) {
|
|
8251
|
+
if (this.policies.has(meta.key)) continue;
|
|
8252
|
+
const raw = await this.storage.read("_context_gate_policies", meta.key);
|
|
8253
|
+
if (!raw) continue;
|
|
8254
|
+
try {
|
|
8255
|
+
const encrypted = JSON.parse(bytesToString(raw));
|
|
8256
|
+
const decrypted = decrypt(encrypted, this.encryptionKey);
|
|
8257
|
+
const policy = JSON.parse(bytesToString(decrypted));
|
|
8258
|
+
this.policies.set(policy.policy_id, policy);
|
|
8259
|
+
} catch {
|
|
8260
|
+
}
|
|
8261
|
+
}
|
|
8262
|
+
} catch {
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
async persist(policy) {
|
|
8266
|
+
const serialized = stringToBytes(JSON.stringify(policy));
|
|
8267
|
+
const encrypted = encrypt(serialized, this.encryptionKey);
|
|
8268
|
+
await this.storage.write(
|
|
8269
|
+
"_context_gate_policies",
|
|
8270
|
+
policy.policy_id,
|
|
8271
|
+
stringToBytes(JSON.stringify(encrypted))
|
|
8272
|
+
);
|
|
8273
|
+
}
|
|
8274
|
+
};
|
|
8275
|
+
|
|
8276
|
+
// src/l2-operational/context-gate-templates.ts
|
|
8277
|
+
var ALWAYS_REDACT_SECRETS = [
|
|
8278
|
+
"api_key",
|
|
8279
|
+
"secret_*",
|
|
8280
|
+
"*_secret",
|
|
8281
|
+
"*_token",
|
|
8282
|
+
"*_key",
|
|
8283
|
+
"password",
|
|
8284
|
+
"*_password",
|
|
8285
|
+
"credential",
|
|
8286
|
+
"*_credential",
|
|
8287
|
+
"private_key",
|
|
8288
|
+
"recovery_key",
|
|
8289
|
+
"passphrase",
|
|
8290
|
+
"auth_*"
|
|
8291
|
+
];
|
|
8292
|
+
var PII_PATTERNS = [
|
|
8293
|
+
"*_pii",
|
|
8294
|
+
"name",
|
|
8295
|
+
"full_name",
|
|
8296
|
+
"email",
|
|
8297
|
+
"email_address",
|
|
8298
|
+
"phone",
|
|
8299
|
+
"phone_number",
|
|
8300
|
+
"address",
|
|
8301
|
+
"ssn",
|
|
8302
|
+
"date_of_birth",
|
|
8303
|
+
"ip_address",
|
|
8304
|
+
"credit_card",
|
|
8305
|
+
"card_number",
|
|
8306
|
+
"cvv",
|
|
8307
|
+
"bank_account",
|
|
8308
|
+
"account_number",
|
|
8309
|
+
"routing_number"
|
|
8310
|
+
];
|
|
8311
|
+
var INTERNAL_STATE_PATTERNS = [
|
|
8312
|
+
"memory",
|
|
8313
|
+
"agent_memory",
|
|
8314
|
+
"internal_reasoning",
|
|
8315
|
+
"internal_state",
|
|
8316
|
+
"reasoning_trace",
|
|
8317
|
+
"chain_of_thought",
|
|
8318
|
+
"private_notes",
|
|
8319
|
+
"soul",
|
|
8320
|
+
"personality",
|
|
8321
|
+
"system_prompt"
|
|
8322
|
+
];
|
|
8323
|
+
var ID_PATTERNS = [
|
|
8324
|
+
"user_id",
|
|
8325
|
+
"session_id",
|
|
8326
|
+
"agent_id",
|
|
8327
|
+
"identity_id",
|
|
8328
|
+
"conversation_id",
|
|
8329
|
+
"thread_id"
|
|
8330
|
+
];
|
|
8331
|
+
var HISTORY_PATTERNS = [
|
|
8332
|
+
"conversation_history",
|
|
8333
|
+
"message_history",
|
|
8334
|
+
"chat_history",
|
|
8335
|
+
"context_window",
|
|
8336
|
+
"previous_messages"
|
|
8337
|
+
];
|
|
8338
|
+
var INFERENCE_MINIMAL = {
|
|
8339
|
+
id: "inference-minimal",
|
|
8340
|
+
name: "Inference Minimal",
|
|
8341
|
+
description: "Maximum privacy. Only the current task and query reach the LLM provider.",
|
|
8342
|
+
use_when: "You want the strictest possible context control for inference calls. The LLM sees only what it needs for the immediate task.",
|
|
8343
|
+
rules: [
|
|
8344
|
+
{
|
|
8345
|
+
provider: "inference",
|
|
8346
|
+
allow: [
|
|
8347
|
+
"task",
|
|
8348
|
+
"task_description",
|
|
8349
|
+
"current_query",
|
|
8350
|
+
"query",
|
|
8351
|
+
"prompt",
|
|
8352
|
+
"question",
|
|
8353
|
+
"instruction"
|
|
8354
|
+
],
|
|
8355
|
+
redact: [
|
|
8356
|
+
...ALWAYS_REDACT_SECRETS,
|
|
8357
|
+
...PII_PATTERNS,
|
|
8358
|
+
...INTERNAL_STATE_PATTERNS,
|
|
8359
|
+
...HISTORY_PATTERNS,
|
|
8360
|
+
"tool_results",
|
|
8361
|
+
"previous_results"
|
|
8362
|
+
],
|
|
8363
|
+
hash: [...ID_PATTERNS],
|
|
8364
|
+
summarize: []
|
|
8365
|
+
}
|
|
8366
|
+
],
|
|
8367
|
+
default_action: "redact"
|
|
8368
|
+
};
|
|
8369
|
+
var INFERENCE_STANDARD = {
|
|
8370
|
+
id: "inference-standard",
|
|
8371
|
+
name: "Inference Standard",
|
|
8372
|
+
description: "Balanced privacy. Task, query, and tool results pass through. History flagged for summarization. Secrets and PII redacted.",
|
|
8373
|
+
use_when: "You need the LLM to have enough context for multi-step tasks while keeping secrets, PII, and internal reasoning private.",
|
|
8374
|
+
rules: [
|
|
8375
|
+
{
|
|
8376
|
+
provider: "inference",
|
|
8377
|
+
allow: [
|
|
8378
|
+
"task",
|
|
8379
|
+
"task_description",
|
|
8380
|
+
"current_query",
|
|
8381
|
+
"query",
|
|
8382
|
+
"prompt",
|
|
8383
|
+
"question",
|
|
8384
|
+
"instruction",
|
|
8385
|
+
"tool_results",
|
|
8386
|
+
"tool_output",
|
|
8387
|
+
"previous_results",
|
|
8388
|
+
"current_step",
|
|
8389
|
+
"remaining_steps",
|
|
8390
|
+
"objective",
|
|
8391
|
+
"constraints",
|
|
8392
|
+
"format",
|
|
8393
|
+
"output_format"
|
|
8394
|
+
],
|
|
8395
|
+
redact: [
|
|
8396
|
+
...ALWAYS_REDACT_SECRETS,
|
|
8397
|
+
...PII_PATTERNS,
|
|
8398
|
+
...INTERNAL_STATE_PATTERNS
|
|
8399
|
+
],
|
|
8400
|
+
hash: [...ID_PATTERNS],
|
|
8401
|
+
summarize: [...HISTORY_PATTERNS]
|
|
8402
|
+
}
|
|
8403
|
+
],
|
|
8404
|
+
default_action: "redact"
|
|
8405
|
+
};
|
|
8406
|
+
var LOGGING_STRICT = {
|
|
8407
|
+
id: "logging-strict",
|
|
8408
|
+
name: "Logging Strict",
|
|
8409
|
+
description: "Redacts all content for logging and analytics providers. Only operation metadata passes through.",
|
|
8410
|
+
use_when: "You send telemetry to logging or analytics services and want usage metrics without any content exposure.",
|
|
8411
|
+
rules: [
|
|
8412
|
+
{
|
|
8413
|
+
provider: "logging",
|
|
8414
|
+
allow: [
|
|
8415
|
+
"operation",
|
|
8416
|
+
"operation_name",
|
|
8417
|
+
"tool_name",
|
|
8418
|
+
"timestamp",
|
|
8419
|
+
"duration_ms",
|
|
8420
|
+
"status",
|
|
8421
|
+
"error_code",
|
|
8422
|
+
"event_type"
|
|
8423
|
+
],
|
|
8424
|
+
redact: [
|
|
8425
|
+
...ALWAYS_REDACT_SECRETS,
|
|
8426
|
+
...PII_PATTERNS,
|
|
8427
|
+
...INTERNAL_STATE_PATTERNS,
|
|
8428
|
+
...HISTORY_PATTERNS
|
|
8429
|
+
],
|
|
8430
|
+
hash: [...ID_PATTERNS],
|
|
8431
|
+
summarize: []
|
|
8432
|
+
},
|
|
8433
|
+
{
|
|
8434
|
+
provider: "analytics",
|
|
8435
|
+
allow: [
|
|
8436
|
+
"event_type",
|
|
8437
|
+
"timestamp",
|
|
8438
|
+
"duration_ms",
|
|
8439
|
+
"status",
|
|
8440
|
+
"tool_name"
|
|
8441
|
+
],
|
|
8442
|
+
redact: [
|
|
8443
|
+
...ALWAYS_REDACT_SECRETS,
|
|
8444
|
+
...PII_PATTERNS,
|
|
8445
|
+
...INTERNAL_STATE_PATTERNS,
|
|
8446
|
+
...HISTORY_PATTERNS
|
|
8447
|
+
],
|
|
8448
|
+
hash: [...ID_PATTERNS],
|
|
8449
|
+
summarize: []
|
|
8450
|
+
}
|
|
8451
|
+
],
|
|
8452
|
+
default_action: "redact"
|
|
8453
|
+
};
|
|
8454
|
+
var TOOL_API_SCOPED = {
|
|
8455
|
+
id: "tool-api-scoped",
|
|
8456
|
+
name: "Tool API Scoped",
|
|
8457
|
+
description: "Allows tool-specific parameters for external API calls. Redacts memory, history, secrets, and PII.",
|
|
8458
|
+
use_when: "Your agent calls external APIs (search, database, web) and you want to send query parameters without full agent context. Note: 'headers' and 'body' are redacted by default because they frequently carry authorization tokens. Add them to 'allow' only if you verify they contain no credentials for your use case.",
|
|
8459
|
+
rules: [
|
|
8460
|
+
{
|
|
8461
|
+
provider: "tool-api",
|
|
8462
|
+
allow: [
|
|
8463
|
+
"task",
|
|
8464
|
+
"task_description",
|
|
8465
|
+
"query",
|
|
8466
|
+
"search_query",
|
|
8467
|
+
"tool_input",
|
|
8468
|
+
"tool_parameters",
|
|
8469
|
+
"url",
|
|
8470
|
+
"endpoint",
|
|
8471
|
+
"method",
|
|
8472
|
+
"filter",
|
|
8473
|
+
"sort",
|
|
8474
|
+
"limit",
|
|
8475
|
+
"offset"
|
|
8476
|
+
],
|
|
8477
|
+
redact: [
|
|
8478
|
+
...ALWAYS_REDACT_SECRETS,
|
|
8479
|
+
...PII_PATTERNS,
|
|
8480
|
+
...INTERNAL_STATE_PATTERNS,
|
|
8481
|
+
...HISTORY_PATTERNS
|
|
8482
|
+
],
|
|
8483
|
+
hash: [...ID_PATTERNS],
|
|
8484
|
+
summarize: []
|
|
8485
|
+
}
|
|
8486
|
+
],
|
|
8487
|
+
default_action: "redact"
|
|
8488
|
+
};
|
|
8489
|
+
var TEMPLATES = {
|
|
8490
|
+
"inference-minimal": INFERENCE_MINIMAL,
|
|
8491
|
+
"inference-standard": INFERENCE_STANDARD,
|
|
8492
|
+
"logging-strict": LOGGING_STRICT,
|
|
8493
|
+
"tool-api-scoped": TOOL_API_SCOPED
|
|
8494
|
+
};
|
|
8495
|
+
function listTemplateIds() {
|
|
8496
|
+
return Object.keys(TEMPLATES);
|
|
8497
|
+
}
|
|
8498
|
+
function getTemplate(id) {
|
|
8499
|
+
return TEMPLATES[id];
|
|
8500
|
+
}
|
|
8501
|
+
|
|
8502
|
+
// src/l2-operational/context-gate-recommend.ts
|
|
8503
|
+
var CLASSIFICATION_RULES = [
|
|
8504
|
+
// ── Secrets (always redact, high confidence) ─────────────────────
|
|
8505
|
+
{
|
|
8506
|
+
patterns: [
|
|
8507
|
+
"api_key",
|
|
8508
|
+
"apikey",
|
|
8509
|
+
"api_secret",
|
|
8510
|
+
"secret",
|
|
8511
|
+
"secret_key",
|
|
8512
|
+
"secret_token",
|
|
8513
|
+
"password",
|
|
8514
|
+
"passwd",
|
|
8515
|
+
"pass",
|
|
8516
|
+
"credential",
|
|
8517
|
+
"credentials",
|
|
8518
|
+
"private_key",
|
|
8519
|
+
"privkey",
|
|
8520
|
+
"recovery_key",
|
|
8521
|
+
"passphrase",
|
|
8522
|
+
"token",
|
|
8523
|
+
"access_token",
|
|
8524
|
+
"refresh_token",
|
|
8525
|
+
"bearer_token",
|
|
8526
|
+
"auth_token",
|
|
8527
|
+
"auth_header",
|
|
8528
|
+
"authorization",
|
|
8529
|
+
"encryption_key",
|
|
8530
|
+
"master_key",
|
|
8531
|
+
"signing_key",
|
|
8532
|
+
"webhook_secret",
|
|
8533
|
+
"client_secret",
|
|
8534
|
+
"connection_string"
|
|
8535
|
+
],
|
|
8536
|
+
action: "redact",
|
|
8537
|
+
confidence: "high",
|
|
8538
|
+
reason: "Matches known secret/credential pattern"
|
|
8539
|
+
},
|
|
8540
|
+
// ── PII (always redact, high confidence) ─────────────────────────
|
|
8541
|
+
{
|
|
8542
|
+
patterns: [
|
|
8543
|
+
"name",
|
|
8544
|
+
"full_name",
|
|
8545
|
+
"first_name",
|
|
8546
|
+
"last_name",
|
|
8547
|
+
"display_name",
|
|
8548
|
+
"email",
|
|
8549
|
+
"email_address",
|
|
8550
|
+
"phone",
|
|
8551
|
+
"phone_number",
|
|
8552
|
+
"mobile",
|
|
8553
|
+
"address",
|
|
8554
|
+
"street_address",
|
|
8555
|
+
"mailing_address",
|
|
8556
|
+
"ssn",
|
|
8557
|
+
"social_security",
|
|
8558
|
+
"date_of_birth",
|
|
8559
|
+
"dob",
|
|
8560
|
+
"birthday",
|
|
8561
|
+
"ip_address",
|
|
8562
|
+
"ip",
|
|
8563
|
+
"location",
|
|
8564
|
+
"geolocation",
|
|
8565
|
+
"coordinates",
|
|
8566
|
+
"credit_card",
|
|
8567
|
+
"card_number",
|
|
8568
|
+
"cvv",
|
|
8569
|
+
"bank_account",
|
|
8570
|
+
"routing_number",
|
|
8571
|
+
"passport",
|
|
8572
|
+
"drivers_license",
|
|
8573
|
+
"license_number"
|
|
8574
|
+
],
|
|
8575
|
+
action: "redact",
|
|
8576
|
+
confidence: "high",
|
|
8577
|
+
reason: "Matches known PII pattern"
|
|
8578
|
+
},
|
|
8579
|
+
// ── Internal agent state (redact, high confidence) ───────────────
|
|
8580
|
+
{
|
|
8581
|
+
patterns: [
|
|
8582
|
+
"memory",
|
|
8583
|
+
"agent_memory",
|
|
8584
|
+
"long_term_memory",
|
|
8585
|
+
"internal_reasoning",
|
|
8586
|
+
"reasoning_trace",
|
|
8587
|
+
"chain_of_thought",
|
|
8588
|
+
"internal_state",
|
|
8589
|
+
"agent_state",
|
|
8590
|
+
"private_notes",
|
|
8591
|
+
"scratchpad",
|
|
8592
|
+
"soul",
|
|
8593
|
+
"personality",
|
|
8594
|
+
"persona",
|
|
8595
|
+
"system_prompt",
|
|
8596
|
+
"system_message",
|
|
8597
|
+
"system_instruction",
|
|
8598
|
+
"preferences",
|
|
8599
|
+
"user_preferences",
|
|
8600
|
+
"agent_preferences",
|
|
8601
|
+
"beliefs",
|
|
8602
|
+
"goals",
|
|
8603
|
+
"motivations"
|
|
8604
|
+
],
|
|
8605
|
+
action: "redact",
|
|
8606
|
+
confidence: "high",
|
|
8607
|
+
reason: "Matches known internal agent state pattern"
|
|
8608
|
+
},
|
|
8609
|
+
// ── IDs (hash, medium confidence) ────────────────────────────────
|
|
8610
|
+
{
|
|
8611
|
+
patterns: [
|
|
8612
|
+
"user_id",
|
|
8613
|
+
"userid",
|
|
8614
|
+
"session_id",
|
|
8615
|
+
"sessionid",
|
|
8616
|
+
"agent_id",
|
|
8617
|
+
"agentid",
|
|
8618
|
+
"identity_id",
|
|
8619
|
+
"conversation_id",
|
|
8620
|
+
"thread_id",
|
|
8621
|
+
"threadid",
|
|
8622
|
+
"request_id",
|
|
8623
|
+
"requestid",
|
|
8624
|
+
"correlation_id",
|
|
8625
|
+
"trace_id",
|
|
8626
|
+
"traceid",
|
|
8627
|
+
"account_id",
|
|
8628
|
+
"accountid"
|
|
8629
|
+
],
|
|
8630
|
+
action: "hash",
|
|
8631
|
+
confidence: "medium",
|
|
8632
|
+
reason: "Matches known identifier pattern \u2014 hash preserves correlation without exposing value"
|
|
8633
|
+
},
|
|
8634
|
+
// ── History (summarize, medium confidence) ───────────────────────
|
|
8635
|
+
{
|
|
8636
|
+
patterns: [
|
|
8637
|
+
"conversation_history",
|
|
8638
|
+
"chat_history",
|
|
8639
|
+
"message_history",
|
|
8640
|
+
"messages",
|
|
8641
|
+
"previous_messages",
|
|
8642
|
+
"prior_messages",
|
|
8643
|
+
"context_window",
|
|
8644
|
+
"interaction_history",
|
|
8645
|
+
"audit_log",
|
|
8646
|
+
"event_log"
|
|
8647
|
+
],
|
|
8648
|
+
action: "summarize",
|
|
8649
|
+
confidence: "medium",
|
|
8650
|
+
reason: "Matches known history/log pattern \u2014 summarize to reduce exposure"
|
|
8651
|
+
},
|
|
8652
|
+
// ── Task/query (allow, medium confidence) ────────────────────────
|
|
8653
|
+
{
|
|
8654
|
+
patterns: [
|
|
8655
|
+
"task",
|
|
8656
|
+
"task_description",
|
|
8657
|
+
"query",
|
|
8658
|
+
"current_query",
|
|
8659
|
+
"search_query",
|
|
8660
|
+
"prompt",
|
|
8661
|
+
"user_prompt",
|
|
8662
|
+
"question",
|
|
8663
|
+
"current_question",
|
|
8664
|
+
"instruction",
|
|
8665
|
+
"instructions",
|
|
8666
|
+
"objective",
|
|
8667
|
+
"goal",
|
|
8668
|
+
"current_step",
|
|
8669
|
+
"next_step",
|
|
8670
|
+
"remaining_steps",
|
|
8671
|
+
"constraints",
|
|
8672
|
+
"requirements",
|
|
8673
|
+
"output_format",
|
|
8674
|
+
"format",
|
|
8675
|
+
"tool_results",
|
|
8676
|
+
"tool_output",
|
|
8677
|
+
"tool_input",
|
|
8678
|
+
"tool_parameters"
|
|
8679
|
+
],
|
|
8680
|
+
action: "allow",
|
|
8681
|
+
confidence: "medium",
|
|
8682
|
+
reason: "Matches known task/query pattern \u2014 likely needed for inference"
|
|
8683
|
+
}
|
|
8684
|
+
];
|
|
8685
|
+
function classifyField(fieldName) {
|
|
8686
|
+
const normalized = fieldName.toLowerCase().trim();
|
|
8687
|
+
for (const rule of CLASSIFICATION_RULES) {
|
|
8688
|
+
for (const pattern of rule.patterns) {
|
|
8689
|
+
if (matchesFieldPattern(normalized, pattern)) {
|
|
8690
|
+
return {
|
|
8691
|
+
field: fieldName,
|
|
8692
|
+
recommended_action: rule.action,
|
|
8693
|
+
reason: rule.reason,
|
|
8694
|
+
confidence: rule.confidence,
|
|
8695
|
+
matched_pattern: pattern
|
|
8696
|
+
};
|
|
8697
|
+
}
|
|
8698
|
+
}
|
|
8699
|
+
}
|
|
8700
|
+
return {
|
|
8701
|
+
field: fieldName,
|
|
8702
|
+
recommended_action: "redact",
|
|
8703
|
+
reason: "No known pattern matched \u2014 defaulting to redact (conservative)",
|
|
8704
|
+
confidence: "low",
|
|
8705
|
+
matched_pattern: null
|
|
8706
|
+
};
|
|
8707
|
+
}
|
|
8708
|
+
function recommendPolicy(context, provider = "inference") {
|
|
8709
|
+
const fields = Object.keys(context);
|
|
8710
|
+
const classifications = fields.map(classifyField);
|
|
8711
|
+
const warnings = [];
|
|
8712
|
+
const allow = [];
|
|
8713
|
+
const redact = [];
|
|
8714
|
+
const hash2 = [];
|
|
8715
|
+
const summarize = [];
|
|
8716
|
+
for (const c of classifications) {
|
|
8717
|
+
switch (c.recommended_action) {
|
|
8718
|
+
case "allow":
|
|
8719
|
+
allow.push(c.field);
|
|
8720
|
+
break;
|
|
8721
|
+
case "redact":
|
|
8722
|
+
redact.push(c.field);
|
|
8723
|
+
break;
|
|
8724
|
+
case "hash":
|
|
8725
|
+
hash2.push(c.field);
|
|
8726
|
+
break;
|
|
8727
|
+
case "summarize":
|
|
8728
|
+
summarize.push(c.field);
|
|
8729
|
+
break;
|
|
8730
|
+
}
|
|
8731
|
+
}
|
|
8732
|
+
const lowConfidence = classifications.filter((c) => c.confidence === "low");
|
|
8733
|
+
if (lowConfidence.length > 0) {
|
|
8734
|
+
warnings.push(
|
|
8735
|
+
`${lowConfidence.length} field(s) could not be classified by pattern and will default to redact: ${lowConfidence.map((c) => c.field).join(", ")}. Review these manually.`
|
|
8736
|
+
);
|
|
8737
|
+
}
|
|
8738
|
+
for (const [key, value] of Object.entries(context)) {
|
|
8739
|
+
if (typeof value === "string" && value.length > 5e3) {
|
|
8740
|
+
const existing = classifications.find((c) => c.field === key);
|
|
8741
|
+
if (existing && existing.recommended_action === "allow") {
|
|
8742
|
+
warnings.push(
|
|
8743
|
+
`Field "${key}" is allowed but contains ${value.length} characters. Consider summarizing it to reduce context size and exposure.`
|
|
8744
|
+
);
|
|
8745
|
+
}
|
|
8746
|
+
}
|
|
8747
|
+
}
|
|
8748
|
+
return {
|
|
8749
|
+
provider,
|
|
8750
|
+
classifications,
|
|
8751
|
+
recommended_rules: { allow, redact, hash: hash2, summarize },
|
|
8752
|
+
default_action: "redact",
|
|
8753
|
+
summary: {
|
|
8754
|
+
total_fields: fields.length,
|
|
8755
|
+
allow: allow.length,
|
|
8756
|
+
redact: redact.length,
|
|
8757
|
+
hash: hash2.length,
|
|
8758
|
+
summarize: summarize.length
|
|
8759
|
+
},
|
|
8760
|
+
warnings
|
|
8761
|
+
};
|
|
8762
|
+
}
|
|
8763
|
+
function matchesFieldPattern(normalizedField, pattern) {
|
|
8764
|
+
if (normalizedField === pattern) return true;
|
|
8765
|
+
if (pattern.length >= 3 && normalizedField.includes(pattern)) {
|
|
8766
|
+
const idx = normalizedField.indexOf(pattern);
|
|
8767
|
+
const before = idx === 0 || normalizedField[idx - 1] === "_" || normalizedField[idx - 1] === "-";
|
|
8768
|
+
const after = idx + pattern.length === normalizedField.length || normalizedField[idx + pattern.length] === "_" || normalizedField[idx + pattern.length] === "-";
|
|
8769
|
+
return before && after;
|
|
8770
|
+
}
|
|
8771
|
+
return false;
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
// src/l2-operational/context-gate-tools.ts
|
|
8775
|
+
function createContextGateTools(storage, masterKey, auditLog) {
|
|
8776
|
+
const policyStore = new ContextGatePolicyStore(storage, masterKey);
|
|
8777
|
+
const tools = [
|
|
8778
|
+
// ── Set Policy ──────────────────────────────────────────────────
|
|
8779
|
+
{
|
|
8780
|
+
name: "sanctuary/context_gate_set_policy",
|
|
8781
|
+
description: "Create a context-gating policy that controls what information flows to remote providers (LLM APIs, tool APIs, logging services). Each rule specifies a provider category and which context fields to allow, redact, hash, or flag for summarization. Redact rules take absolute priority \u2014 if a field is in both 'allow' and 'redact', it is redacted. Default action applies to any field not mentioned in any rule. Use this to prevent your full agent context from being sent to remote LLM providers during inference calls.",
|
|
8782
|
+
inputSchema: {
|
|
8783
|
+
type: "object",
|
|
8784
|
+
properties: {
|
|
8785
|
+
policy_name: {
|
|
8786
|
+
type: "string",
|
|
8787
|
+
description: "Human-readable name for this policy (e.g., 'inference-minimal', 'tool-api-strict')"
|
|
8788
|
+
},
|
|
8789
|
+
rules: {
|
|
8790
|
+
type: "array",
|
|
8791
|
+
description: "Array of rules. Each rule has: provider (inference|tool-api|logging|analytics|peer-agent|custom|*), allow (fields to pass through), redact (fields to remove \u2014 highest priority), hash (fields to replace with SHA-256 hash), summarize (fields to flag for compression).",
|
|
8792
|
+
items: {
|
|
8793
|
+
type: "object",
|
|
8794
|
+
properties: {
|
|
8795
|
+
provider: {
|
|
8796
|
+
type: "string",
|
|
8797
|
+
description: "Provider category: inference, tool-api, logging, analytics, peer-agent, custom, or * for all"
|
|
8798
|
+
},
|
|
8799
|
+
allow: {
|
|
8800
|
+
type: "array",
|
|
8801
|
+
items: { type: "string" },
|
|
8802
|
+
description: "Fields/patterns to allow through (e.g., 'task_description', 'current_query', 'tool_*')"
|
|
8803
|
+
},
|
|
8804
|
+
redact: {
|
|
8805
|
+
type: "array",
|
|
8806
|
+
items: { type: "string" },
|
|
8807
|
+
description: "Fields/patterns to redact (e.g., 'conversation_history', 'secret_*', '*_pii'). Takes absolute priority."
|
|
8808
|
+
},
|
|
8809
|
+
hash: {
|
|
8810
|
+
type: "array",
|
|
8811
|
+
items: { type: "string" },
|
|
8812
|
+
description: "Fields/patterns to replace with SHA-256 hash (e.g., 'user_id', 'session_id')"
|
|
8813
|
+
},
|
|
8814
|
+
summarize: {
|
|
8815
|
+
type: "array",
|
|
8816
|
+
items: { type: "string" },
|
|
8817
|
+
description: "Fields/patterns to flag for summarization (advisory \u2014 agent should compress these before sending)"
|
|
8818
|
+
}
|
|
8819
|
+
},
|
|
8820
|
+
required: ["provider", "allow", "redact"]
|
|
8821
|
+
}
|
|
8822
|
+
},
|
|
8823
|
+
default_action: {
|
|
8824
|
+
type: "string",
|
|
8825
|
+
enum: ["redact", "deny"],
|
|
8826
|
+
description: "Action for fields not matched by any rule. 'redact' removes the field value; 'deny' blocks the entire request. Default: 'redact'."
|
|
8827
|
+
},
|
|
8828
|
+
identity_id: {
|
|
8829
|
+
type: "string",
|
|
8830
|
+
description: "Bind this policy to a specific identity (optional)"
|
|
8831
|
+
}
|
|
8832
|
+
},
|
|
8833
|
+
required: ["policy_name", "rules"]
|
|
8834
|
+
},
|
|
8835
|
+
handler: async (args) => {
|
|
8836
|
+
const policyName = args.policy_name;
|
|
8837
|
+
const rawRules = args.rules;
|
|
8838
|
+
const defaultAction = args.default_action ?? "redact";
|
|
8839
|
+
const identityId = args.identity_id;
|
|
8840
|
+
if (!Array.isArray(rawRules)) {
|
|
8841
|
+
return toolResult({ error: "invalid_rules", message: "rules must be an array" });
|
|
8842
|
+
}
|
|
8843
|
+
if (rawRules.length > MAX_POLICY_RULES) {
|
|
8844
|
+
return toolResult({
|
|
8845
|
+
error: "too_many_rules",
|
|
8846
|
+
message: `Policy has ${rawRules.length} rules, exceeding limit of ${MAX_POLICY_RULES}`
|
|
8847
|
+
});
|
|
8848
|
+
}
|
|
8849
|
+
const rules = [];
|
|
8850
|
+
for (const r of rawRules) {
|
|
8851
|
+
const allow = Array.isArray(r.allow) ? r.allow : [];
|
|
8852
|
+
const redact = Array.isArray(r.redact) ? r.redact : [];
|
|
8853
|
+
const hash2 = Array.isArray(r.hash) ? r.hash : [];
|
|
8854
|
+
const summarize = Array.isArray(r.summarize) ? r.summarize : [];
|
|
8855
|
+
for (const [name, arr] of [["allow", allow], ["redact", redact], ["hash", hash2], ["summarize", summarize]]) {
|
|
8856
|
+
if (arr.length > MAX_PATTERNS_PER_ARRAY) {
|
|
8857
|
+
return toolResult({
|
|
8858
|
+
error: "too_many_patterns",
|
|
8859
|
+
message: `Rule ${name} array has ${arr.length} patterns, exceeding limit of ${MAX_PATTERNS_PER_ARRAY}`
|
|
8860
|
+
});
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8863
|
+
rules.push({
|
|
8864
|
+
provider: r.provider ?? "*",
|
|
8865
|
+
allow,
|
|
8866
|
+
redact,
|
|
8867
|
+
hash: hash2,
|
|
8868
|
+
summarize
|
|
8869
|
+
});
|
|
8870
|
+
}
|
|
8871
|
+
const policy = await policyStore.create(
|
|
8872
|
+
policyName,
|
|
8873
|
+
rules,
|
|
8874
|
+
defaultAction,
|
|
8875
|
+
identityId
|
|
8876
|
+
);
|
|
8877
|
+
auditLog.append("l2", "context_gate_set_policy", identityId ?? "system", {
|
|
8878
|
+
policy_id: policy.policy_id,
|
|
8879
|
+
policy_name: policyName,
|
|
8880
|
+
rule_count: rules.length,
|
|
8881
|
+
default_action: defaultAction
|
|
8882
|
+
});
|
|
8883
|
+
return toolResult({
|
|
8884
|
+
policy_id: policy.policy_id,
|
|
8885
|
+
policy_name: policy.policy_name,
|
|
8886
|
+
rules: policy.rules,
|
|
8887
|
+
default_action: policy.default_action,
|
|
8888
|
+
created_at: policy.created_at,
|
|
8889
|
+
message: "Context-gating policy created. Use sanctuary/context_gate_filter to apply this policy before making outbound calls."
|
|
8890
|
+
});
|
|
8891
|
+
}
|
|
8892
|
+
},
|
|
8893
|
+
// ── Apply Template ───────────────────────────────────────────────
|
|
8894
|
+
{
|
|
8895
|
+
name: "sanctuary/context_gate_apply_template",
|
|
8896
|
+
description: "Apply a starter context-gating template. Available templates: inference-minimal (strictest \u2014 only task and query pass through), inference-standard (balanced \u2014 adds tool results, summarizes history), logging-strict (redacts all content for telemetry services), tool-api-scoped (allows tool parameters, redacts agent state). Templates are starting points \u2014 customize after applying.",
|
|
8897
|
+
inputSchema: {
|
|
8898
|
+
type: "object",
|
|
8899
|
+
properties: {
|
|
8900
|
+
template_id: {
|
|
8901
|
+
type: "string",
|
|
8902
|
+
description: "Template to apply: inference-minimal, inference-standard, logging-strict, or tool-api-scoped"
|
|
8903
|
+
},
|
|
8904
|
+
identity_id: {
|
|
8905
|
+
type: "string",
|
|
8906
|
+
description: "Bind this policy to a specific identity (optional)"
|
|
8907
|
+
}
|
|
8908
|
+
},
|
|
8909
|
+
required: ["template_id"]
|
|
8910
|
+
},
|
|
8911
|
+
handler: async (args) => {
|
|
8912
|
+
const templateId = args.template_id;
|
|
8913
|
+
const identityId = args.identity_id;
|
|
8914
|
+
const template = getTemplate(templateId);
|
|
8915
|
+
if (!template) {
|
|
8916
|
+
return toolResult({
|
|
8917
|
+
error: "template_not_found",
|
|
8918
|
+
message: `Unknown template "${templateId}"`,
|
|
8919
|
+
available_templates: listTemplateIds().map((id) => {
|
|
8920
|
+
const t = TEMPLATES[id];
|
|
8921
|
+
return { id, name: t.name, description: t.description };
|
|
8922
|
+
})
|
|
8923
|
+
});
|
|
8924
|
+
}
|
|
8925
|
+
const policy = await policyStore.create(
|
|
8926
|
+
template.name,
|
|
8927
|
+
template.rules,
|
|
8928
|
+
template.default_action,
|
|
8929
|
+
identityId
|
|
8930
|
+
);
|
|
8931
|
+
auditLog.append("l2", "context_gate_apply_template", identityId ?? "system", {
|
|
8932
|
+
policy_id: policy.policy_id,
|
|
8933
|
+
template_id: templateId
|
|
8934
|
+
});
|
|
8935
|
+
return toolResult({
|
|
8936
|
+
policy_id: policy.policy_id,
|
|
8937
|
+
template_applied: templateId,
|
|
8938
|
+
policy_name: template.name,
|
|
8939
|
+
description: template.description,
|
|
8940
|
+
use_when: template.use_when,
|
|
8941
|
+
rules: policy.rules,
|
|
8942
|
+
default_action: policy.default_action,
|
|
8943
|
+
created_at: policy.created_at,
|
|
8944
|
+
message: "Template applied. Use sanctuary/context_gate_filter with this policy_id to filter context before outbound calls. Customize rules with sanctuary/context_gate_set_policy if needed."
|
|
8945
|
+
});
|
|
8946
|
+
}
|
|
8947
|
+
},
|
|
8948
|
+
// ── Recommend Policy ────────────────────────────────────────────
|
|
8949
|
+
{
|
|
8950
|
+
name: "sanctuary/context_gate_recommend",
|
|
8951
|
+
description: "Analyze a sample context object and recommend a context-gating policy based on field name heuristics. Classifies each field as allow, redact, hash, or summarize with confidence levels. Returns a ready-to-apply rule set. When in doubt, recommends redact (conservative). Review the recommendations before applying.",
|
|
8952
|
+
inputSchema: {
|
|
8953
|
+
type: "object",
|
|
8954
|
+
properties: {
|
|
8955
|
+
context: {
|
|
8956
|
+
type: "object",
|
|
8957
|
+
description: "A sample context object to analyze. Each top-level key will be classified. Values are inspected for size warnings but not stored."
|
|
8958
|
+
},
|
|
8959
|
+
provider: {
|
|
8960
|
+
type: "string",
|
|
8961
|
+
description: "Provider category to generate rules for. Default: 'inference'."
|
|
8962
|
+
}
|
|
8963
|
+
},
|
|
8964
|
+
required: ["context"]
|
|
8965
|
+
},
|
|
8966
|
+
handler: async (args) => {
|
|
8967
|
+
const context = args.context;
|
|
8968
|
+
const provider = args.provider ?? "inference";
|
|
8969
|
+
const contextKeys = Object.keys(context);
|
|
8970
|
+
if (contextKeys.length > MAX_CONTEXT_FIELDS) {
|
|
8971
|
+
return toolResult({
|
|
8972
|
+
error: "context_too_large",
|
|
8973
|
+
message: `Context has ${contextKeys.length} fields, exceeding limit of ${MAX_CONTEXT_FIELDS}`
|
|
8974
|
+
});
|
|
8975
|
+
}
|
|
8976
|
+
const recommendation = recommendPolicy(context, provider);
|
|
8977
|
+
auditLog.append("l2", "context_gate_recommend", "system", {
|
|
8978
|
+
provider,
|
|
8979
|
+
fields_analyzed: recommendation.summary.total_fields,
|
|
8980
|
+
fields_allow: recommendation.summary.allow,
|
|
8981
|
+
fields_redact: recommendation.summary.redact,
|
|
8982
|
+
fields_hash: recommendation.summary.hash,
|
|
8983
|
+
fields_summarize: recommendation.summary.summarize
|
|
8984
|
+
});
|
|
8985
|
+
return toolResult({
|
|
8986
|
+
...recommendation,
|
|
8987
|
+
next_steps: "Review the classifications above. If they look correct, you can apply them directly with sanctuary/context_gate_set_policy using the recommended_rules. Or start with a template via sanctuary/context_gate_apply_template and customize from there.",
|
|
8988
|
+
available_templates: listTemplateIds().map((id) => {
|
|
8989
|
+
const t = TEMPLATES[id];
|
|
8990
|
+
return { id, name: t.name, description: t.description };
|
|
8991
|
+
})
|
|
8992
|
+
});
|
|
8993
|
+
}
|
|
8994
|
+
},
|
|
8995
|
+
// ── Filter Context ──────────────────────────────────────────────
|
|
8996
|
+
{
|
|
8997
|
+
name: "sanctuary/context_gate_filter",
|
|
8998
|
+
description: "Filter agent context through a gating policy before sending to a remote provider. Returns per-field decisions (allow, redact, hash, summarize) and content hashes for the audit trail. Call this BEFORE making any outbound API call to ensure you are only sending the minimum necessary context. The filtered output tells you exactly what can be sent safely.",
|
|
8999
|
+
inputSchema: {
|
|
9000
|
+
type: "object",
|
|
9001
|
+
properties: {
|
|
9002
|
+
policy_id: {
|
|
9003
|
+
type: "string",
|
|
9004
|
+
description: "ID of the context-gating policy to apply"
|
|
9005
|
+
},
|
|
9006
|
+
provider: {
|
|
9007
|
+
type: "string",
|
|
9008
|
+
description: "Provider category for this call: inference, tool-api, logging, analytics, peer-agent, or custom"
|
|
9009
|
+
},
|
|
9010
|
+
context: {
|
|
9011
|
+
type: "object",
|
|
9012
|
+
description: "The context object to filter. Each top-level key is evaluated against the policy. Example keys: task_description, conversation_history, user_preferences, api_keys, memory, internal_reasoning"
|
|
9013
|
+
}
|
|
9014
|
+
},
|
|
9015
|
+
required: ["policy_id", "provider", "context"]
|
|
9016
|
+
},
|
|
9017
|
+
handler: async (args) => {
|
|
9018
|
+
const policyId = args.policy_id;
|
|
9019
|
+
const provider = args.provider;
|
|
9020
|
+
const context = args.context;
|
|
9021
|
+
const contextKeys = Object.keys(context);
|
|
9022
|
+
if (contextKeys.length > MAX_CONTEXT_FIELDS) {
|
|
9023
|
+
return toolResult({
|
|
9024
|
+
error: "context_too_large",
|
|
9025
|
+
message: `Context has ${contextKeys.length} fields, exceeding limit of ${MAX_CONTEXT_FIELDS}`
|
|
9026
|
+
});
|
|
9027
|
+
}
|
|
9028
|
+
const policy = await policyStore.get(policyId);
|
|
9029
|
+
if (!policy) {
|
|
9030
|
+
return toolResult({
|
|
9031
|
+
error: "policy_not_found",
|
|
9032
|
+
message: `No context-gating policy found with ID "${policyId}"`
|
|
9033
|
+
});
|
|
9034
|
+
}
|
|
9035
|
+
const result = filterContext(policy, provider, context);
|
|
9036
|
+
const deniedFields = result.decisions.filter((d) => d.action === "deny");
|
|
9037
|
+
if (deniedFields.length > 0) {
|
|
9038
|
+
auditLog.append("l2", "context_gate_deny", policy.identity_id ?? "system", {
|
|
9039
|
+
policy_id: policyId,
|
|
9040
|
+
provider,
|
|
9041
|
+
denied_fields: deniedFields.map((d) => d.field),
|
|
9042
|
+
original_context_hash: result.original_context_hash
|
|
9043
|
+
});
|
|
9044
|
+
return toolResult({
|
|
9045
|
+
blocked: true,
|
|
9046
|
+
reason: "Context contains fields that trigger deny action",
|
|
9047
|
+
denied_fields: deniedFields.map((d) => ({
|
|
9048
|
+
field: d.field,
|
|
9049
|
+
reason: d.reason
|
|
9050
|
+
})),
|
|
9051
|
+
recommendation: "Remove the denied fields from context before retrying, or update the policy to handle these fields differently."
|
|
9052
|
+
});
|
|
9053
|
+
}
|
|
9054
|
+
const safeContext = {};
|
|
9055
|
+
for (const decision of result.decisions) {
|
|
9056
|
+
switch (decision.action) {
|
|
9057
|
+
case "allow":
|
|
9058
|
+
safeContext[decision.field] = context[decision.field];
|
|
9059
|
+
break;
|
|
9060
|
+
case "redact":
|
|
9061
|
+
break;
|
|
9062
|
+
case "hash":
|
|
9063
|
+
safeContext[decision.field] = decision.hash_value;
|
|
9064
|
+
break;
|
|
9065
|
+
case "summarize":
|
|
9066
|
+
safeContext[decision.field] = context[decision.field];
|
|
9067
|
+
break;
|
|
9068
|
+
}
|
|
9069
|
+
}
|
|
9070
|
+
auditLog.append("l2", "context_gate_filter", policy.identity_id ?? "system", {
|
|
9071
|
+
policy_id: policyId,
|
|
9072
|
+
provider,
|
|
9073
|
+
fields_total: Object.keys(context).length,
|
|
9074
|
+
fields_allowed: result.fields_allowed,
|
|
9075
|
+
fields_redacted: result.fields_redacted,
|
|
9076
|
+
fields_hashed: result.fields_hashed,
|
|
9077
|
+
fields_summarized: result.fields_summarized,
|
|
9078
|
+
original_context_hash: result.original_context_hash,
|
|
9079
|
+
filtered_context_hash: result.filtered_context_hash
|
|
9080
|
+
});
|
|
9081
|
+
return toolResult({
|
|
9082
|
+
blocked: false,
|
|
9083
|
+
safe_context: safeContext,
|
|
9084
|
+
summary: {
|
|
9085
|
+
total_fields: Object.keys(context).length,
|
|
9086
|
+
allowed: result.fields_allowed,
|
|
9087
|
+
redacted: result.fields_redacted,
|
|
9088
|
+
hashed: result.fields_hashed,
|
|
9089
|
+
summarized: result.fields_summarized
|
|
9090
|
+
},
|
|
9091
|
+
decisions: result.decisions,
|
|
9092
|
+
audit: {
|
|
9093
|
+
original_context_hash: result.original_context_hash,
|
|
9094
|
+
filtered_context_hash: result.filtered_context_hash,
|
|
9095
|
+
filtered_at: result.filtered_at
|
|
9096
|
+
},
|
|
9097
|
+
guidance: result.fields_summarized > 0 ? "Some fields are marked for summarization. Consider compressing them before sending to reduce context size and information exposure." : void 0
|
|
9098
|
+
});
|
|
9099
|
+
}
|
|
9100
|
+
},
|
|
9101
|
+
// ── List Policies ───────────────────────────────────────────────
|
|
9102
|
+
{
|
|
9103
|
+
name: "sanctuary/context_gate_list_policies",
|
|
9104
|
+
description: "List all configured context-gating policies. Returns policy IDs, names, rule summaries, and default actions.",
|
|
9105
|
+
inputSchema: {
|
|
9106
|
+
type: "object",
|
|
9107
|
+
properties: {}
|
|
9108
|
+
},
|
|
9109
|
+
handler: async () => {
|
|
9110
|
+
const policies = await policyStore.list();
|
|
9111
|
+
auditLog.append("l2", "context_gate_list_policies", "system", {
|
|
9112
|
+
policy_count: policies.length
|
|
9113
|
+
});
|
|
9114
|
+
return toolResult({
|
|
9115
|
+
policies: policies.map((p) => ({
|
|
9116
|
+
policy_id: p.policy_id,
|
|
9117
|
+
policy_name: p.policy_name,
|
|
9118
|
+
rule_count: p.rules.length,
|
|
9119
|
+
providers: p.rules.map((r) => r.provider),
|
|
9120
|
+
default_action: p.default_action,
|
|
9121
|
+
identity_id: p.identity_id ?? null,
|
|
9122
|
+
created_at: p.created_at,
|
|
9123
|
+
updated_at: p.updated_at
|
|
9124
|
+
})),
|
|
9125
|
+
count: policies.length,
|
|
9126
|
+
message: policies.length === 0 ? "No context-gating policies configured. Use sanctuary/context_gate_set_policy to create one." : `${policies.length} context-gating ${policies.length === 1 ? "policy" : "policies"} configured.`
|
|
9127
|
+
});
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
9130
|
+
];
|
|
9131
|
+
return { tools, policyStore };
|
|
9132
|
+
}
|
|
9133
|
+
function checkMemoryProtection() {
|
|
9134
|
+
const checks = {
|
|
9135
|
+
aslr_enabled: checkASLR(),
|
|
9136
|
+
stack_canaries: true,
|
|
9137
|
+
// Enabled by default in Node.js runtime
|
|
9138
|
+
secure_buffer_zeros: true,
|
|
9139
|
+
// We use crypto.randomBytes and explicit zeroing
|
|
9140
|
+
argon2id_kdf: true
|
|
9141
|
+
// Master key derivation uses Argon2id
|
|
9142
|
+
};
|
|
9143
|
+
const activeCount = Object.values(checks).filter((v) => v).length;
|
|
9144
|
+
const overall = activeCount >= 4 ? "full" : activeCount >= 3 ? "partial" : "minimal";
|
|
9145
|
+
return {
|
|
9146
|
+
...checks,
|
|
9147
|
+
overall
|
|
9148
|
+
};
|
|
9149
|
+
}
|
|
9150
|
+
function checkASLR() {
|
|
9151
|
+
if (process.platform === "linux") {
|
|
9152
|
+
try {
|
|
9153
|
+
const result = child_process.execSync("cat /proc/sys/kernel/randomize_va_space", {
|
|
9154
|
+
encoding: "utf-8",
|
|
9155
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
9156
|
+
}).trim();
|
|
9157
|
+
return result === "2";
|
|
9158
|
+
} catch {
|
|
9159
|
+
return false;
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
if (process.platform === "darwin") {
|
|
9163
|
+
return true;
|
|
9164
|
+
}
|
|
9165
|
+
return false;
|
|
9166
|
+
}
|
|
9167
|
+
function checkProcessIsolation() {
|
|
9168
|
+
const isContainer = detectContainer();
|
|
9169
|
+
const isVM = detectVM();
|
|
9170
|
+
const isSandboxed = detectSandbox();
|
|
9171
|
+
let isolationLevel = "none";
|
|
9172
|
+
if (isContainer) isolationLevel = "hardened";
|
|
9173
|
+
else if (isVM) isolationLevel = "hardened";
|
|
9174
|
+
else if (isSandboxed) isolationLevel = "basic";
|
|
9175
|
+
const details = {};
|
|
9176
|
+
if (isContainer && isContainer !== true) details.container_type = isContainer;
|
|
9177
|
+
if (isVM && isVM !== true) details.vm_type = isVM;
|
|
9178
|
+
if (isSandboxed && isSandboxed !== true) details.sandbox_type = isSandboxed;
|
|
9179
|
+
return {
|
|
9180
|
+
isolation_level: isolationLevel,
|
|
9181
|
+
is_container: isContainer !== false,
|
|
9182
|
+
is_vm: isVM !== false,
|
|
9183
|
+
is_sandboxed: isSandboxed !== false,
|
|
9184
|
+
is_tee: false,
|
|
9185
|
+
details
|
|
9186
|
+
};
|
|
9187
|
+
}
|
|
9188
|
+
function detectContainer() {
|
|
9189
|
+
try {
|
|
9190
|
+
if (process.env.DOCKER_HOST) return "docker";
|
|
9191
|
+
try {
|
|
9192
|
+
fs.statSync("/.dockerenv");
|
|
9193
|
+
return "docker";
|
|
9194
|
+
} catch {
|
|
9195
|
+
}
|
|
9196
|
+
if (process.platform === "linux") {
|
|
9197
|
+
const cgroup = child_process.execSync("cat /proc/1/cgroup 2>/dev/null || echo ''", {
|
|
9198
|
+
encoding: "utf-8"
|
|
9199
|
+
});
|
|
9200
|
+
if (cgroup.includes("docker")) return "docker";
|
|
9201
|
+
if (cgroup.includes("lxc")) return "lxc";
|
|
9202
|
+
if (cgroup.includes("kubepods") || cgroup.includes("kubernetes")) return "kubernetes";
|
|
9203
|
+
}
|
|
9204
|
+
if (process.env.container === "podman") return "podman";
|
|
9205
|
+
if (process.env.CONTAINER_ID) return "oci";
|
|
9206
|
+
return false;
|
|
9207
|
+
} catch {
|
|
9208
|
+
return false;
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
function detectVM() {
|
|
9212
|
+
if (process.platform === "linux") {
|
|
9213
|
+
try {
|
|
9214
|
+
const dmidecode = child_process.execSync("dmidecode -s system-product-name 2>/dev/null || echo ''", {
|
|
9215
|
+
encoding: "utf-8"
|
|
9216
|
+
}).toLowerCase();
|
|
9217
|
+
if (dmidecode.includes("vmware")) return "vmware";
|
|
9218
|
+
if (dmidecode.includes("virtualbox")) return "virtualbox";
|
|
9219
|
+
if (dmidecode.includes("kvm")) return "kvm";
|
|
9220
|
+
if (dmidecode.includes("xen")) return "xen";
|
|
9221
|
+
if (dmidecode.includes("hyper-v")) return "hyper-v";
|
|
9222
|
+
const cpuinfo = child_process.execSync("grep -i hypervisor /proc/cpuinfo || echo ''", {
|
|
9223
|
+
encoding: "utf-8"
|
|
9224
|
+
});
|
|
9225
|
+
if (cpuinfo.length > 0) return "detected";
|
|
9226
|
+
} catch {
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
if (process.platform === "darwin") {
|
|
9230
|
+
try {
|
|
9231
|
+
const bootargs = child_process.execSync(
|
|
9232
|
+
"nvram boot-args 2>/dev/null | grep -i 'parallels\\|vmware\\|virtualbox' || echo ''",
|
|
9233
|
+
{
|
|
9234
|
+
encoding: "utf-8"
|
|
9235
|
+
}
|
|
9236
|
+
);
|
|
9237
|
+
if (bootargs.length > 0) return "detected";
|
|
9238
|
+
} catch {
|
|
9239
|
+
}
|
|
9240
|
+
}
|
|
9241
|
+
return false;
|
|
9242
|
+
}
|
|
9243
|
+
function detectSandbox() {
|
|
9244
|
+
if (process.platform === "darwin") {
|
|
9245
|
+
if (process.env.APP_SANDBOX_READ_ONLY_HOME === "1") return "app-sandbox";
|
|
9246
|
+
if (process.env.TMPDIR && process.env.TMPDIR.includes("AppSandbox")) return "app-sandbox";
|
|
9247
|
+
}
|
|
9248
|
+
if (process.platform === "openbsd") {
|
|
9249
|
+
try {
|
|
9250
|
+
const pledge = child_process.execSync("pledge -v 2>/dev/null || echo ''", {
|
|
9251
|
+
encoding: "utf-8"
|
|
9252
|
+
});
|
|
9253
|
+
if (pledge.length > 0) return "pledge";
|
|
9254
|
+
} catch {
|
|
9255
|
+
}
|
|
9256
|
+
}
|
|
9257
|
+
if (process.platform === "linux") {
|
|
9258
|
+
if (process.env.container === "lxc") return "lxc";
|
|
9259
|
+
try {
|
|
9260
|
+
const context = child_process.execSync("getenforce 2>/dev/null || echo ''", {
|
|
9261
|
+
encoding: "utf-8"
|
|
9262
|
+
}).trim();
|
|
9263
|
+
if (context === "Enforcing") return "selinux";
|
|
9264
|
+
} catch {
|
|
9265
|
+
}
|
|
9266
|
+
}
|
|
9267
|
+
return false;
|
|
9268
|
+
}
|
|
9269
|
+
function checkFilesystemPermissions(storagePath) {
|
|
9270
|
+
try {
|
|
9271
|
+
const stats = fs.statSync(storagePath);
|
|
9272
|
+
const mode = stats.mode & parseInt("777", 8);
|
|
9273
|
+
const modeString = mode.toString(8).padStart(3, "0");
|
|
9274
|
+
const isSecure = mode === parseInt("700", 8);
|
|
9275
|
+
const groupReadable = (mode & parseInt("040", 8)) !== 0;
|
|
9276
|
+
const othersReadable = (mode & parseInt("007", 8)) !== 0;
|
|
9277
|
+
const currentUid = process.getuid?.() || -1;
|
|
9278
|
+
const ownerIsCurrentUser = stats.uid === currentUid;
|
|
9279
|
+
let overall = "secure";
|
|
9280
|
+
if (groupReadable || othersReadable) overall = "insecure";
|
|
9281
|
+
else if (!ownerIsCurrentUser) overall = "warning";
|
|
9282
|
+
return {
|
|
9283
|
+
sanctuary_storage_protected: isSecure,
|
|
9284
|
+
sanctuary_storage_mode: modeString,
|
|
9285
|
+
owner_is_current_user: ownerIsCurrentUser,
|
|
9286
|
+
group_readable: groupReadable,
|
|
9287
|
+
others_readable: othersReadable,
|
|
9288
|
+
overall
|
|
9289
|
+
};
|
|
9290
|
+
} catch {
|
|
9291
|
+
return {
|
|
9292
|
+
sanctuary_storage_protected: false,
|
|
9293
|
+
sanctuary_storage_mode: "unknown",
|
|
9294
|
+
owner_is_current_user: false,
|
|
9295
|
+
group_readable: false,
|
|
9296
|
+
others_readable: false,
|
|
9297
|
+
overall: "warning"
|
|
9298
|
+
};
|
|
9299
|
+
}
|
|
9300
|
+
}
|
|
9301
|
+
function checkRuntimeIntegrity() {
|
|
9302
|
+
return {
|
|
9303
|
+
config_hash_stable: true,
|
|
9304
|
+
environment_state: "clean",
|
|
9305
|
+
discrepancies: []
|
|
9306
|
+
};
|
|
9307
|
+
}
|
|
9308
|
+
function assessL2Hardening(storagePath) {
|
|
9309
|
+
const memory = checkMemoryProtection();
|
|
9310
|
+
const isolation = checkProcessIsolation();
|
|
9311
|
+
const filesystem = checkFilesystemPermissions(storagePath);
|
|
9312
|
+
const integrity = checkRuntimeIntegrity();
|
|
9313
|
+
let checksPassed = 0;
|
|
9314
|
+
let checksTotal = 0;
|
|
9315
|
+
if (memory.aslr_enabled) checksPassed++;
|
|
9316
|
+
checksTotal++;
|
|
9317
|
+
if (memory.stack_canaries) checksPassed++;
|
|
9318
|
+
checksTotal++;
|
|
9319
|
+
if (memory.secure_buffer_zeros) checksPassed++;
|
|
9320
|
+
checksTotal++;
|
|
9321
|
+
if (memory.argon2id_kdf) checksPassed++;
|
|
9322
|
+
checksTotal++;
|
|
9323
|
+
if (isolation.is_container) checksPassed++;
|
|
9324
|
+
checksTotal++;
|
|
9325
|
+
if (isolation.is_vm) checksPassed++;
|
|
9326
|
+
checksTotal++;
|
|
9327
|
+
if (isolation.is_sandboxed) checksPassed++;
|
|
9328
|
+
checksTotal++;
|
|
9329
|
+
if (filesystem.sanctuary_storage_protected) checksPassed++;
|
|
9330
|
+
checksTotal++;
|
|
9331
|
+
{
|
|
9332
|
+
checksPassed++;
|
|
9333
|
+
}
|
|
9334
|
+
checksTotal++;
|
|
9335
|
+
let hardeningLevel = isolation.isolation_level;
|
|
9336
|
+
if (filesystem.overall === "insecure" || memory.overall === "none" || memory.overall === "minimal") {
|
|
9337
|
+
if (hardeningLevel === "hardened") {
|
|
9338
|
+
hardeningLevel = "basic";
|
|
9339
|
+
} else if (hardeningLevel === "basic") {
|
|
9340
|
+
hardeningLevel = "none";
|
|
9341
|
+
}
|
|
9342
|
+
}
|
|
9343
|
+
const summaryParts = [];
|
|
9344
|
+
if (isolation.is_container || isolation.is_vm) {
|
|
9345
|
+
summaryParts.push(`Running in ${isolation.details.container_type || isolation.details.vm_type || "isolated environment"}`);
|
|
9346
|
+
}
|
|
9347
|
+
if (memory.aslr_enabled) {
|
|
9348
|
+
summaryParts.push("ASLR enabled");
|
|
9349
|
+
}
|
|
9350
|
+
if (filesystem.sanctuary_storage_protected) {
|
|
9351
|
+
summaryParts.push("Storage permissions secured (0700)");
|
|
9352
|
+
}
|
|
9353
|
+
const summary = summaryParts.length > 0 ? summaryParts.join("; ") : "No process-level hardening detected";
|
|
9354
|
+
return {
|
|
9355
|
+
hardening_level: hardeningLevel,
|
|
9356
|
+
memory_protection: memory,
|
|
9357
|
+
process_isolation: isolation,
|
|
9358
|
+
filesystem_permissions: filesystem,
|
|
9359
|
+
runtime_integrity: integrity,
|
|
9360
|
+
checks_passed: checksPassed,
|
|
9361
|
+
checks_total: checksTotal,
|
|
9362
|
+
summary
|
|
9363
|
+
};
|
|
9364
|
+
}
|
|
9365
|
+
|
|
9366
|
+
// src/l2-operational/hardening-tools.ts
|
|
9367
|
+
function createL2HardeningTools(storagePath, auditLog) {
|
|
9368
|
+
return [
|
|
9369
|
+
{
|
|
9370
|
+
name: "sanctuary/l2_hardening_status",
|
|
9371
|
+
description: "L2 Process Hardening Status \u2014 Verify software-based operational isolation. Reports memory protection, process isolation level, filesystem permissions, and overall hardening assessment. Read-only. Tier 3 \u2014 always allowed.",
|
|
9372
|
+
inputSchema: {
|
|
9373
|
+
type: "object",
|
|
9374
|
+
properties: {
|
|
9375
|
+
include_details: {
|
|
9376
|
+
type: "boolean",
|
|
9377
|
+
description: "If true, include detailed check results for memory, process, and filesystem. If false, show summary only.",
|
|
9378
|
+
default: false
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
},
|
|
9382
|
+
handler: async (args) => {
|
|
9383
|
+
const includeDetails = args.include_details ?? false;
|
|
9384
|
+
const status = assessL2Hardening(storagePath);
|
|
9385
|
+
auditLog.append(
|
|
9386
|
+
"l2",
|
|
9387
|
+
"l2_hardening_status",
|
|
9388
|
+
"system",
|
|
9389
|
+
{ include_details: includeDetails }
|
|
9390
|
+
);
|
|
9391
|
+
if (includeDetails) {
|
|
9392
|
+
return toolResult({
|
|
9393
|
+
hardening_level: status.hardening_level,
|
|
9394
|
+
summary: status.summary,
|
|
9395
|
+
checks_passed: status.checks_passed,
|
|
9396
|
+
checks_total: status.checks_total,
|
|
9397
|
+
memory_protection: {
|
|
9398
|
+
aslr_enabled: status.memory_protection.aslr_enabled,
|
|
9399
|
+
stack_canaries: status.memory_protection.stack_canaries,
|
|
9400
|
+
secure_buffer_zeros: status.memory_protection.secure_buffer_zeros,
|
|
9401
|
+
argon2id_kdf: status.memory_protection.argon2id_kdf,
|
|
9402
|
+
overall: status.memory_protection.overall
|
|
9403
|
+
},
|
|
9404
|
+
process_isolation: {
|
|
9405
|
+
isolation_level: status.process_isolation.isolation_level,
|
|
9406
|
+
is_container: status.process_isolation.is_container,
|
|
9407
|
+
is_vm: status.process_isolation.is_vm,
|
|
9408
|
+
is_sandboxed: status.process_isolation.is_sandboxed,
|
|
9409
|
+
is_tee: status.process_isolation.is_tee,
|
|
9410
|
+
details: status.process_isolation.details
|
|
9411
|
+
},
|
|
9412
|
+
filesystem_permissions: {
|
|
9413
|
+
sanctuary_storage_protected: status.filesystem_permissions.sanctuary_storage_protected,
|
|
9414
|
+
sanctuary_storage_mode: status.filesystem_permissions.sanctuary_storage_mode,
|
|
9415
|
+
owner_is_current_user: status.filesystem_permissions.owner_is_current_user,
|
|
9416
|
+
group_readable: status.filesystem_permissions.group_readable,
|
|
9417
|
+
others_readable: status.filesystem_permissions.others_readable,
|
|
9418
|
+
overall: status.filesystem_permissions.overall
|
|
9419
|
+
},
|
|
9420
|
+
runtime_integrity: {
|
|
9421
|
+
config_hash_stable: status.runtime_integrity.config_hash_stable,
|
|
9422
|
+
environment_state: status.runtime_integrity.environment_state,
|
|
9423
|
+
discrepancies: status.runtime_integrity.discrepancies
|
|
9424
|
+
}
|
|
9425
|
+
});
|
|
9426
|
+
} else {
|
|
9427
|
+
return toolResult({
|
|
9428
|
+
hardening_level: status.hardening_level,
|
|
9429
|
+
summary: status.summary,
|
|
9430
|
+
checks_passed: status.checks_passed,
|
|
9431
|
+
checks_total: status.checks_total,
|
|
9432
|
+
note: "Pass include_details: true to see full breakdown of memory, process isolation, and filesystem checks."
|
|
9433
|
+
});
|
|
9434
|
+
}
|
|
9435
|
+
}
|
|
9436
|
+
},
|
|
9437
|
+
{
|
|
9438
|
+
name: "sanctuary/l2_verify_isolation",
|
|
9439
|
+
description: "Verify L2 process isolation at runtime. Checks whether the Sanctuary server is running in an isolated environment (container, VM, sandbox) and validates filesystem and memory protections. Reports isolation level and any issues. Read-only. Tier 3 \u2014 always allowed.",
|
|
9440
|
+
inputSchema: {
|
|
9441
|
+
type: "object",
|
|
9442
|
+
properties: {
|
|
9443
|
+
check_filesystem: {
|
|
9444
|
+
type: "boolean",
|
|
9445
|
+
description: "If true, verify Sanctuary storage directory permissions.",
|
|
9446
|
+
default: true
|
|
9447
|
+
},
|
|
9448
|
+
check_memory: {
|
|
9449
|
+
type: "boolean",
|
|
9450
|
+
description: "If true, verify memory protection mechanisms (ASLR, etc.).",
|
|
9451
|
+
default: true
|
|
9452
|
+
},
|
|
9453
|
+
check_process: {
|
|
9454
|
+
type: "boolean",
|
|
9455
|
+
description: "If true, detect container, VM, or sandbox environment.",
|
|
9456
|
+
default: true
|
|
9457
|
+
}
|
|
9458
|
+
}
|
|
9459
|
+
},
|
|
9460
|
+
handler: async (args) => {
|
|
9461
|
+
const checkFilesystem = args.check_filesystem ?? true;
|
|
9462
|
+
const checkMemory = args.check_memory ?? true;
|
|
9463
|
+
const checkProcess = args.check_process ?? true;
|
|
9464
|
+
const status = assessL2Hardening(storagePath);
|
|
9465
|
+
auditLog.append(
|
|
9466
|
+
"l2",
|
|
9467
|
+
"l2_verify_isolation",
|
|
9468
|
+
"system",
|
|
9469
|
+
{
|
|
9470
|
+
check_filesystem: checkFilesystem,
|
|
9471
|
+
check_memory: checkMemory,
|
|
9472
|
+
check_process: checkProcess
|
|
9473
|
+
}
|
|
9474
|
+
);
|
|
9475
|
+
const results = {
|
|
9476
|
+
isolation_level: status.hardening_level,
|
|
9477
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9478
|
+
};
|
|
9479
|
+
if (checkFilesystem) {
|
|
9480
|
+
const fs = status.filesystem_permissions;
|
|
9481
|
+
results.filesystem = {
|
|
9482
|
+
sanctuary_storage_protected: fs.sanctuary_storage_protected,
|
|
9483
|
+
storage_mode: fs.sanctuary_storage_mode,
|
|
9484
|
+
is_secure: fs.overall === "secure",
|
|
9485
|
+
issues: fs.overall === "insecure" ? [
|
|
9486
|
+
"Storage directory is readable by group or others. Recommend: chmod 700 on Sanctuary storage path."
|
|
9487
|
+
] : fs.overall === "warning" ? [
|
|
9488
|
+
"Storage directory not owned by current user. Verify correct user is running Sanctuary."
|
|
9489
|
+
] : []
|
|
9490
|
+
};
|
|
9491
|
+
}
|
|
9492
|
+
if (checkMemory) {
|
|
9493
|
+
const mem = status.memory_protection;
|
|
9494
|
+
const issues = [];
|
|
9495
|
+
if (!mem.aslr_enabled) {
|
|
9496
|
+
issues.push(
|
|
9497
|
+
"ASLR not detected. On Linux, enable with: echo 2 | sudo tee /proc/sys/kernel/randomize_va_space"
|
|
9498
|
+
);
|
|
9499
|
+
}
|
|
9500
|
+
results.memory = {
|
|
9501
|
+
aslr_enabled: mem.aslr_enabled,
|
|
9502
|
+
stack_canaries: mem.stack_canaries,
|
|
9503
|
+
secure_buffer_handling: mem.secure_buffer_zeros,
|
|
9504
|
+
argon2id_key_derivation: mem.argon2id_kdf,
|
|
9505
|
+
protection_level: mem.overall,
|
|
9506
|
+
issues
|
|
9507
|
+
};
|
|
9508
|
+
}
|
|
9509
|
+
if (checkProcess) {
|
|
9510
|
+
const iso = status.process_isolation;
|
|
9511
|
+
results.process = {
|
|
9512
|
+
isolation_level: iso.isolation_level,
|
|
9513
|
+
in_container: iso.is_container,
|
|
9514
|
+
in_vm: iso.is_vm,
|
|
9515
|
+
sandboxed: iso.is_sandboxed,
|
|
9516
|
+
has_tee: iso.is_tee,
|
|
9517
|
+
environment: iso.details,
|
|
9518
|
+
recommendation: iso.isolation_level === "none" ? "Consider running Sanctuary in a container or VM for improved isolation." : iso.isolation_level === "basic" ? "Basic isolation detected. Container or VM would provide stronger guarantees." : "Running in isolated environment \u2014 process-level isolation is strong."
|
|
9519
|
+
};
|
|
9520
|
+
}
|
|
9521
|
+
return toolResult({
|
|
9522
|
+
status: "verified",
|
|
9523
|
+
results
|
|
9524
|
+
});
|
|
9525
|
+
}
|
|
9526
|
+
}
|
|
9527
|
+
];
|
|
9528
|
+
}
|
|
9529
|
+
|
|
9530
|
+
// src/index.ts
|
|
9531
|
+
init_encoding();
|
|
9532
|
+
|
|
9533
|
+
// src/storage/memory.ts
|
|
9534
|
+
var MemoryStorage = class {
|
|
9535
|
+
store = /* @__PURE__ */ new Map();
|
|
9536
|
+
storageKey(namespace, key) {
|
|
9537
|
+
return `${namespace}/${key}`;
|
|
9538
|
+
}
|
|
9539
|
+
async write(namespace, key, data) {
|
|
9540
|
+
this.store.set(this.storageKey(namespace, key), {
|
|
9541
|
+
data: new Uint8Array(data),
|
|
9542
|
+
// Copy to prevent external mutation
|
|
9543
|
+
modified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9544
|
+
});
|
|
9545
|
+
}
|
|
9546
|
+
async read(namespace, key) {
|
|
9547
|
+
const entry = this.store.get(this.storageKey(namespace, key));
|
|
9548
|
+
if (!entry) return null;
|
|
9549
|
+
return new Uint8Array(entry.data);
|
|
9550
|
+
}
|
|
9551
|
+
async delete(namespace, key, _secureOverwrite) {
|
|
9552
|
+
return this.store.delete(this.storageKey(namespace, key));
|
|
9553
|
+
}
|
|
9554
|
+
async list(namespace, prefix) {
|
|
9555
|
+
const entries = [];
|
|
9556
|
+
const nsPrefix = `${namespace}/`;
|
|
9557
|
+
for (const [storeKey, entry] of this.store) {
|
|
9558
|
+
if (!storeKey.startsWith(nsPrefix)) continue;
|
|
9559
|
+
const key = storeKey.slice(nsPrefix.length);
|
|
9560
|
+
if (prefix && !key.startsWith(prefix)) continue;
|
|
9561
|
+
entries.push({
|
|
9562
|
+
key,
|
|
9563
|
+
namespace,
|
|
9564
|
+
size_bytes: entry.data.length,
|
|
9565
|
+
modified_at: entry.modified_at
|
|
9566
|
+
});
|
|
9567
|
+
}
|
|
9568
|
+
return entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
9569
|
+
}
|
|
9570
|
+
async exists(namespace, key) {
|
|
9571
|
+
return this.store.has(this.storageKey(namespace, key));
|
|
9572
|
+
}
|
|
9573
|
+
async totalSize() {
|
|
9574
|
+
let total = 0;
|
|
9575
|
+
for (const entry of this.store.values()) {
|
|
9576
|
+
total += entry.data.length;
|
|
9577
|
+
}
|
|
9578
|
+
return total;
|
|
9579
|
+
}
|
|
9580
|
+
/** Clear all stored data (useful in tests) */
|
|
9581
|
+
clear() {
|
|
9582
|
+
this.store.clear();
|
|
9583
|
+
}
|
|
9584
|
+
};
|
|
9585
|
+
|
|
9586
|
+
// src/index.ts
|
|
9587
|
+
async function createSanctuaryServer(options) {
|
|
9588
|
+
const config = await loadConfig(options?.configPath);
|
|
9589
|
+
await promises.mkdir(config.storage_path, { recursive: true, mode: 448 });
|
|
9590
|
+
const storage = options?.storage ?? new FilesystemStorage(
|
|
9591
|
+
`${config.storage_path}/state`
|
|
9592
|
+
);
|
|
9593
|
+
let masterKey;
|
|
9594
|
+
let keyProtection;
|
|
9595
|
+
let recoveryKey;
|
|
9596
|
+
const passphrase = options?.passphrase ?? process.env.SANCTUARY_PASSPHRASE;
|
|
9597
|
+
if (passphrase) {
|
|
9598
|
+
keyProtection = "passphrase";
|
|
9599
|
+
let existingParams;
|
|
7645
9600
|
try {
|
|
7646
9601
|
const raw = await storage.read("_meta", "key-params");
|
|
7647
9602
|
if (raw) {
|
|
@@ -7793,7 +9748,7 @@ async function createSanctuaryServer(options) {
|
|
|
7793
9748
|
layer: "l2",
|
|
7794
9749
|
description: "Process-level isolation only (no TEE)",
|
|
7795
9750
|
severity: "warning",
|
|
7796
|
-
mitigation: "TEE support planned for
|
|
9751
|
+
mitigation: "TEE support planned for a future release"
|
|
7797
9752
|
});
|
|
7798
9753
|
if (config.disclosure.proof_system === "commitment-only") {
|
|
7799
9754
|
degradations.push({
|
|
@@ -7933,7 +9888,7 @@ async function createSanctuaryServer(options) {
|
|
|
7933
9888
|
},
|
|
7934
9889
|
limitations: [
|
|
7935
9890
|
"L1 identity uses ed25519 only; KERI support planned for v0.2.0",
|
|
7936
|
-
"L2 isolation is process-level only; TEE support planned for
|
|
9891
|
+
"L2 isolation is process-level only; TEE support planned for a future release",
|
|
7937
9892
|
"L3 uses commitment schemes only; ZK proofs planned for v0.2.0",
|
|
7938
9893
|
"L4 Sybil resistance is escrow-based only",
|
|
7939
9894
|
"Spec license: CC-BY-4.0 | Code license: Apache-2.0"
|
|
@@ -7954,7 +9909,7 @@ async function createSanctuaryServer(options) {
|
|
|
7954
9909
|
masterKey,
|
|
7955
9910
|
auditLog
|
|
7956
9911
|
);
|
|
7957
|
-
const { tools: l4Tools
|
|
9912
|
+
const { tools: l4Tools} = createL4Tools(
|
|
7958
9913
|
storage,
|
|
7959
9914
|
masterKey,
|
|
7960
9915
|
identityManager,
|
|
@@ -7973,6 +9928,12 @@ async function createSanctuaryServer(options) {
|
|
|
7973
9928
|
handshakeResults
|
|
7974
9929
|
);
|
|
7975
9930
|
const { tools: auditTools } = createAuditTools(config);
|
|
9931
|
+
const { tools: contextGateTools } = createContextGateTools(
|
|
9932
|
+
storage,
|
|
9933
|
+
masterKey,
|
|
9934
|
+
auditLog
|
|
9935
|
+
);
|
|
9936
|
+
const hardeningTools = createL2HardeningTools(config.storage_path, auditLog);
|
|
7976
9937
|
const policy = await loadPrincipalPolicy(config.storage_path);
|
|
7977
9938
|
const baseline = new BaselineTracker(storage, masterKey);
|
|
7978
9939
|
await baseline.load();
|
|
@@ -8022,6 +9983,8 @@ async function createSanctuaryServer(options) {
|
|
|
8022
9983
|
...federationTools,
|
|
8023
9984
|
...bridgeTools,
|
|
8024
9985
|
...auditTools,
|
|
9986
|
+
...contextGateTools,
|
|
9987
|
+
...hardeningTools,
|
|
8025
9988
|
manifestTool
|
|
8026
9989
|
];
|
|
8027
9990
|
const server = createServer(allTools, { gate });
|
|
@@ -8051,8 +10014,10 @@ exports.ApprovalGate = ApprovalGate;
|
|
|
8051
10014
|
exports.AuditLog = AuditLog;
|
|
8052
10015
|
exports.AutoApproveChannel = AutoApproveChannel;
|
|
8053
10016
|
exports.BaselineTracker = BaselineTracker;
|
|
10017
|
+
exports.CONTEXT_GATE_TEMPLATES = TEMPLATES;
|
|
8054
10018
|
exports.CallbackApprovalChannel = CallbackApprovalChannel;
|
|
8055
10019
|
exports.CommitmentStore = CommitmentStore;
|
|
10020
|
+
exports.ContextGatePolicyStore = ContextGatePolicyStore;
|
|
8056
10021
|
exports.DashboardApprovalChannel = DashboardApprovalChannel;
|
|
8057
10022
|
exports.FederationRegistry = FederationRegistry;
|
|
8058
10023
|
exports.FilesystemStorage = FilesystemStorage;
|
|
@@ -8064,6 +10029,7 @@ exports.StderrApprovalChannel = StderrApprovalChannel;
|
|
|
8064
10029
|
exports.TIER_WEIGHTS = TIER_WEIGHTS;
|
|
8065
10030
|
exports.WebhookApprovalChannel = WebhookApprovalChannel;
|
|
8066
10031
|
exports.canonicalize = canonicalize;
|
|
10032
|
+
exports.classifyField = classifyField;
|
|
8067
10033
|
exports.completeHandshake = completeHandshake;
|
|
8068
10034
|
exports.computeWeightedScore = computeWeightedScore;
|
|
8069
10035
|
exports.createBridgeCommitment = createBridgeCommitment;
|
|
@@ -8071,10 +10037,15 @@ exports.createPedersenCommitment = createPedersenCommitment;
|
|
|
8071
10037
|
exports.createProofOfKnowledge = createProofOfKnowledge;
|
|
8072
10038
|
exports.createRangeProof = createRangeProof;
|
|
8073
10039
|
exports.createSanctuaryServer = createSanctuaryServer;
|
|
10040
|
+
exports.evaluateField = evaluateField;
|
|
10041
|
+
exports.filterContext = filterContext;
|
|
8074
10042
|
exports.generateSHR = generateSHR;
|
|
10043
|
+
exports.getTemplate = getTemplate;
|
|
8075
10044
|
exports.initiateHandshake = initiateHandshake;
|
|
10045
|
+
exports.listTemplateIds = listTemplateIds;
|
|
8076
10046
|
exports.loadConfig = loadConfig;
|
|
8077
10047
|
exports.loadPrincipalPolicy = loadPrincipalPolicy;
|
|
10048
|
+
exports.recommendPolicy = recommendPolicy;
|
|
8078
10049
|
exports.resolveTier = resolveTier;
|
|
8079
10050
|
exports.respondToHandshake = respondToHandshake;
|
|
8080
10051
|
exports.signPayload = signPayload;
|