@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/index.cjs
CHANGED
|
@@ -429,6 +429,12 @@ function defaultConfig() {
|
|
|
429
429
|
secret: "",
|
|
430
430
|
callback_port: 3502,
|
|
431
431
|
callback_host: "127.0.0.1"
|
|
432
|
+
},
|
|
433
|
+
verascore: {
|
|
434
|
+
url: "https://verascore.ai",
|
|
435
|
+
auto_publish_to_verascore: true,
|
|
436
|
+
// DELTA-04: default OFF for privacy. Enable explicitly per deployment.
|
|
437
|
+
auto_publish_handshakes: false
|
|
432
438
|
}
|
|
433
439
|
};
|
|
434
440
|
}
|
|
@@ -499,6 +505,21 @@ async function loadConfig(configPath) {
|
|
|
499
505
|
if (process.env.SANCTUARY_WEBHOOK_CALLBACK_HOST) {
|
|
500
506
|
config.webhook.callback_host = process.env.SANCTUARY_WEBHOOK_CALLBACK_HOST;
|
|
501
507
|
}
|
|
508
|
+
if (process.env.SANCTUARY_VERASCORE_URL) {
|
|
509
|
+
config.verascore.url = process.env.SANCTUARY_VERASCORE_URL;
|
|
510
|
+
}
|
|
511
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_TO_VERASCORE === "true") {
|
|
512
|
+
config.verascore.auto_publish_to_verascore = true;
|
|
513
|
+
}
|
|
514
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_TO_VERASCORE === "false") {
|
|
515
|
+
config.verascore.auto_publish_to_verascore = false;
|
|
516
|
+
}
|
|
517
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_HANDSHAKES === "true") {
|
|
518
|
+
config.verascore.auto_publish_handshakes = true;
|
|
519
|
+
}
|
|
520
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_HANDSHAKES === "false") {
|
|
521
|
+
config.verascore.auto_publish_handshakes = false;
|
|
522
|
+
}
|
|
502
523
|
config.version = PKG_VERSION;
|
|
503
524
|
validateConfig(config);
|
|
504
525
|
return config;
|
|
@@ -3188,7 +3209,7 @@ function tierDistribution(tiers) {
|
|
|
3188
3209
|
}
|
|
3189
3210
|
|
|
3190
3211
|
// src/l4-reputation/tools.ts
|
|
3191
|
-
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults) {
|
|
3212
|
+
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults, verascoreUrl) {
|
|
3192
3213
|
const reputationStore = new ReputationStore(storage, masterKey);
|
|
3193
3214
|
const identityEncryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
3194
3215
|
const hsResults = handshakeResults ?? /* @__PURE__ */ new Map();
|
|
@@ -3678,8 +3699,18 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
3678
3699
|
});
|
|
3679
3700
|
}
|
|
3680
3701
|
const publishType = args.type;
|
|
3681
|
-
const
|
|
3682
|
-
const
|
|
3702
|
+
const configuredVerascoreUrl = verascoreUrl || "https://verascore.ai";
|
|
3703
|
+
const veracoreUrl = args.verascore_url || configuredVerascoreUrl;
|
|
3704
|
+
const ALLOWED_VERASCORE_HOSTS = /* @__PURE__ */ new Set([
|
|
3705
|
+
"verascore.ai",
|
|
3706
|
+
"www.verascore.ai",
|
|
3707
|
+
"api.verascore.ai"
|
|
3708
|
+
]);
|
|
3709
|
+
try {
|
|
3710
|
+
const configuredHost = new URL(configuredVerascoreUrl).hostname;
|
|
3711
|
+
ALLOWED_VERASCORE_HOSTS.add(configuredHost);
|
|
3712
|
+
} catch {
|
|
3713
|
+
}
|
|
3683
3714
|
try {
|
|
3684
3715
|
const parsed = new URL(veracoreUrl);
|
|
3685
3716
|
if (parsed.protocol !== "https:") {
|
|
@@ -3687,9 +3718,9 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
3687
3718
|
error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
|
|
3688
3719
|
});
|
|
3689
3720
|
}
|
|
3690
|
-
if (!ALLOWED_VERASCORE_HOSTS.
|
|
3721
|
+
if (!ALLOWED_VERASCORE_HOSTS.has(parsed.hostname)) {
|
|
3691
3722
|
return toolResult({
|
|
3692
|
-
error: `verascore_url must point to a known Verascore domain (${ALLOWED_VERASCORE_HOSTS.join(", ")}). Got: ${parsed.hostname}`
|
|
3723
|
+
error: `verascore_url must point to a known Verascore domain (${[...ALLOWED_VERASCORE_HOSTS].join(", ")}). Got: ${parsed.hostname}`
|
|
3693
3724
|
});
|
|
3694
3725
|
}
|
|
3695
3726
|
} catch {
|
|
@@ -3820,12 +3851,14 @@ var DEFAULT_POLICY = {
|
|
|
3820
3851
|
"reputation_export",
|
|
3821
3852
|
"bootstrap_provide_guarantee",
|
|
3822
3853
|
"decommission_certificate",
|
|
3823
|
-
"reputation_publish",
|
|
3824
|
-
// SEC-039: Explicit Tier 1 — sends data to external API
|
|
3825
3854
|
"sovereignty_profile_update",
|
|
3826
3855
|
// Changes enforcement behavior — always requires approval
|
|
3827
|
-
"governor_reset"
|
|
3856
|
+
"governor_reset",
|
|
3828
3857
|
// Clears all runtime governance state — always requires approval
|
|
3858
|
+
"sanctuary_bootstrap",
|
|
3859
|
+
// Creates new Ed25519 identity + publishes — always requires approval
|
|
3860
|
+
"sanctuary_export_identity_bundle"
|
|
3861
|
+
// Exports portable identity — always requires approval
|
|
3829
3862
|
],
|
|
3830
3863
|
tier2_anomaly: DEFAULT_TIER2,
|
|
3831
3864
|
tier3_always_allow: [
|
|
@@ -3883,7 +3916,11 @@ var DEFAULT_POLICY = {
|
|
|
3883
3916
|
"sovereignty_profile_get",
|
|
3884
3917
|
"sovereignty_profile_generate_prompt",
|
|
3885
3918
|
// Agent needs its own config to generate system prompt
|
|
3886
|
-
"governor_status"
|
|
3919
|
+
"governor_status",
|
|
3920
|
+
"reputation_publish",
|
|
3921
|
+
// Auto-allow: publishing sovereignty data to Verascore is routine
|
|
3922
|
+
"sanctuary_policy_status"
|
|
3923
|
+
// Read-only policy summary
|
|
3887
3924
|
],
|
|
3888
3925
|
approval_channel: DEFAULT_CHANNEL
|
|
3889
3926
|
};
|
|
@@ -3994,9 +4031,10 @@ tier1_always_approve:
|
|
|
3994
4031
|
- reputation_import
|
|
3995
4032
|
- reputation_export
|
|
3996
4033
|
- bootstrap_provide_guarantee
|
|
3997
|
-
- reputation_publish
|
|
3998
4034
|
- sovereignty_profile_update
|
|
3999
4035
|
- governor_reset
|
|
4036
|
+
- sanctuary_bootstrap
|
|
4037
|
+
- sanctuary_export_identity_bundle
|
|
4000
4038
|
|
|
4001
4039
|
# \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
|
|
4002
4040
|
# Triggers approval when agent behavior deviates from its baseline.
|
|
@@ -4062,6 +4100,10 @@ tier3_always_allow:
|
|
|
4062
4100
|
- dashboard_open
|
|
4063
4101
|
- sovereignty_profile_get
|
|
4064
4102
|
- governor_status
|
|
4103
|
+
- reputation_publish
|
|
4104
|
+
- sanctuary_policy_status
|
|
4105
|
+
- sanctuary_link_to_human
|
|
4106
|
+
- sanctuary_sign_challenge
|
|
4065
4107
|
|
|
4066
4108
|
# \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
|
|
4067
4109
|
# How Sanctuary reaches you when approval is needed.
|
|
@@ -4634,7 +4676,7 @@ function generateLoginHTML(options) {
|
|
|
4634
4676
|
if (response.ok) {
|
|
4635
4677
|
const data = await response.json();
|
|
4636
4678
|
sessionStorage.setItem('authToken', token);
|
|
4637
|
-
window.location.href = '/
|
|
4679
|
+
window.location.href = '/'; // Dashboard is served at root path
|
|
4638
4680
|
} else if (response.status === 401) {
|
|
4639
4681
|
showError('Invalid token. Please check and try again.');
|
|
4640
4682
|
} else {
|
|
@@ -7571,7 +7613,24 @@ var DashboardApprovalChannel = class {
|
|
|
7571
7613
|
}
|
|
7572
7614
|
resolve();
|
|
7573
7615
|
});
|
|
7574
|
-
this.httpServer.on("error",
|
|
7616
|
+
this.httpServer.on("error", (err) => {
|
|
7617
|
+
if (err.code === "EADDRINUSE") {
|
|
7618
|
+
const port = this.config.port;
|
|
7619
|
+
process.stderr.write(
|
|
7620
|
+
`
|
|
7621
|
+
\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
|
|
7622
|
+
\u2551 Port ${port} is already in use. \u2551
|
|
7623
|
+
\u2551 \u2551
|
|
7624
|
+
\u2551 Another Sanctuary Dashboard may still be running. \u2551
|
|
7625
|
+
\u2551 To fix: lsof -ti:${port} | xargs kill \u2551
|
|
7626
|
+
\u2551 Then restart the dashboard. \u2551
|
|
7627
|
+
\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
|
|
7628
|
+
|
|
7629
|
+
`
|
|
7630
|
+
);
|
|
7631
|
+
}
|
|
7632
|
+
reject(err);
|
|
7633
|
+
});
|
|
7575
7634
|
});
|
|
7576
7635
|
}
|
|
7577
7636
|
/**
|
|
@@ -7851,7 +7910,7 @@ var DashboardApprovalChannel = class {
|
|
|
7851
7910
|
if (!this.checkAuth(req, url, res)) return;
|
|
7852
7911
|
if (!this.checkRateLimit(req, res, "general")) return;
|
|
7853
7912
|
try {
|
|
7854
|
-
if (method === "GET" && url.pathname === "/") {
|
|
7913
|
+
if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
|
|
7855
7914
|
this.serveDashboard(res);
|
|
7856
7915
|
} else if (method === "GET" && url.pathname === "/events") {
|
|
7857
7916
|
this.handleSSE(req, res);
|
|
@@ -10563,6 +10622,10 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
10563
10622
|
return { tools };
|
|
10564
10623
|
}
|
|
10565
10624
|
|
|
10625
|
+
// src/handshake/tools.ts
|
|
10626
|
+
init_identity();
|
|
10627
|
+
init_encoding();
|
|
10628
|
+
|
|
10566
10629
|
// src/handshake/protocol.ts
|
|
10567
10630
|
init_identity();
|
|
10568
10631
|
init_encoding();
|
|
@@ -10883,7 +10946,10 @@ function verifyAttestation(attestation, now) {
|
|
|
10883
10946
|
}
|
|
10884
10947
|
|
|
10885
10948
|
// src/handshake/tools.ts
|
|
10886
|
-
function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
10949
|
+
function createHandshakeTools(config, identityManager, masterKey, auditLog, options) {
|
|
10950
|
+
const autoPublishHandshakes = options?.autoPublishHandshakes ?? false;
|
|
10951
|
+
const verascoreUrl = options?.verascoreUrl ?? "https://verascore.ai";
|
|
10952
|
+
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
10887
10953
|
const sessions = /* @__PURE__ */ new Map();
|
|
10888
10954
|
const handshakeResults = /* @__PURE__ */ new Map();
|
|
10889
10955
|
const shrOpts = {
|
|
@@ -10955,10 +11021,86 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
|
10955
11021
|
}
|
|
10956
11022
|
sessions.set(result.session.session_id, result.session);
|
|
10957
11023
|
auditLog.append("l4", "handshake_respond", shr.body.instance_id);
|
|
11024
|
+
let autoPublishResult;
|
|
11025
|
+
if (autoPublishHandshakes) {
|
|
11026
|
+
autoPublishResult = { attempted: true };
|
|
11027
|
+
try {
|
|
11028
|
+
const parsed = new URL(verascoreUrl);
|
|
11029
|
+
if (parsed.protocol !== "https:") {
|
|
11030
|
+
autoPublishResult.error = `verascore URL must use HTTPS (got ${parsed.protocol})`;
|
|
11031
|
+
} else {
|
|
11032
|
+
const attestationPayload = {
|
|
11033
|
+
type: "handshake",
|
|
11034
|
+
our_shr_signed_by: shr.signed_by,
|
|
11035
|
+
counterparty_signed_by: "redacted",
|
|
11036
|
+
session_id: result.session.session_id,
|
|
11037
|
+
responded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11038
|
+
};
|
|
11039
|
+
const responderIdentity = identityManager.get(shr.body.instance_id);
|
|
11040
|
+
if (!responderIdentity) {
|
|
11041
|
+
autoPublishResult.error = `responder identity ${shr.body.instance_id} not found; skipping auto-publish`;
|
|
11042
|
+
auditLog.append(
|
|
11043
|
+
"l4",
|
|
11044
|
+
"handshake_auto_publish",
|
|
11045
|
+
shr.body.instance_id,
|
|
11046
|
+
{ error: autoPublishResult.error },
|
|
11047
|
+
"failure"
|
|
11048
|
+
);
|
|
11049
|
+
} else {
|
|
11050
|
+
const payloadBytes = new TextEncoder().encode(
|
|
11051
|
+
JSON.stringify(attestationPayload)
|
|
11052
|
+
);
|
|
11053
|
+
const sigBytes = sign(
|
|
11054
|
+
payloadBytes,
|
|
11055
|
+
responderIdentity.encrypted_private_key,
|
|
11056
|
+
identityEncKey
|
|
11057
|
+
);
|
|
11058
|
+
const signatureB64 = toBase64url(sigBytes);
|
|
11059
|
+
const resp = await fetch(
|
|
11060
|
+
`${verascoreUrl.replace(/\/$/, "")}/api/publish`,
|
|
11061
|
+
{
|
|
11062
|
+
method: "POST",
|
|
11063
|
+
headers: { "Content-Type": "application/json" },
|
|
11064
|
+
body: JSON.stringify({
|
|
11065
|
+
agentId: shr.body.instance_id,
|
|
11066
|
+
publicKey: shr.signed_by,
|
|
11067
|
+
signature: signatureB64,
|
|
11068
|
+
type: "handshake",
|
|
11069
|
+
data: attestationPayload
|
|
11070
|
+
})
|
|
11071
|
+
}
|
|
11072
|
+
);
|
|
11073
|
+
autoPublishResult.ok = resp.ok;
|
|
11074
|
+
autoPublishResult.status = resp.status;
|
|
11075
|
+
auditLog.append(
|
|
11076
|
+
"l4",
|
|
11077
|
+
"handshake_auto_publish",
|
|
11078
|
+
shr.body.instance_id,
|
|
11079
|
+
{
|
|
11080
|
+
verascore_url: verascoreUrl,
|
|
11081
|
+
status: resp.status,
|
|
11082
|
+
ok: resp.ok
|
|
11083
|
+
},
|
|
11084
|
+
resp.ok ? "success" : "failure"
|
|
11085
|
+
);
|
|
11086
|
+
}
|
|
11087
|
+
}
|
|
11088
|
+
} catch (err) {
|
|
11089
|
+
autoPublishResult.error = err instanceof Error ? err.message : String(err);
|
|
11090
|
+
auditLog.append(
|
|
11091
|
+
"l4",
|
|
11092
|
+
"handshake_auto_publish",
|
|
11093
|
+
shr.body.instance_id,
|
|
11094
|
+
{ verascore_url: verascoreUrl, error: autoPublishResult.error },
|
|
11095
|
+
"failure"
|
|
11096
|
+
);
|
|
11097
|
+
}
|
|
11098
|
+
}
|
|
10958
11099
|
return toolResult({
|
|
10959
11100
|
session_id: result.session.session_id,
|
|
10960
11101
|
response: result.response,
|
|
10961
11102
|
instructions: "Send the 'response' object back to the initiator. When you receive their completion, pass it to sanctuary/handshake_status with this session_id.",
|
|
11103
|
+
auto_publish: autoPublishResult,
|
|
10962
11104
|
// SEC-ADD-03: Tag response — contains SHR data that will be sent to counterparty
|
|
10963
11105
|
_content_trust: "external"
|
|
10964
11106
|
});
|
|
@@ -15800,6 +15942,435 @@ function createGovernorTools(governor, auditLog) {
|
|
|
15800
15942
|
return { tools };
|
|
15801
15943
|
}
|
|
15802
15944
|
|
|
15945
|
+
// src/sanctuary-tools.ts
|
|
15946
|
+
init_identity();
|
|
15947
|
+
init_encoding();
|
|
15948
|
+
init_identity();
|
|
15949
|
+
function validateVerascoreUrl(urlStr, configuredUrl) {
|
|
15950
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
15951
|
+
"verascore.ai",
|
|
15952
|
+
"www.verascore.ai",
|
|
15953
|
+
"api.verascore.ai"
|
|
15954
|
+
]);
|
|
15955
|
+
try {
|
|
15956
|
+
allowed.add(new URL(configuredUrl).hostname);
|
|
15957
|
+
} catch {
|
|
15958
|
+
}
|
|
15959
|
+
try {
|
|
15960
|
+
const parsed = new URL(urlStr);
|
|
15961
|
+
if (parsed.protocol !== "https:") {
|
|
15962
|
+
return { ok: false, error: `Verascore URL must use HTTPS. Got: ${parsed.protocol}` };
|
|
15963
|
+
}
|
|
15964
|
+
if (!allowed.has(parsed.hostname)) {
|
|
15965
|
+
return {
|
|
15966
|
+
ok: false,
|
|
15967
|
+
error: `Verascore URL must point to a known Verascore host (${[...allowed].join(", ")}). Got: ${parsed.hostname}`
|
|
15968
|
+
};
|
|
15969
|
+
}
|
|
15970
|
+
return { ok: true };
|
|
15971
|
+
} catch {
|
|
15972
|
+
return { ok: false, error: `Invalid Verascore URL: ${urlStr}` };
|
|
15973
|
+
}
|
|
15974
|
+
}
|
|
15975
|
+
function createSanctuaryTools(opts) {
|
|
15976
|
+
const { config, identityManager, masterKey, auditLog, policy, keyProtection } = opts;
|
|
15977
|
+
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
15978
|
+
const tools = [
|
|
15979
|
+
// ─── sanctuary_bootstrap ───────────────────────────────────────────
|
|
15980
|
+
{
|
|
15981
|
+
name: "sanctuary/sanctuary_bootstrap",
|
|
15982
|
+
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.",
|
|
15983
|
+
inputSchema: {
|
|
15984
|
+
type: "object",
|
|
15985
|
+
properties: {
|
|
15986
|
+
label: {
|
|
15987
|
+
type: "string",
|
|
15988
|
+
description: "Human-readable label for the new identity (default: 'sovereign-agent')"
|
|
15989
|
+
},
|
|
15990
|
+
verascore_url: {
|
|
15991
|
+
type: "string",
|
|
15992
|
+
description: "Verascore base URL. Defaults to server config / SANCTUARY_VERASCORE_URL."
|
|
15993
|
+
},
|
|
15994
|
+
publish: {
|
|
15995
|
+
type: "boolean",
|
|
15996
|
+
description: "Whether to publish the SHR to Verascore. Defaults to true."
|
|
15997
|
+
}
|
|
15998
|
+
}
|
|
15999
|
+
},
|
|
16000
|
+
handler: async (args) => {
|
|
16001
|
+
const label = args.label || "sovereign-agent";
|
|
16002
|
+
const publish = args.publish === void 0 ? true : Boolean(args.publish);
|
|
16003
|
+
const verascoreUrl = args.verascore_url || config.verascore.url || "https://verascore.ai";
|
|
16004
|
+
const { publicIdentity, storedIdentity } = createIdentity(
|
|
16005
|
+
label,
|
|
16006
|
+
identityEncKey,
|
|
16007
|
+
keyProtection
|
|
16008
|
+
);
|
|
16009
|
+
await identityManager.save(storedIdentity);
|
|
16010
|
+
auditLog.append("l1", "sanctuary_bootstrap:identity_create", publicIdentity.identity_id, {
|
|
16011
|
+
label,
|
|
16012
|
+
did: publicIdentity.did
|
|
16013
|
+
});
|
|
16014
|
+
const shr = generateSHR(publicIdentity.identity_id, {
|
|
16015
|
+
config,
|
|
16016
|
+
identityManager,
|
|
16017
|
+
masterKey
|
|
16018
|
+
});
|
|
16019
|
+
if (typeof shr === "string") {
|
|
16020
|
+
return toolResult({
|
|
16021
|
+
error: `Identity created but SHR generation failed: ${shr}`,
|
|
16022
|
+
did: publicIdentity.did,
|
|
16023
|
+
identity_id: publicIdentity.identity_id
|
|
16024
|
+
});
|
|
16025
|
+
}
|
|
16026
|
+
const agentSlug = publicIdentity.did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
16027
|
+
const profileUrl = `${verascoreUrl.replace(/\/$/, "")}/agent/${publicIdentity.did}`;
|
|
16028
|
+
if (!publish || !config.verascore.auto_publish_to_verascore) {
|
|
16029
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16030
|
+
did: publicIdentity.did,
|
|
16031
|
+
published: false
|
|
16032
|
+
});
|
|
16033
|
+
return toolResult({
|
|
16034
|
+
did: publicIdentity.did,
|
|
16035
|
+
identity_id: publicIdentity.identity_id,
|
|
16036
|
+
profileUrl,
|
|
16037
|
+
tier: "self-attested",
|
|
16038
|
+
published: false
|
|
16039
|
+
});
|
|
16040
|
+
}
|
|
16041
|
+
const urlCheck = validateVerascoreUrl(verascoreUrl, config.verascore.url);
|
|
16042
|
+
if (!urlCheck.ok) {
|
|
16043
|
+
return toolResult({
|
|
16044
|
+
error: urlCheck.error,
|
|
16045
|
+
did: publicIdentity.did,
|
|
16046
|
+
identity_id: publicIdentity.identity_id
|
|
16047
|
+
});
|
|
16048
|
+
}
|
|
16049
|
+
const publishData = {
|
|
16050
|
+
sovereigntyLayers: shr.body.layers,
|
|
16051
|
+
capabilities: shr.body.capabilities,
|
|
16052
|
+
degradations: shr.body.degradations,
|
|
16053
|
+
did: publicIdentity.did,
|
|
16054
|
+
label
|
|
16055
|
+
};
|
|
16056
|
+
const payloadBytes = new TextEncoder().encode(JSON.stringify(publishData));
|
|
16057
|
+
let signatureB64;
|
|
16058
|
+
try {
|
|
16059
|
+
const sigBytes = sign(
|
|
16060
|
+
payloadBytes,
|
|
16061
|
+
storedIdentity.encrypted_private_key,
|
|
16062
|
+
identityEncKey
|
|
16063
|
+
);
|
|
16064
|
+
signatureB64 = toBase64url(sigBytes);
|
|
16065
|
+
} catch (err) {
|
|
16066
|
+
return toolResult({
|
|
16067
|
+
error: "Failed to sign bootstrap payload",
|
|
16068
|
+
details: err instanceof Error ? err.message : String(err),
|
|
16069
|
+
did: publicIdentity.did
|
|
16070
|
+
});
|
|
16071
|
+
}
|
|
16072
|
+
const body = {
|
|
16073
|
+
agentId: agentSlug,
|
|
16074
|
+
signature: signatureB64,
|
|
16075
|
+
publicKey: publicIdentity.public_key,
|
|
16076
|
+
type: "shr",
|
|
16077
|
+
data: publishData
|
|
16078
|
+
};
|
|
16079
|
+
try {
|
|
16080
|
+
const response = await fetch(`${verascoreUrl.replace(/\/$/, "")}/api/publish`, {
|
|
16081
|
+
method: "POST",
|
|
16082
|
+
headers: { "Content-Type": "application/json" },
|
|
16083
|
+
body: JSON.stringify(body)
|
|
16084
|
+
});
|
|
16085
|
+
const result = await response.json().catch(() => ({}));
|
|
16086
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16087
|
+
did: publicIdentity.did,
|
|
16088
|
+
verascore_url: verascoreUrl,
|
|
16089
|
+
status: response.status,
|
|
16090
|
+
published: response.ok
|
|
16091
|
+
});
|
|
16092
|
+
return toolResult({
|
|
16093
|
+
did: publicIdentity.did,
|
|
16094
|
+
identity_id: publicIdentity.identity_id,
|
|
16095
|
+
profileUrl,
|
|
16096
|
+
tier: "self-attested",
|
|
16097
|
+
published: response.ok,
|
|
16098
|
+
verascore_status: response.status,
|
|
16099
|
+
verascore_response: result
|
|
16100
|
+
});
|
|
16101
|
+
} catch (err) {
|
|
16102
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16103
|
+
did: publicIdentity.did,
|
|
16104
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16105
|
+
});
|
|
16106
|
+
return toolResult({
|
|
16107
|
+
did: publicIdentity.did,
|
|
16108
|
+
identity_id: publicIdentity.identity_id,
|
|
16109
|
+
profileUrl,
|
|
16110
|
+
tier: "self-attested",
|
|
16111
|
+
published: false,
|
|
16112
|
+
warning: `Identity created but Verascore publish failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16113
|
+
});
|
|
16114
|
+
}
|
|
16115
|
+
}
|
|
16116
|
+
},
|
|
16117
|
+
// ─── sanctuary_policy_status ───────────────────────────────────────
|
|
16118
|
+
{
|
|
16119
|
+
name: "sanctuary/sanctuary_policy_status",
|
|
16120
|
+
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).",
|
|
16121
|
+
inputSchema: {
|
|
16122
|
+
type: "object",
|
|
16123
|
+
properties: {}
|
|
16124
|
+
},
|
|
16125
|
+
handler: async () => {
|
|
16126
|
+
const tier1 = [...policy.tier1_always_approve].sort();
|
|
16127
|
+
const tier3 = [...policy.tier3_always_allow].sort();
|
|
16128
|
+
const tier2Config = policy.tier2_anomaly;
|
|
16129
|
+
auditLog.append("l2", "sanctuary_policy_status", "system", {
|
|
16130
|
+
tier1_count: tier1.length,
|
|
16131
|
+
tier3_count: tier3.length
|
|
16132
|
+
});
|
|
16133
|
+
return toolResult({
|
|
16134
|
+
tier1,
|
|
16135
|
+
tier2: [],
|
|
16136
|
+
tier3,
|
|
16137
|
+
tier2_anomaly_config: tier2Config,
|
|
16138
|
+
counts: {
|
|
16139
|
+
tier1: tier1.length,
|
|
16140
|
+
tier2: 0,
|
|
16141
|
+
tier3: tier3.length
|
|
16142
|
+
},
|
|
16143
|
+
note: "Tier 2 is not a named list in Sanctuary \u2014 it is behavioral anomaly detection applied to all operations. See tier2_anomaly_config."
|
|
16144
|
+
});
|
|
16145
|
+
}
|
|
16146
|
+
},
|
|
16147
|
+
// ─── sanctuary_export_identity_bundle ──────────────────────────────
|
|
16148
|
+
{
|
|
16149
|
+
name: "sanctuary/sanctuary_export_identity_bundle",
|
|
16150
|
+
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.",
|
|
16151
|
+
inputSchema: {
|
|
16152
|
+
type: "object",
|
|
16153
|
+
properties: {
|
|
16154
|
+
identity_id: {
|
|
16155
|
+
type: "string",
|
|
16156
|
+
description: "Identity to export (defaults to primary identity)."
|
|
16157
|
+
},
|
|
16158
|
+
attestations: {
|
|
16159
|
+
type: "array",
|
|
16160
|
+
items: { type: "object" },
|
|
16161
|
+
description: "Optional list of attestation objects to include in the bundle."
|
|
16162
|
+
}
|
|
16163
|
+
}
|
|
16164
|
+
},
|
|
16165
|
+
handler: async (args) => {
|
|
16166
|
+
const identityId = args.identity_id;
|
|
16167
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
16168
|
+
if (!identity) {
|
|
16169
|
+
return toolResult({
|
|
16170
|
+
error: "No identity found. Create one with identity_create first."
|
|
16171
|
+
});
|
|
16172
|
+
}
|
|
16173
|
+
const shr = generateSHR(identity.identity_id, {
|
|
16174
|
+
config,
|
|
16175
|
+
identityManager,
|
|
16176
|
+
masterKey
|
|
16177
|
+
});
|
|
16178
|
+
const attestations = args.attestations ?? [];
|
|
16179
|
+
const body = {
|
|
16180
|
+
format: "SANCTUARY_IDENTITY_BUNDLE_V1",
|
|
16181
|
+
publicKey: identity.public_key,
|
|
16182
|
+
did: identity.did,
|
|
16183
|
+
identity_id: identity.identity_id,
|
|
16184
|
+
label: identity.label,
|
|
16185
|
+
key_type: identity.key_type,
|
|
16186
|
+
shr: typeof shr === "string" ? null : shr,
|
|
16187
|
+
attestations,
|
|
16188
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
16189
|
+
};
|
|
16190
|
+
const bodyBytes = new TextEncoder().encode(JSON.stringify(body));
|
|
16191
|
+
let signatureB64;
|
|
16192
|
+
try {
|
|
16193
|
+
const sigBytes = sign(
|
|
16194
|
+
bodyBytes,
|
|
16195
|
+
identity.encrypted_private_key,
|
|
16196
|
+
identityEncKey
|
|
16197
|
+
);
|
|
16198
|
+
signatureB64 = toBase64url(sigBytes);
|
|
16199
|
+
} catch (err) {
|
|
16200
|
+
return toolResult({
|
|
16201
|
+
error: "Failed to sign identity bundle.",
|
|
16202
|
+
details: err instanceof Error ? err.message : String(err)
|
|
16203
|
+
});
|
|
16204
|
+
}
|
|
16205
|
+
auditLog.append("l1", "sanctuary_export_identity_bundle", identity.identity_id, {
|
|
16206
|
+
did: identity.did,
|
|
16207
|
+
attestation_count: attestations.length
|
|
16208
|
+
});
|
|
16209
|
+
return toolResult({
|
|
16210
|
+
bundle: body,
|
|
16211
|
+
signature: signatureB64,
|
|
16212
|
+
signed_by: identity.did
|
|
16213
|
+
});
|
|
16214
|
+
}
|
|
16215
|
+
},
|
|
16216
|
+
// ─── sanctuary_link_to_human ───────────────────────────────────────
|
|
16217
|
+
{
|
|
16218
|
+
name: "sanctuary/sanctuary_link_to_human",
|
|
16219
|
+
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.",
|
|
16220
|
+
inputSchema: {
|
|
16221
|
+
type: "object",
|
|
16222
|
+
properties: {
|
|
16223
|
+
email: {
|
|
16224
|
+
type: "string",
|
|
16225
|
+
description: "Email address of the human to link this agent to."
|
|
16226
|
+
},
|
|
16227
|
+
verascore_url: {
|
|
16228
|
+
type: "string",
|
|
16229
|
+
description: "Verascore base URL. Defaults to server config."
|
|
16230
|
+
}
|
|
16231
|
+
},
|
|
16232
|
+
required: ["email"]
|
|
16233
|
+
},
|
|
16234
|
+
handler: async (args) => {
|
|
16235
|
+
const email = args.email;
|
|
16236
|
+
const verascoreUrl = args.verascore_url || config.verascore.url || "https://verascore.ai";
|
|
16237
|
+
const urlCheck = validateVerascoreUrl(verascoreUrl, config.verascore.url);
|
|
16238
|
+
if (!urlCheck.ok) {
|
|
16239
|
+
return toolResult({ ok: false, error: urlCheck.error });
|
|
16240
|
+
}
|
|
16241
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
|
|
16242
|
+
return toolResult({ ok: false, error: "Invalid email format." });
|
|
16243
|
+
}
|
|
16244
|
+
try {
|
|
16245
|
+
const response = await fetch(`${verascoreUrl.replace(/\/$/, "")}/api/auth/request`, {
|
|
16246
|
+
method: "POST",
|
|
16247
|
+
headers: { "Content-Type": "application/json" },
|
|
16248
|
+
body: JSON.stringify({ email })
|
|
16249
|
+
});
|
|
16250
|
+
await response.json().catch(() => ({}));
|
|
16251
|
+
auditLog.append("l4", "sanctuary_link_to_human", "system", {
|
|
16252
|
+
verascore_url: verascoreUrl,
|
|
16253
|
+
status: response.status,
|
|
16254
|
+
// Do not log the email to the audit trail — keep it local.
|
|
16255
|
+
email_domain: email.split("@")[1] ?? null
|
|
16256
|
+
});
|
|
16257
|
+
return toolResult({
|
|
16258
|
+
ok: response.ok,
|
|
16259
|
+
message: "Check your email for a login link. After logging in, visit verascore.ai to claim this agent's DID.",
|
|
16260
|
+
email_redacted: `***@${email.split("@")[1] ?? "***"}`,
|
|
16261
|
+
verascore_status: response.status
|
|
16262
|
+
});
|
|
16263
|
+
} catch (err) {
|
|
16264
|
+
return toolResult({
|
|
16265
|
+
ok: false,
|
|
16266
|
+
error: `Failed to reach Verascore at ${verascoreUrl}: ${err instanceof Error ? err.message : String(err)}`
|
|
16267
|
+
});
|
|
16268
|
+
}
|
|
16269
|
+
}
|
|
16270
|
+
},
|
|
16271
|
+
// ─── sanctuary_sign_challenge ──────────────────────────────────────
|
|
16272
|
+
{
|
|
16273
|
+
name: "sanctuary/sanctuary_sign_challenge",
|
|
16274
|
+
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.",
|
|
16275
|
+
inputSchema: {
|
|
16276
|
+
type: "object",
|
|
16277
|
+
properties: {
|
|
16278
|
+
nonce: {
|
|
16279
|
+
type: "string",
|
|
16280
|
+
description: "The nonce / challenge string to sign."
|
|
16281
|
+
},
|
|
16282
|
+
purpose: {
|
|
16283
|
+
type: "string",
|
|
16284
|
+
description: "Domain-separation tag identifying what the signature will be used for (e.g. 'verascore-claim'). Required. Max 128 chars, printable ASCII only."
|
|
16285
|
+
},
|
|
16286
|
+
identity_id: {
|
|
16287
|
+
type: "string",
|
|
16288
|
+
description: "Identity to sign with (defaults to primary)."
|
|
16289
|
+
}
|
|
16290
|
+
},
|
|
16291
|
+
required: ["nonce", "purpose"]
|
|
16292
|
+
},
|
|
16293
|
+
handler: async (args) => {
|
|
16294
|
+
const nonce = args.nonce;
|
|
16295
|
+
const purpose = args.purpose;
|
|
16296
|
+
if (!nonce || nonce.length === 0) {
|
|
16297
|
+
return toolResult({ error: "nonce must be a non-empty string." });
|
|
16298
|
+
}
|
|
16299
|
+
if (nonce.length > 4096) {
|
|
16300
|
+
return toolResult({ error: "nonce exceeds maximum length (4096)." });
|
|
16301
|
+
}
|
|
16302
|
+
if (typeof purpose !== "string" || purpose.length === 0) {
|
|
16303
|
+
return toolResult({
|
|
16304
|
+
error: "purpose is required (domain-separation tag, e.g. 'verascore-claim')."
|
|
16305
|
+
});
|
|
16306
|
+
}
|
|
16307
|
+
if (purpose.length > 128) {
|
|
16308
|
+
return toolResult({ error: "purpose exceeds maximum length (128)." });
|
|
16309
|
+
}
|
|
16310
|
+
if (!/^[\x20-\x7E]+$/.test(purpose)) {
|
|
16311
|
+
return toolResult({
|
|
16312
|
+
error: "purpose must be printable ASCII only (no NUL, no non-ASCII)."
|
|
16313
|
+
});
|
|
16314
|
+
}
|
|
16315
|
+
const identityId = args.identity_id;
|
|
16316
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
16317
|
+
if (!identity) {
|
|
16318
|
+
return toolResult({
|
|
16319
|
+
error: "No identity found. Create one with identity_create first."
|
|
16320
|
+
});
|
|
16321
|
+
}
|
|
16322
|
+
const domainTag = "sanctuary-sign-challenge-v1";
|
|
16323
|
+
const enc = new TextEncoder();
|
|
16324
|
+
const tagBytes = enc.encode(domainTag);
|
|
16325
|
+
const purposeBytes = enc.encode(purpose);
|
|
16326
|
+
const nonceBytes = enc.encode(nonce);
|
|
16327
|
+
const sep = new Uint8Array([0]);
|
|
16328
|
+
const message = new Uint8Array(
|
|
16329
|
+
tagBytes.length + 1 + purposeBytes.length + 1 + nonceBytes.length
|
|
16330
|
+
);
|
|
16331
|
+
let offset = 0;
|
|
16332
|
+
message.set(tagBytes, offset);
|
|
16333
|
+
offset += tagBytes.length;
|
|
16334
|
+
message.set(sep, offset);
|
|
16335
|
+
offset += 1;
|
|
16336
|
+
message.set(purposeBytes, offset);
|
|
16337
|
+
offset += purposeBytes.length;
|
|
16338
|
+
message.set(sep, offset);
|
|
16339
|
+
offset += 1;
|
|
16340
|
+
message.set(nonceBytes, offset);
|
|
16341
|
+
let sigB64;
|
|
16342
|
+
try {
|
|
16343
|
+
const sig = sign(
|
|
16344
|
+
message,
|
|
16345
|
+
identity.encrypted_private_key,
|
|
16346
|
+
identityEncKey
|
|
16347
|
+
);
|
|
16348
|
+
sigB64 = toBase64url(sig);
|
|
16349
|
+
} catch (err) {
|
|
16350
|
+
return toolResult({
|
|
16351
|
+
error: "Failed to sign nonce.",
|
|
16352
|
+
details: err instanceof Error ? err.message : String(err)
|
|
16353
|
+
});
|
|
16354
|
+
}
|
|
16355
|
+
auditLog.append("l1", "sanctuary_sign_challenge", identity.identity_id, {
|
|
16356
|
+
did: identity.did,
|
|
16357
|
+
nonce_len: nonce.length,
|
|
16358
|
+
purpose
|
|
16359
|
+
});
|
|
16360
|
+
return toolResult({
|
|
16361
|
+
signature: sigB64,
|
|
16362
|
+
did: identity.did,
|
|
16363
|
+
public_key: identity.public_key,
|
|
16364
|
+
signed_by: identity.did,
|
|
16365
|
+
domain_tag: domainTag,
|
|
16366
|
+
purpose
|
|
16367
|
+
});
|
|
16368
|
+
}
|
|
16369
|
+
}
|
|
16370
|
+
];
|
|
16371
|
+
return { tools };
|
|
16372
|
+
}
|
|
16373
|
+
|
|
15803
16374
|
// src/index.ts
|
|
15804
16375
|
init_random();
|
|
15805
16376
|
init_encoding();
|
|
@@ -16285,14 +16856,19 @@ async function createSanctuaryServer(options) {
|
|
|
16285
16856
|
config,
|
|
16286
16857
|
identityManager,
|
|
16287
16858
|
masterKey,
|
|
16288
|
-
auditLog
|
|
16859
|
+
auditLog,
|
|
16860
|
+
{
|
|
16861
|
+
autoPublishHandshakes: config.verascore.auto_publish_handshakes,
|
|
16862
|
+
verascoreUrl: config.verascore.url
|
|
16863
|
+
}
|
|
16289
16864
|
);
|
|
16290
16865
|
const { tools: l4Tools} = createL4Tools(
|
|
16291
16866
|
storage,
|
|
16292
16867
|
masterKey,
|
|
16293
16868
|
identityManager,
|
|
16294
16869
|
auditLog,
|
|
16295
|
-
handshakeResults
|
|
16870
|
+
handshakeResults,
|
|
16871
|
+
config.verascore.url
|
|
16296
16872
|
);
|
|
16297
16873
|
const { tools: federationTools } = createFederationTools(
|
|
16298
16874
|
auditLog,
|
|
@@ -16377,6 +16953,14 @@ async function createSanctuaryServer(options) {
|
|
|
16377
16953
|
} : void 0;
|
|
16378
16954
|
const gate = new ApprovalGate(policy, baseline, approvalChannel, auditLog, injectionDetector, onInjectionAlert);
|
|
16379
16955
|
const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
|
|
16956
|
+
const { tools: sanctuaryMetaTools } = createSanctuaryTools({
|
|
16957
|
+
config,
|
|
16958
|
+
identityManager,
|
|
16959
|
+
masterKey,
|
|
16960
|
+
auditLog,
|
|
16961
|
+
policy,
|
|
16962
|
+
keyProtection
|
|
16963
|
+
});
|
|
16380
16964
|
const dashboardTools = [];
|
|
16381
16965
|
if (dashboard) {
|
|
16382
16966
|
dashboardTools.push({
|
|
@@ -16418,6 +17002,7 @@ async function createSanctuaryServer(options) {
|
|
|
16418
17002
|
...hardeningTools,
|
|
16419
17003
|
...profileTools,
|
|
16420
17004
|
...dashboardTools,
|
|
17005
|
+
...sanctuaryMetaTools,
|
|
16421
17006
|
manifestTool
|
|
16422
17007
|
];
|
|
16423
17008
|
let clientManager;
|