@sanctuary-framework/mcp-server 0.5.15 → 0.6.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 +603 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +603 -16
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +601 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +601 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -73,6 +73,12 @@ function defaultConfig() {
|
|
|
73
73
|
secret: "",
|
|
74
74
|
callback_port: 3502,
|
|
75
75
|
callback_host: "127.0.0.1"
|
|
76
|
+
},
|
|
77
|
+
verascore: {
|
|
78
|
+
url: "https://verascore.ai",
|
|
79
|
+
auto_publish_to_verascore: true,
|
|
80
|
+
// DELTA-04: default OFF for privacy. Enable explicitly per deployment.
|
|
81
|
+
auto_publish_handshakes: false
|
|
76
82
|
}
|
|
77
83
|
};
|
|
78
84
|
}
|
|
@@ -143,6 +149,21 @@ async function loadConfig(configPath) {
|
|
|
143
149
|
if (process.env.SANCTUARY_WEBHOOK_CALLBACK_HOST) {
|
|
144
150
|
config.webhook.callback_host = process.env.SANCTUARY_WEBHOOK_CALLBACK_HOST;
|
|
145
151
|
}
|
|
152
|
+
if (process.env.SANCTUARY_VERASCORE_URL) {
|
|
153
|
+
config.verascore.url = process.env.SANCTUARY_VERASCORE_URL;
|
|
154
|
+
}
|
|
155
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_TO_VERASCORE === "true") {
|
|
156
|
+
config.verascore.auto_publish_to_verascore = true;
|
|
157
|
+
}
|
|
158
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_TO_VERASCORE === "false") {
|
|
159
|
+
config.verascore.auto_publish_to_verascore = false;
|
|
160
|
+
}
|
|
161
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_HANDSHAKES === "true") {
|
|
162
|
+
config.verascore.auto_publish_handshakes = true;
|
|
163
|
+
}
|
|
164
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_HANDSHAKES === "false") {
|
|
165
|
+
config.verascore.auto_publish_handshakes = false;
|
|
166
|
+
}
|
|
146
167
|
config.version = PKG_VERSION;
|
|
147
168
|
validateConfig(config);
|
|
148
169
|
return config;
|
|
@@ -1643,9 +1664,10 @@ tier1_always_approve:
|
|
|
1643
1664
|
- reputation_import
|
|
1644
1665
|
- reputation_export
|
|
1645
1666
|
- bootstrap_provide_guarantee
|
|
1646
|
-
- reputation_publish
|
|
1647
1667
|
- sovereignty_profile_update
|
|
1648
1668
|
- governor_reset
|
|
1669
|
+
- sanctuary_bootstrap
|
|
1670
|
+
- sanctuary_export_identity_bundle
|
|
1649
1671
|
|
|
1650
1672
|
# \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
|
|
1651
1673
|
# Triggers approval when agent behavior deviates from its baseline.
|
|
@@ -1711,6 +1733,10 @@ tier3_always_allow:
|
|
|
1711
1733
|
- dashboard_open
|
|
1712
1734
|
- sovereignty_profile_get
|
|
1713
1735
|
- governor_status
|
|
1736
|
+
- reputation_publish
|
|
1737
|
+
- sanctuary_policy_status
|
|
1738
|
+
- sanctuary_link_to_human
|
|
1739
|
+
- sanctuary_sign_challenge
|
|
1714
1740
|
|
|
1715
1741
|
# \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
|
|
1716
1742
|
# How Sanctuary reaches you when approval is needed.
|
|
@@ -1764,12 +1790,14 @@ var init_loader = __esm({
|
|
|
1764
1790
|
"reputation_export",
|
|
1765
1791
|
"bootstrap_provide_guarantee",
|
|
1766
1792
|
"decommission_certificate",
|
|
1767
|
-
"reputation_publish",
|
|
1768
|
-
// SEC-039: Explicit Tier 1 — sends data to external API
|
|
1769
1793
|
"sovereignty_profile_update",
|
|
1770
1794
|
// Changes enforcement behavior — always requires approval
|
|
1771
|
-
"governor_reset"
|
|
1795
|
+
"governor_reset",
|
|
1772
1796
|
// Clears all runtime governance state — always requires approval
|
|
1797
|
+
"sanctuary_bootstrap",
|
|
1798
|
+
// Creates new Ed25519 identity + publishes — always requires approval
|
|
1799
|
+
"sanctuary_export_identity_bundle"
|
|
1800
|
+
// Exports portable identity — always requires approval
|
|
1773
1801
|
],
|
|
1774
1802
|
tier2_anomaly: DEFAULT_TIER2,
|
|
1775
1803
|
tier3_always_allow: [
|
|
@@ -1827,7 +1855,11 @@ var init_loader = __esm({
|
|
|
1827
1855
|
"sovereignty_profile_get",
|
|
1828
1856
|
"sovereignty_profile_generate_prompt",
|
|
1829
1857
|
// Agent needs its own config to generate system prompt
|
|
1830
|
-
"governor_status"
|
|
1858
|
+
"governor_status",
|
|
1859
|
+
"reputation_publish",
|
|
1860
|
+
// Auto-allow: publishing sovereignty data to Verascore is routine
|
|
1861
|
+
"sanctuary_policy_status"
|
|
1862
|
+
// Read-only policy summary
|
|
1831
1863
|
],
|
|
1832
1864
|
approval_channel: DEFAULT_CHANNEL
|
|
1833
1865
|
};
|
|
@@ -2341,7 +2373,7 @@ function generateLoginHTML(options) {
|
|
|
2341
2373
|
if (response.ok) {
|
|
2342
2374
|
const data = await response.json();
|
|
2343
2375
|
sessionStorage.setItem('authToken', token);
|
|
2344
|
-
window.location.href = '/
|
|
2376
|
+
window.location.href = '/'; // Dashboard is served at root path
|
|
2345
2377
|
} else if (response.status === 401) {
|
|
2346
2378
|
showError('Invalid token. Please check and try again.');
|
|
2347
2379
|
} else {
|
|
@@ -5292,7 +5324,24 @@ var init_dashboard = __esm({
|
|
|
5292
5324
|
}
|
|
5293
5325
|
resolve();
|
|
5294
5326
|
});
|
|
5295
|
-
this.httpServer.on("error",
|
|
5327
|
+
this.httpServer.on("error", (err) => {
|
|
5328
|
+
if (err.code === "EADDRINUSE") {
|
|
5329
|
+
const port = this.config.port;
|
|
5330
|
+
process.stderr.write(
|
|
5331
|
+
`
|
|
5332
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
5333
|
+
\u2551 Port ${port} is already in use. \u2551
|
|
5334
|
+
\u2551 \u2551
|
|
5335
|
+
\u2551 Another Sanctuary Dashboard may still be running. \u2551
|
|
5336
|
+
\u2551 To fix: lsof -ti:${port} | xargs kill \u2551
|
|
5337
|
+
\u2551 Then restart the dashboard. \u2551
|
|
5338
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
5339
|
+
|
|
5340
|
+
`
|
|
5341
|
+
);
|
|
5342
|
+
}
|
|
5343
|
+
reject(err);
|
|
5344
|
+
});
|
|
5296
5345
|
});
|
|
5297
5346
|
}
|
|
5298
5347
|
/**
|
|
@@ -5572,7 +5621,7 @@ var init_dashboard = __esm({
|
|
|
5572
5621
|
if (!this.checkAuth(req, url, res)) return;
|
|
5573
5622
|
if (!this.checkRateLimit(req, res, "general")) return;
|
|
5574
5623
|
try {
|
|
5575
|
-
if (method === "GET" && url.pathname === "/") {
|
|
5624
|
+
if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
|
|
5576
5625
|
this.serveDashboard(res);
|
|
5577
5626
|
} else if (method === "GET" && url.pathname === "/events") {
|
|
5578
5627
|
this.handleSSE(req, res);
|
|
@@ -8183,7 +8232,7 @@ function tierDistribution(tiers) {
|
|
|
8183
8232
|
}
|
|
8184
8233
|
|
|
8185
8234
|
// src/l4-reputation/tools.ts
|
|
8186
|
-
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults) {
|
|
8235
|
+
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults, verascoreUrl) {
|
|
8187
8236
|
const reputationStore = new ReputationStore(storage, masterKey);
|
|
8188
8237
|
const identityEncryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
8189
8238
|
const hsResults = handshakeResults ?? /* @__PURE__ */ new Map();
|
|
@@ -8673,8 +8722,18 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
8673
8722
|
});
|
|
8674
8723
|
}
|
|
8675
8724
|
const publishType = args.type;
|
|
8676
|
-
const
|
|
8677
|
-
const
|
|
8725
|
+
const configuredVerascoreUrl = verascoreUrl || "https://verascore.ai";
|
|
8726
|
+
const veracoreUrl = args.verascore_url || configuredVerascoreUrl;
|
|
8727
|
+
const ALLOWED_VERASCORE_HOSTS = /* @__PURE__ */ new Set([
|
|
8728
|
+
"verascore.ai",
|
|
8729
|
+
"www.verascore.ai",
|
|
8730
|
+
"api.verascore.ai"
|
|
8731
|
+
]);
|
|
8732
|
+
try {
|
|
8733
|
+
const configuredHost = new URL(configuredVerascoreUrl).hostname;
|
|
8734
|
+
ALLOWED_VERASCORE_HOSTS.add(configuredHost);
|
|
8735
|
+
} catch {
|
|
8736
|
+
}
|
|
8678
8737
|
try {
|
|
8679
8738
|
const parsed = new URL(veracoreUrl);
|
|
8680
8739
|
if (parsed.protocol !== "https:") {
|
|
@@ -8682,9 +8741,9 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
8682
8741
|
error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
|
|
8683
8742
|
});
|
|
8684
8743
|
}
|
|
8685
|
-
if (!ALLOWED_VERASCORE_HOSTS.
|
|
8744
|
+
if (!ALLOWED_VERASCORE_HOSTS.has(parsed.hostname)) {
|
|
8686
8745
|
return toolResult({
|
|
8687
|
-
error: `verascore_url must point to a known Verascore domain (${ALLOWED_VERASCORE_HOSTS.join(", ")}). Got: ${parsed.hostname}`
|
|
8746
|
+
error: `verascore_url must point to a known Verascore domain (${[...ALLOWED_VERASCORE_HOSTS].join(", ")}). Got: ${parsed.hostname}`
|
|
8688
8747
|
});
|
|
8689
8748
|
}
|
|
8690
8749
|
} catch {
|
|
@@ -11013,6 +11072,9 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
11013
11072
|
// src/handshake/tools.ts
|
|
11014
11073
|
init_router();
|
|
11015
11074
|
init_generator();
|
|
11075
|
+
init_identity();
|
|
11076
|
+
init_key_derivation();
|
|
11077
|
+
init_encoding();
|
|
11016
11078
|
|
|
11017
11079
|
// src/handshake/protocol.ts
|
|
11018
11080
|
init_identity();
|
|
@@ -11337,7 +11399,10 @@ function verifyAttestation(attestation, now) {
|
|
|
11337
11399
|
}
|
|
11338
11400
|
|
|
11339
11401
|
// src/handshake/tools.ts
|
|
11340
|
-
function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
11402
|
+
function createHandshakeTools(config, identityManager, masterKey, auditLog, options) {
|
|
11403
|
+
const autoPublishHandshakes = options?.autoPublishHandshakes ?? false;
|
|
11404
|
+
const verascoreUrl = options?.verascoreUrl ?? "https://verascore.ai";
|
|
11405
|
+
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
11341
11406
|
const sessions = /* @__PURE__ */ new Map();
|
|
11342
11407
|
const handshakeResults = /* @__PURE__ */ new Map();
|
|
11343
11408
|
const shrOpts = {
|
|
@@ -11409,10 +11474,86 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
|
11409
11474
|
}
|
|
11410
11475
|
sessions.set(result.session.session_id, result.session);
|
|
11411
11476
|
auditLog.append("l4", "handshake_respond", shr.body.instance_id);
|
|
11477
|
+
let autoPublishResult;
|
|
11478
|
+
if (autoPublishHandshakes) {
|
|
11479
|
+
autoPublishResult = { attempted: true };
|
|
11480
|
+
try {
|
|
11481
|
+
const parsed = new URL(verascoreUrl);
|
|
11482
|
+
if (parsed.protocol !== "https:") {
|
|
11483
|
+
autoPublishResult.error = `verascore URL must use HTTPS (got ${parsed.protocol})`;
|
|
11484
|
+
} else {
|
|
11485
|
+
const attestationPayload = {
|
|
11486
|
+
type: "handshake",
|
|
11487
|
+
our_shr_signed_by: shr.signed_by,
|
|
11488
|
+
counterparty_signed_by: "redacted",
|
|
11489
|
+
session_id: result.session.session_id,
|
|
11490
|
+
responded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11491
|
+
};
|
|
11492
|
+
const responderIdentity = identityManager.get(shr.body.instance_id);
|
|
11493
|
+
if (!responderIdentity) {
|
|
11494
|
+
autoPublishResult.error = `responder identity ${shr.body.instance_id} not found; skipping auto-publish`;
|
|
11495
|
+
auditLog.append(
|
|
11496
|
+
"l4",
|
|
11497
|
+
"handshake_auto_publish",
|
|
11498
|
+
shr.body.instance_id,
|
|
11499
|
+
{ error: autoPublishResult.error },
|
|
11500
|
+
"failure"
|
|
11501
|
+
);
|
|
11502
|
+
} else {
|
|
11503
|
+
const payloadBytes = new TextEncoder().encode(
|
|
11504
|
+
JSON.stringify(attestationPayload)
|
|
11505
|
+
);
|
|
11506
|
+
const sigBytes = sign(
|
|
11507
|
+
payloadBytes,
|
|
11508
|
+
responderIdentity.encrypted_private_key,
|
|
11509
|
+
identityEncKey
|
|
11510
|
+
);
|
|
11511
|
+
const signatureB64 = toBase64url(sigBytes);
|
|
11512
|
+
const resp = await fetch(
|
|
11513
|
+
`${verascoreUrl.replace(/\/$/, "")}/api/publish`,
|
|
11514
|
+
{
|
|
11515
|
+
method: "POST",
|
|
11516
|
+
headers: { "Content-Type": "application/json" },
|
|
11517
|
+
body: JSON.stringify({
|
|
11518
|
+
agentId: shr.body.instance_id,
|
|
11519
|
+
publicKey: shr.signed_by,
|
|
11520
|
+
signature: signatureB64,
|
|
11521
|
+
type: "handshake",
|
|
11522
|
+
data: attestationPayload
|
|
11523
|
+
})
|
|
11524
|
+
}
|
|
11525
|
+
);
|
|
11526
|
+
autoPublishResult.ok = resp.ok;
|
|
11527
|
+
autoPublishResult.status = resp.status;
|
|
11528
|
+
auditLog.append(
|
|
11529
|
+
"l4",
|
|
11530
|
+
"handshake_auto_publish",
|
|
11531
|
+
shr.body.instance_id,
|
|
11532
|
+
{
|
|
11533
|
+
verascore_url: verascoreUrl,
|
|
11534
|
+
status: resp.status,
|
|
11535
|
+
ok: resp.ok
|
|
11536
|
+
},
|
|
11537
|
+
resp.ok ? "success" : "failure"
|
|
11538
|
+
);
|
|
11539
|
+
}
|
|
11540
|
+
}
|
|
11541
|
+
} catch (err) {
|
|
11542
|
+
autoPublishResult.error = err instanceof Error ? err.message : String(err);
|
|
11543
|
+
auditLog.append(
|
|
11544
|
+
"l4",
|
|
11545
|
+
"handshake_auto_publish",
|
|
11546
|
+
shr.body.instance_id,
|
|
11547
|
+
{ verascore_url: verascoreUrl, error: autoPublishResult.error },
|
|
11548
|
+
"failure"
|
|
11549
|
+
);
|
|
11550
|
+
}
|
|
11551
|
+
}
|
|
11412
11552
|
return toolResult({
|
|
11413
11553
|
session_id: result.session.session_id,
|
|
11414
11554
|
response: result.response,
|
|
11415
11555
|
instructions: "Send the 'response' object back to the initiator. When you receive their completion, pass it to sanctuary/handshake_status with this session_id.",
|
|
11556
|
+
auto_publish: autoPublishResult,
|
|
11416
11557
|
// SEC-ADD-03: Tag response — contains SHR data that will be sent to counterparty
|
|
11417
11558
|
_content_trust: "external"
|
|
11418
11559
|
});
|
|
@@ -16092,6 +16233,438 @@ function createGovernorTools(governor, auditLog) {
|
|
|
16092
16233
|
return { tools };
|
|
16093
16234
|
}
|
|
16094
16235
|
|
|
16236
|
+
// src/sanctuary-tools.ts
|
|
16237
|
+
init_router();
|
|
16238
|
+
init_identity();
|
|
16239
|
+
init_key_derivation();
|
|
16240
|
+
init_encoding();
|
|
16241
|
+
init_identity();
|
|
16242
|
+
init_generator();
|
|
16243
|
+
function validateVerascoreUrl(urlStr, configuredUrl) {
|
|
16244
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
16245
|
+
"verascore.ai",
|
|
16246
|
+
"www.verascore.ai",
|
|
16247
|
+
"api.verascore.ai"
|
|
16248
|
+
]);
|
|
16249
|
+
try {
|
|
16250
|
+
allowed.add(new URL(configuredUrl).hostname);
|
|
16251
|
+
} catch {
|
|
16252
|
+
}
|
|
16253
|
+
try {
|
|
16254
|
+
const parsed = new URL(urlStr);
|
|
16255
|
+
if (parsed.protocol !== "https:") {
|
|
16256
|
+
return { ok: false, error: `Verascore URL must use HTTPS. Got: ${parsed.protocol}` };
|
|
16257
|
+
}
|
|
16258
|
+
if (!allowed.has(parsed.hostname)) {
|
|
16259
|
+
return {
|
|
16260
|
+
ok: false,
|
|
16261
|
+
error: `Verascore URL must point to a known Verascore host (${[...allowed].join(", ")}). Got: ${parsed.hostname}`
|
|
16262
|
+
};
|
|
16263
|
+
}
|
|
16264
|
+
return { ok: true };
|
|
16265
|
+
} catch {
|
|
16266
|
+
return { ok: false, error: `Invalid Verascore URL: ${urlStr}` };
|
|
16267
|
+
}
|
|
16268
|
+
}
|
|
16269
|
+
function createSanctuaryTools(opts) {
|
|
16270
|
+
const { config, identityManager, masterKey, auditLog, policy, keyProtection } = opts;
|
|
16271
|
+
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
16272
|
+
const tools = [
|
|
16273
|
+
// ─── sanctuary_bootstrap ───────────────────────────────────────────
|
|
16274
|
+
{
|
|
16275
|
+
name: "sanctuary/sanctuary_bootstrap",
|
|
16276
|
+
description: "One-shot bootstrap for a new sovereign agent identity. Generates an Ed25519 keypair, stores the encrypted identity, constructs a Sovereignty Health Report (SHR), and publishes it to Verascore. Returns { did, profileUrl, tier } for the newly-minted agent.",
|
|
16277
|
+
inputSchema: {
|
|
16278
|
+
type: "object",
|
|
16279
|
+
properties: {
|
|
16280
|
+
label: {
|
|
16281
|
+
type: "string",
|
|
16282
|
+
description: "Human-readable label for the new identity (default: 'sovereign-agent')"
|
|
16283
|
+
},
|
|
16284
|
+
verascore_url: {
|
|
16285
|
+
type: "string",
|
|
16286
|
+
description: "Verascore base URL. Defaults to server config / SANCTUARY_VERASCORE_URL."
|
|
16287
|
+
},
|
|
16288
|
+
publish: {
|
|
16289
|
+
type: "boolean",
|
|
16290
|
+
description: "Whether to publish the SHR to Verascore. Defaults to true."
|
|
16291
|
+
}
|
|
16292
|
+
}
|
|
16293
|
+
},
|
|
16294
|
+
handler: async (args) => {
|
|
16295
|
+
const label = args.label || "sovereign-agent";
|
|
16296
|
+
const publish = args.publish === void 0 ? true : Boolean(args.publish);
|
|
16297
|
+
const verascoreUrl = args.verascore_url || config.verascore.url || "https://verascore.ai";
|
|
16298
|
+
const { publicIdentity, storedIdentity } = createIdentity(
|
|
16299
|
+
label,
|
|
16300
|
+
identityEncKey,
|
|
16301
|
+
keyProtection
|
|
16302
|
+
);
|
|
16303
|
+
await identityManager.save(storedIdentity);
|
|
16304
|
+
auditLog.append("l1", "sanctuary_bootstrap:identity_create", publicIdentity.identity_id, {
|
|
16305
|
+
label,
|
|
16306
|
+
did: publicIdentity.did
|
|
16307
|
+
});
|
|
16308
|
+
const shr = generateSHR(publicIdentity.identity_id, {
|
|
16309
|
+
config,
|
|
16310
|
+
identityManager,
|
|
16311
|
+
masterKey
|
|
16312
|
+
});
|
|
16313
|
+
if (typeof shr === "string") {
|
|
16314
|
+
return toolResult({
|
|
16315
|
+
error: `Identity created but SHR generation failed: ${shr}`,
|
|
16316
|
+
did: publicIdentity.did,
|
|
16317
|
+
identity_id: publicIdentity.identity_id
|
|
16318
|
+
});
|
|
16319
|
+
}
|
|
16320
|
+
const agentSlug = publicIdentity.did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
16321
|
+
const profileUrl = `${verascoreUrl.replace(/\/$/, "")}/agent/${publicIdentity.did}`;
|
|
16322
|
+
if (!publish || !config.verascore.auto_publish_to_verascore) {
|
|
16323
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16324
|
+
did: publicIdentity.did,
|
|
16325
|
+
published: false
|
|
16326
|
+
});
|
|
16327
|
+
return toolResult({
|
|
16328
|
+
did: publicIdentity.did,
|
|
16329
|
+
identity_id: publicIdentity.identity_id,
|
|
16330
|
+
profileUrl,
|
|
16331
|
+
tier: "self-attested",
|
|
16332
|
+
published: false
|
|
16333
|
+
});
|
|
16334
|
+
}
|
|
16335
|
+
const urlCheck = validateVerascoreUrl(verascoreUrl, config.verascore.url);
|
|
16336
|
+
if (!urlCheck.ok) {
|
|
16337
|
+
return toolResult({
|
|
16338
|
+
error: urlCheck.error,
|
|
16339
|
+
did: publicIdentity.did,
|
|
16340
|
+
identity_id: publicIdentity.identity_id
|
|
16341
|
+
});
|
|
16342
|
+
}
|
|
16343
|
+
const publishData = {
|
|
16344
|
+
sovereigntyLayers: shr.body.layers,
|
|
16345
|
+
capabilities: shr.body.capabilities,
|
|
16346
|
+
degradations: shr.body.degradations,
|
|
16347
|
+
did: publicIdentity.did,
|
|
16348
|
+
label
|
|
16349
|
+
};
|
|
16350
|
+
const payloadBytes = new TextEncoder().encode(JSON.stringify(publishData));
|
|
16351
|
+
let signatureB64;
|
|
16352
|
+
try {
|
|
16353
|
+
const sigBytes = sign(
|
|
16354
|
+
payloadBytes,
|
|
16355
|
+
storedIdentity.encrypted_private_key,
|
|
16356
|
+
identityEncKey
|
|
16357
|
+
);
|
|
16358
|
+
signatureB64 = toBase64url(sigBytes);
|
|
16359
|
+
} catch (err) {
|
|
16360
|
+
return toolResult({
|
|
16361
|
+
error: "Failed to sign bootstrap payload",
|
|
16362
|
+
details: err instanceof Error ? err.message : String(err),
|
|
16363
|
+
did: publicIdentity.did
|
|
16364
|
+
});
|
|
16365
|
+
}
|
|
16366
|
+
const body = {
|
|
16367
|
+
agentId: agentSlug,
|
|
16368
|
+
signature: signatureB64,
|
|
16369
|
+
publicKey: publicIdentity.public_key,
|
|
16370
|
+
type: "shr",
|
|
16371
|
+
data: publishData
|
|
16372
|
+
};
|
|
16373
|
+
try {
|
|
16374
|
+
const response = await fetch(`${verascoreUrl.replace(/\/$/, "")}/api/publish`, {
|
|
16375
|
+
method: "POST",
|
|
16376
|
+
headers: { "Content-Type": "application/json" },
|
|
16377
|
+
body: JSON.stringify(body)
|
|
16378
|
+
});
|
|
16379
|
+
const result = await response.json().catch(() => ({}));
|
|
16380
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16381
|
+
did: publicIdentity.did,
|
|
16382
|
+
verascore_url: verascoreUrl,
|
|
16383
|
+
status: response.status,
|
|
16384
|
+
published: response.ok
|
|
16385
|
+
});
|
|
16386
|
+
return toolResult({
|
|
16387
|
+
did: publicIdentity.did,
|
|
16388
|
+
identity_id: publicIdentity.identity_id,
|
|
16389
|
+
profileUrl,
|
|
16390
|
+
tier: "self-attested",
|
|
16391
|
+
published: response.ok,
|
|
16392
|
+
verascore_status: response.status,
|
|
16393
|
+
verascore_response: result
|
|
16394
|
+
});
|
|
16395
|
+
} catch (err) {
|
|
16396
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16397
|
+
did: publicIdentity.did,
|
|
16398
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16399
|
+
});
|
|
16400
|
+
return toolResult({
|
|
16401
|
+
did: publicIdentity.did,
|
|
16402
|
+
identity_id: publicIdentity.identity_id,
|
|
16403
|
+
profileUrl,
|
|
16404
|
+
tier: "self-attested",
|
|
16405
|
+
published: false,
|
|
16406
|
+
warning: `Identity created but Verascore publish failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16407
|
+
});
|
|
16408
|
+
}
|
|
16409
|
+
}
|
|
16410
|
+
},
|
|
16411
|
+
// ─── sanctuary_policy_status ───────────────────────────────────────
|
|
16412
|
+
{
|
|
16413
|
+
name: "sanctuary/sanctuary_policy_status",
|
|
16414
|
+
description: "Return a summary of the active Principal Policy: which operations require approval (Tier 1), which are subject to anomaly detection (Tier 2), and which auto-allow with audit (Tier 3).",
|
|
16415
|
+
inputSchema: {
|
|
16416
|
+
type: "object",
|
|
16417
|
+
properties: {}
|
|
16418
|
+
},
|
|
16419
|
+
handler: async () => {
|
|
16420
|
+
const tier1 = [...policy.tier1_always_approve].sort();
|
|
16421
|
+
const tier3 = [...policy.tier3_always_allow].sort();
|
|
16422
|
+
const tier2Config = policy.tier2_anomaly;
|
|
16423
|
+
auditLog.append("l2", "sanctuary_policy_status", "system", {
|
|
16424
|
+
tier1_count: tier1.length,
|
|
16425
|
+
tier3_count: tier3.length
|
|
16426
|
+
});
|
|
16427
|
+
return toolResult({
|
|
16428
|
+
tier1,
|
|
16429
|
+
tier2: [],
|
|
16430
|
+
tier3,
|
|
16431
|
+
tier2_anomaly_config: tier2Config,
|
|
16432
|
+
counts: {
|
|
16433
|
+
tier1: tier1.length,
|
|
16434
|
+
tier2: 0,
|
|
16435
|
+
tier3: tier3.length
|
|
16436
|
+
},
|
|
16437
|
+
note: "Tier 2 is not a named list in Sanctuary \u2014 it is behavioral anomaly detection applied to all operations. See tier2_anomaly_config."
|
|
16438
|
+
});
|
|
16439
|
+
}
|
|
16440
|
+
},
|
|
16441
|
+
// ─── sanctuary_export_identity_bundle ──────────────────────────────
|
|
16442
|
+
{
|
|
16443
|
+
name: "sanctuary/sanctuary_export_identity_bundle",
|
|
16444
|
+
description: "Export a signed, portable identity bundle: { publicKey, did, shr, attestations }. The bundle is signed with the identity's Ed25519 key so a recipient can verify authenticity against the public key. Private keys are never included.",
|
|
16445
|
+
inputSchema: {
|
|
16446
|
+
type: "object",
|
|
16447
|
+
properties: {
|
|
16448
|
+
identity_id: {
|
|
16449
|
+
type: "string",
|
|
16450
|
+
description: "Identity to export (defaults to primary identity)."
|
|
16451
|
+
},
|
|
16452
|
+
attestations: {
|
|
16453
|
+
type: "array",
|
|
16454
|
+
items: { type: "object" },
|
|
16455
|
+
description: "Optional list of attestation objects to include in the bundle."
|
|
16456
|
+
}
|
|
16457
|
+
}
|
|
16458
|
+
},
|
|
16459
|
+
handler: async (args) => {
|
|
16460
|
+
const identityId = args.identity_id;
|
|
16461
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
16462
|
+
if (!identity) {
|
|
16463
|
+
return toolResult({
|
|
16464
|
+
error: "No identity found. Create one with identity_create first."
|
|
16465
|
+
});
|
|
16466
|
+
}
|
|
16467
|
+
const shr = generateSHR(identity.identity_id, {
|
|
16468
|
+
config,
|
|
16469
|
+
identityManager,
|
|
16470
|
+
masterKey
|
|
16471
|
+
});
|
|
16472
|
+
const attestations = args.attestations ?? [];
|
|
16473
|
+
const body = {
|
|
16474
|
+
format: "SANCTUARY_IDENTITY_BUNDLE_V1",
|
|
16475
|
+
publicKey: identity.public_key,
|
|
16476
|
+
did: identity.did,
|
|
16477
|
+
identity_id: identity.identity_id,
|
|
16478
|
+
label: identity.label,
|
|
16479
|
+
key_type: identity.key_type,
|
|
16480
|
+
shr: typeof shr === "string" ? null : shr,
|
|
16481
|
+
attestations,
|
|
16482
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
16483
|
+
};
|
|
16484
|
+
const bodyBytes = new TextEncoder().encode(JSON.stringify(body));
|
|
16485
|
+
let signatureB64;
|
|
16486
|
+
try {
|
|
16487
|
+
const sigBytes = sign(
|
|
16488
|
+
bodyBytes,
|
|
16489
|
+
identity.encrypted_private_key,
|
|
16490
|
+
identityEncKey
|
|
16491
|
+
);
|
|
16492
|
+
signatureB64 = toBase64url(sigBytes);
|
|
16493
|
+
} catch (err) {
|
|
16494
|
+
return toolResult({
|
|
16495
|
+
error: "Failed to sign identity bundle.",
|
|
16496
|
+
details: err instanceof Error ? err.message : String(err)
|
|
16497
|
+
});
|
|
16498
|
+
}
|
|
16499
|
+
auditLog.append("l1", "sanctuary_export_identity_bundle", identity.identity_id, {
|
|
16500
|
+
did: identity.did,
|
|
16501
|
+
attestation_count: attestations.length
|
|
16502
|
+
});
|
|
16503
|
+
return toolResult({
|
|
16504
|
+
bundle: body,
|
|
16505
|
+
signature: signatureB64,
|
|
16506
|
+
signed_by: identity.did
|
|
16507
|
+
});
|
|
16508
|
+
}
|
|
16509
|
+
},
|
|
16510
|
+
// ─── sanctuary_link_to_human ───────────────────────────────────────
|
|
16511
|
+
{
|
|
16512
|
+
name: "sanctuary/sanctuary_link_to_human",
|
|
16513
|
+
description: "Trigger a Verascore magic-link login flow so a human principal can authenticate and subsequently claim this agent's DID. The email is sent by Verascore to the supplied address. This tool only initiates the flow \u2014 it does not directly bind the DID.",
|
|
16514
|
+
inputSchema: {
|
|
16515
|
+
type: "object",
|
|
16516
|
+
properties: {
|
|
16517
|
+
email: {
|
|
16518
|
+
type: "string",
|
|
16519
|
+
description: "Email address of the human to link this agent to."
|
|
16520
|
+
},
|
|
16521
|
+
verascore_url: {
|
|
16522
|
+
type: "string",
|
|
16523
|
+
description: "Verascore base URL. Defaults to server config."
|
|
16524
|
+
}
|
|
16525
|
+
},
|
|
16526
|
+
required: ["email"]
|
|
16527
|
+
},
|
|
16528
|
+
handler: async (args) => {
|
|
16529
|
+
const email = args.email;
|
|
16530
|
+
const verascoreUrl = args.verascore_url || config.verascore.url || "https://verascore.ai";
|
|
16531
|
+
const urlCheck = validateVerascoreUrl(verascoreUrl, config.verascore.url);
|
|
16532
|
+
if (!urlCheck.ok) {
|
|
16533
|
+
return toolResult({ ok: false, error: urlCheck.error });
|
|
16534
|
+
}
|
|
16535
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
|
|
16536
|
+
return toolResult({ ok: false, error: "Invalid email format." });
|
|
16537
|
+
}
|
|
16538
|
+
try {
|
|
16539
|
+
const response = await fetch(`${verascoreUrl.replace(/\/$/, "")}/api/auth/request`, {
|
|
16540
|
+
method: "POST",
|
|
16541
|
+
headers: { "Content-Type": "application/json" },
|
|
16542
|
+
body: JSON.stringify({ email })
|
|
16543
|
+
});
|
|
16544
|
+
await response.json().catch(() => ({}));
|
|
16545
|
+
auditLog.append("l4", "sanctuary_link_to_human", "system", {
|
|
16546
|
+
verascore_url: verascoreUrl,
|
|
16547
|
+
status: response.status,
|
|
16548
|
+
// Do not log the email to the audit trail — keep it local.
|
|
16549
|
+
email_domain: email.split("@")[1] ?? null
|
|
16550
|
+
});
|
|
16551
|
+
return toolResult({
|
|
16552
|
+
ok: response.ok,
|
|
16553
|
+
message: "Check your email for a login link. After logging in, visit verascore.ai to claim this agent's DID.",
|
|
16554
|
+
email_redacted: `***@${email.split("@")[1] ?? "***"}`,
|
|
16555
|
+
verascore_status: response.status
|
|
16556
|
+
});
|
|
16557
|
+
} catch (err) {
|
|
16558
|
+
return toolResult({
|
|
16559
|
+
ok: false,
|
|
16560
|
+
error: `Failed to reach Verascore at ${verascoreUrl}: ${err instanceof Error ? err.message : String(err)}`
|
|
16561
|
+
});
|
|
16562
|
+
}
|
|
16563
|
+
}
|
|
16564
|
+
},
|
|
16565
|
+
// ─── sanctuary_sign_challenge ──────────────────────────────────────
|
|
16566
|
+
{
|
|
16567
|
+
name: "sanctuary/sanctuary_sign_challenge",
|
|
16568
|
+
description: "Sign a domain-separated nonce with the agent's Ed25519 key. Used in DID-ownership proof flows. The signed message is constructed as: 'sanctuary-sign-challenge-v1\\x00' + purpose + '\\x00' + nonce. The verifier MUST reconstruct the same domain-prefixed message before calling Ed25519 verify \u2014 a raw-nonce signature is NOT valid for this tool. The `purpose` field binds the signature to a specific use case (e.g. 'verascore-claim') so a signature produced for one purpose cannot be replayed against a different verifier.",
|
|
16569
|
+
inputSchema: {
|
|
16570
|
+
type: "object",
|
|
16571
|
+
properties: {
|
|
16572
|
+
nonce: {
|
|
16573
|
+
type: "string",
|
|
16574
|
+
description: "The nonce / challenge string to sign."
|
|
16575
|
+
},
|
|
16576
|
+
purpose: {
|
|
16577
|
+
type: "string",
|
|
16578
|
+
description: "Domain-separation tag identifying what the signature will be used for (e.g. 'verascore-claim'). Required. Max 128 chars, printable ASCII only."
|
|
16579
|
+
},
|
|
16580
|
+
identity_id: {
|
|
16581
|
+
type: "string",
|
|
16582
|
+
description: "Identity to sign with (defaults to primary)."
|
|
16583
|
+
}
|
|
16584
|
+
},
|
|
16585
|
+
required: ["nonce", "purpose"]
|
|
16586
|
+
},
|
|
16587
|
+
handler: async (args) => {
|
|
16588
|
+
const nonce = args.nonce;
|
|
16589
|
+
const purpose = args.purpose;
|
|
16590
|
+
if (!nonce || nonce.length === 0) {
|
|
16591
|
+
return toolResult({ error: "nonce must be a non-empty string." });
|
|
16592
|
+
}
|
|
16593
|
+
if (nonce.length > 4096) {
|
|
16594
|
+
return toolResult({ error: "nonce exceeds maximum length (4096)." });
|
|
16595
|
+
}
|
|
16596
|
+
if (typeof purpose !== "string" || purpose.length === 0) {
|
|
16597
|
+
return toolResult({
|
|
16598
|
+
error: "purpose is required (domain-separation tag, e.g. 'verascore-claim')."
|
|
16599
|
+
});
|
|
16600
|
+
}
|
|
16601
|
+
if (purpose.length > 128) {
|
|
16602
|
+
return toolResult({ error: "purpose exceeds maximum length (128)." });
|
|
16603
|
+
}
|
|
16604
|
+
if (!/^[\x20-\x7E]+$/.test(purpose)) {
|
|
16605
|
+
return toolResult({
|
|
16606
|
+
error: "purpose must be printable ASCII only (no NUL, no non-ASCII)."
|
|
16607
|
+
});
|
|
16608
|
+
}
|
|
16609
|
+
const identityId = args.identity_id;
|
|
16610
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
16611
|
+
if (!identity) {
|
|
16612
|
+
return toolResult({
|
|
16613
|
+
error: "No identity found. Create one with identity_create first."
|
|
16614
|
+
});
|
|
16615
|
+
}
|
|
16616
|
+
const domainTag = "sanctuary-sign-challenge-v1";
|
|
16617
|
+
const enc = new TextEncoder();
|
|
16618
|
+
const tagBytes = enc.encode(domainTag);
|
|
16619
|
+
const purposeBytes = enc.encode(purpose);
|
|
16620
|
+
const nonceBytes = enc.encode(nonce);
|
|
16621
|
+
const sep = new Uint8Array([0]);
|
|
16622
|
+
const message = new Uint8Array(
|
|
16623
|
+
tagBytes.length + 1 + purposeBytes.length + 1 + nonceBytes.length
|
|
16624
|
+
);
|
|
16625
|
+
let offset = 0;
|
|
16626
|
+
message.set(tagBytes, offset);
|
|
16627
|
+
offset += tagBytes.length;
|
|
16628
|
+
message.set(sep, offset);
|
|
16629
|
+
offset += 1;
|
|
16630
|
+
message.set(purposeBytes, offset);
|
|
16631
|
+
offset += purposeBytes.length;
|
|
16632
|
+
message.set(sep, offset);
|
|
16633
|
+
offset += 1;
|
|
16634
|
+
message.set(nonceBytes, offset);
|
|
16635
|
+
let sigB64;
|
|
16636
|
+
try {
|
|
16637
|
+
const sig = sign(
|
|
16638
|
+
message,
|
|
16639
|
+
identity.encrypted_private_key,
|
|
16640
|
+
identityEncKey
|
|
16641
|
+
);
|
|
16642
|
+
sigB64 = toBase64url(sig);
|
|
16643
|
+
} catch (err) {
|
|
16644
|
+
return toolResult({
|
|
16645
|
+
error: "Failed to sign nonce.",
|
|
16646
|
+
details: err instanceof Error ? err.message : String(err)
|
|
16647
|
+
});
|
|
16648
|
+
}
|
|
16649
|
+
auditLog.append("l1", "sanctuary_sign_challenge", identity.identity_id, {
|
|
16650
|
+
did: identity.did,
|
|
16651
|
+
nonce_len: nonce.length,
|
|
16652
|
+
purpose
|
|
16653
|
+
});
|
|
16654
|
+
return toolResult({
|
|
16655
|
+
signature: sigB64,
|
|
16656
|
+
did: identity.did,
|
|
16657
|
+
public_key: identity.public_key,
|
|
16658
|
+
signed_by: identity.did,
|
|
16659
|
+
domain_tag: domainTag,
|
|
16660
|
+
purpose
|
|
16661
|
+
});
|
|
16662
|
+
}
|
|
16663
|
+
}
|
|
16664
|
+
];
|
|
16665
|
+
return { tools };
|
|
16666
|
+
}
|
|
16667
|
+
|
|
16095
16668
|
// src/index.ts
|
|
16096
16669
|
init_key_derivation();
|
|
16097
16670
|
init_random();
|
|
@@ -16437,14 +17010,19 @@ async function createSanctuaryServer(options) {
|
|
|
16437
17010
|
config,
|
|
16438
17011
|
identityManager,
|
|
16439
17012
|
masterKey,
|
|
16440
|
-
auditLog
|
|
17013
|
+
auditLog,
|
|
17014
|
+
{
|
|
17015
|
+
autoPublishHandshakes: config.verascore.auto_publish_handshakes,
|
|
17016
|
+
verascoreUrl: config.verascore.url
|
|
17017
|
+
}
|
|
16441
17018
|
);
|
|
16442
17019
|
const { tools: l4Tools} = createL4Tools(
|
|
16443
17020
|
storage,
|
|
16444
17021
|
masterKey,
|
|
16445
17022
|
identityManager,
|
|
16446
17023
|
auditLog,
|
|
16447
|
-
handshakeResults
|
|
17024
|
+
handshakeResults,
|
|
17025
|
+
config.verascore.url
|
|
16448
17026
|
);
|
|
16449
17027
|
const { tools: federationTools } = createFederationTools(
|
|
16450
17028
|
auditLog,
|
|
@@ -16529,6 +17107,14 @@ async function createSanctuaryServer(options) {
|
|
|
16529
17107
|
} : void 0;
|
|
16530
17108
|
const gate = new ApprovalGate(policy, baseline, approvalChannel, auditLog, injectionDetector, onInjectionAlert);
|
|
16531
17109
|
const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
|
|
17110
|
+
const { tools: sanctuaryMetaTools } = createSanctuaryTools({
|
|
17111
|
+
config,
|
|
17112
|
+
identityManager,
|
|
17113
|
+
masterKey,
|
|
17114
|
+
auditLog,
|
|
17115
|
+
policy,
|
|
17116
|
+
keyProtection
|
|
17117
|
+
});
|
|
16532
17118
|
const dashboardTools = [];
|
|
16533
17119
|
if (dashboard) {
|
|
16534
17120
|
dashboardTools.push({
|
|
@@ -16570,6 +17156,7 @@ async function createSanctuaryServer(options) {
|
|
|
16570
17156
|
...hardeningTools,
|
|
16571
17157
|
...profileTools,
|
|
16572
17158
|
...dashboardTools,
|
|
17159
|
+
...sanctuaryMetaTools,
|
|
16573
17160
|
manifestTool
|
|
16574
17161
|
];
|
|
16575
17162
|
let clientManager;
|