@sanctuary-framework/mcp-server 0.5.16 → 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 +583 -13
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +583 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +581 -13
- 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 +581 -13
- 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
|
};
|
|
@@ -8200,7 +8232,7 @@ function tierDistribution(tiers) {
|
|
|
8200
8232
|
}
|
|
8201
8233
|
|
|
8202
8234
|
// src/l4-reputation/tools.ts
|
|
8203
|
-
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults) {
|
|
8235
|
+
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults, verascoreUrl) {
|
|
8204
8236
|
const reputationStore = new ReputationStore(storage, masterKey);
|
|
8205
8237
|
const identityEncryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
8206
8238
|
const hsResults = handshakeResults ?? /* @__PURE__ */ new Map();
|
|
@@ -8690,8 +8722,18 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
8690
8722
|
});
|
|
8691
8723
|
}
|
|
8692
8724
|
const publishType = args.type;
|
|
8693
|
-
const
|
|
8694
|
-
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
|
+
}
|
|
8695
8737
|
try {
|
|
8696
8738
|
const parsed = new URL(veracoreUrl);
|
|
8697
8739
|
if (parsed.protocol !== "https:") {
|
|
@@ -8699,9 +8741,9 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
8699
8741
|
error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
|
|
8700
8742
|
});
|
|
8701
8743
|
}
|
|
8702
|
-
if (!ALLOWED_VERASCORE_HOSTS.
|
|
8744
|
+
if (!ALLOWED_VERASCORE_HOSTS.has(parsed.hostname)) {
|
|
8703
8745
|
return toolResult({
|
|
8704
|
-
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}`
|
|
8705
8747
|
});
|
|
8706
8748
|
}
|
|
8707
8749
|
} catch {
|
|
@@ -11030,6 +11072,9 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
11030
11072
|
// src/handshake/tools.ts
|
|
11031
11073
|
init_router();
|
|
11032
11074
|
init_generator();
|
|
11075
|
+
init_identity();
|
|
11076
|
+
init_key_derivation();
|
|
11077
|
+
init_encoding();
|
|
11033
11078
|
|
|
11034
11079
|
// src/handshake/protocol.ts
|
|
11035
11080
|
init_identity();
|
|
@@ -11354,7 +11399,10 @@ function verifyAttestation(attestation, now) {
|
|
|
11354
11399
|
}
|
|
11355
11400
|
|
|
11356
11401
|
// src/handshake/tools.ts
|
|
11357
|
-
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");
|
|
11358
11406
|
const sessions = /* @__PURE__ */ new Map();
|
|
11359
11407
|
const handshakeResults = /* @__PURE__ */ new Map();
|
|
11360
11408
|
const shrOpts = {
|
|
@@ -11426,10 +11474,86 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
|
11426
11474
|
}
|
|
11427
11475
|
sessions.set(result.session.session_id, result.session);
|
|
11428
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
|
+
}
|
|
11429
11552
|
return toolResult({
|
|
11430
11553
|
session_id: result.session.session_id,
|
|
11431
11554
|
response: result.response,
|
|
11432
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,
|
|
11433
11557
|
// SEC-ADD-03: Tag response — contains SHR data that will be sent to counterparty
|
|
11434
11558
|
_content_trust: "external"
|
|
11435
11559
|
});
|
|
@@ -16109,6 +16233,438 @@ function createGovernorTools(governor, auditLog) {
|
|
|
16109
16233
|
return { tools };
|
|
16110
16234
|
}
|
|
16111
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
|
+
|
|
16112
16668
|
// src/index.ts
|
|
16113
16669
|
init_key_derivation();
|
|
16114
16670
|
init_random();
|
|
@@ -16454,14 +17010,19 @@ async function createSanctuaryServer(options) {
|
|
|
16454
17010
|
config,
|
|
16455
17011
|
identityManager,
|
|
16456
17012
|
masterKey,
|
|
16457
|
-
auditLog
|
|
17013
|
+
auditLog,
|
|
17014
|
+
{
|
|
17015
|
+
autoPublishHandshakes: config.verascore.auto_publish_handshakes,
|
|
17016
|
+
verascoreUrl: config.verascore.url
|
|
17017
|
+
}
|
|
16458
17018
|
);
|
|
16459
17019
|
const { tools: l4Tools} = createL4Tools(
|
|
16460
17020
|
storage,
|
|
16461
17021
|
masterKey,
|
|
16462
17022
|
identityManager,
|
|
16463
17023
|
auditLog,
|
|
16464
|
-
handshakeResults
|
|
17024
|
+
handshakeResults,
|
|
17025
|
+
config.verascore.url
|
|
16465
17026
|
);
|
|
16466
17027
|
const { tools: federationTools } = createFederationTools(
|
|
16467
17028
|
auditLog,
|
|
@@ -16546,6 +17107,14 @@ async function createSanctuaryServer(options) {
|
|
|
16546
17107
|
} : void 0;
|
|
16547
17108
|
const gate = new ApprovalGate(policy, baseline, approvalChannel, auditLog, injectionDetector, onInjectionAlert);
|
|
16548
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
|
+
});
|
|
16549
17118
|
const dashboardTools = [];
|
|
16550
17119
|
if (dashboard) {
|
|
16551
17120
|
dashboardTools.push({
|
|
@@ -16587,6 +17156,7 @@ async function createSanctuaryServer(options) {
|
|
|
16587
17156
|
...hardeningTools,
|
|
16588
17157
|
...profileTools,
|
|
16589
17158
|
...dashboardTools,
|
|
17159
|
+
...sanctuaryMetaTools,
|
|
16590
17160
|
manifestTool
|
|
16591
17161
|
];
|
|
16592
17162
|
let clientManager;
|