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