@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/index.js
CHANGED
|
@@ -426,6 +426,12 @@ function defaultConfig() {
|
|
|
426
426
|
secret: "",
|
|
427
427
|
callback_port: 3502,
|
|
428
428
|
callback_host: "127.0.0.1"
|
|
429
|
+
},
|
|
430
|
+
verascore: {
|
|
431
|
+
url: "https://verascore.ai",
|
|
432
|
+
auto_publish_to_verascore: true,
|
|
433
|
+
// DELTA-04: default OFF for privacy. Enable explicitly per deployment.
|
|
434
|
+
auto_publish_handshakes: false
|
|
429
435
|
}
|
|
430
436
|
};
|
|
431
437
|
}
|
|
@@ -496,6 +502,21 @@ async function loadConfig(configPath) {
|
|
|
496
502
|
if (process.env.SANCTUARY_WEBHOOK_CALLBACK_HOST) {
|
|
497
503
|
config.webhook.callback_host = process.env.SANCTUARY_WEBHOOK_CALLBACK_HOST;
|
|
498
504
|
}
|
|
505
|
+
if (process.env.SANCTUARY_VERASCORE_URL) {
|
|
506
|
+
config.verascore.url = process.env.SANCTUARY_VERASCORE_URL;
|
|
507
|
+
}
|
|
508
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_TO_VERASCORE === "true") {
|
|
509
|
+
config.verascore.auto_publish_to_verascore = true;
|
|
510
|
+
}
|
|
511
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_TO_VERASCORE === "false") {
|
|
512
|
+
config.verascore.auto_publish_to_verascore = false;
|
|
513
|
+
}
|
|
514
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_HANDSHAKES === "true") {
|
|
515
|
+
config.verascore.auto_publish_handshakes = true;
|
|
516
|
+
}
|
|
517
|
+
if (process.env.SANCTUARY_AUTO_PUBLISH_HANDSHAKES === "false") {
|
|
518
|
+
config.verascore.auto_publish_handshakes = false;
|
|
519
|
+
}
|
|
499
520
|
config.version = PKG_VERSION;
|
|
500
521
|
validateConfig(config);
|
|
501
522
|
return config;
|
|
@@ -3185,7 +3206,7 @@ function tierDistribution(tiers) {
|
|
|
3185
3206
|
}
|
|
3186
3207
|
|
|
3187
3208
|
// src/l4-reputation/tools.ts
|
|
3188
|
-
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults) {
|
|
3209
|
+
function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeResults, verascoreUrl) {
|
|
3189
3210
|
const reputationStore = new ReputationStore(storage, masterKey);
|
|
3190
3211
|
const identityEncryptionKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
3191
3212
|
const hsResults = handshakeResults ?? /* @__PURE__ */ new Map();
|
|
@@ -3675,8 +3696,18 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
3675
3696
|
});
|
|
3676
3697
|
}
|
|
3677
3698
|
const publishType = args.type;
|
|
3678
|
-
const
|
|
3679
|
-
const
|
|
3699
|
+
const configuredVerascoreUrl = verascoreUrl || "https://verascore.ai";
|
|
3700
|
+
const veracoreUrl = args.verascore_url || configuredVerascoreUrl;
|
|
3701
|
+
const ALLOWED_VERASCORE_HOSTS = /* @__PURE__ */ new Set([
|
|
3702
|
+
"verascore.ai",
|
|
3703
|
+
"www.verascore.ai",
|
|
3704
|
+
"api.verascore.ai"
|
|
3705
|
+
]);
|
|
3706
|
+
try {
|
|
3707
|
+
const configuredHost = new URL(configuredVerascoreUrl).hostname;
|
|
3708
|
+
ALLOWED_VERASCORE_HOSTS.add(configuredHost);
|
|
3709
|
+
} catch {
|
|
3710
|
+
}
|
|
3680
3711
|
try {
|
|
3681
3712
|
const parsed = new URL(veracoreUrl);
|
|
3682
3713
|
if (parsed.protocol !== "https:") {
|
|
@@ -3684,9 +3715,9 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
3684
3715
|
error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
|
|
3685
3716
|
});
|
|
3686
3717
|
}
|
|
3687
|
-
if (!ALLOWED_VERASCORE_HOSTS.
|
|
3718
|
+
if (!ALLOWED_VERASCORE_HOSTS.has(parsed.hostname)) {
|
|
3688
3719
|
return toolResult({
|
|
3689
|
-
error: `verascore_url must point to a known Verascore domain (${ALLOWED_VERASCORE_HOSTS.join(", ")}). Got: ${parsed.hostname}`
|
|
3720
|
+
error: `verascore_url must point to a known Verascore domain (${[...ALLOWED_VERASCORE_HOSTS].join(", ")}). Got: ${parsed.hostname}`
|
|
3690
3721
|
});
|
|
3691
3722
|
}
|
|
3692
3723
|
} catch {
|
|
@@ -3817,12 +3848,14 @@ var DEFAULT_POLICY = {
|
|
|
3817
3848
|
"reputation_export",
|
|
3818
3849
|
"bootstrap_provide_guarantee",
|
|
3819
3850
|
"decommission_certificate",
|
|
3820
|
-
"reputation_publish",
|
|
3821
|
-
// SEC-039: Explicit Tier 1 — sends data to external API
|
|
3822
3851
|
"sovereignty_profile_update",
|
|
3823
3852
|
// Changes enforcement behavior — always requires approval
|
|
3824
|
-
"governor_reset"
|
|
3853
|
+
"governor_reset",
|
|
3825
3854
|
// Clears all runtime governance state — always requires approval
|
|
3855
|
+
"sanctuary_bootstrap",
|
|
3856
|
+
// Creates new Ed25519 identity + publishes — always requires approval
|
|
3857
|
+
"sanctuary_export_identity_bundle"
|
|
3858
|
+
// Exports portable identity — always requires approval
|
|
3826
3859
|
],
|
|
3827
3860
|
tier2_anomaly: DEFAULT_TIER2,
|
|
3828
3861
|
tier3_always_allow: [
|
|
@@ -3880,7 +3913,11 @@ var DEFAULT_POLICY = {
|
|
|
3880
3913
|
"sovereignty_profile_get",
|
|
3881
3914
|
"sovereignty_profile_generate_prompt",
|
|
3882
3915
|
// Agent needs its own config to generate system prompt
|
|
3883
|
-
"governor_status"
|
|
3916
|
+
"governor_status",
|
|
3917
|
+
"reputation_publish",
|
|
3918
|
+
// Auto-allow: publishing sovereignty data to Verascore is routine
|
|
3919
|
+
"sanctuary_policy_status"
|
|
3920
|
+
// Read-only policy summary
|
|
3884
3921
|
],
|
|
3885
3922
|
approval_channel: DEFAULT_CHANNEL
|
|
3886
3923
|
};
|
|
@@ -3991,9 +4028,10 @@ tier1_always_approve:
|
|
|
3991
4028
|
- reputation_import
|
|
3992
4029
|
- reputation_export
|
|
3993
4030
|
- bootstrap_provide_guarantee
|
|
3994
|
-
- reputation_publish
|
|
3995
4031
|
- sovereignty_profile_update
|
|
3996
4032
|
- governor_reset
|
|
4033
|
+
- sanctuary_bootstrap
|
|
4034
|
+
- sanctuary_export_identity_bundle
|
|
3997
4035
|
|
|
3998
4036
|
# \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
|
|
3999
4037
|
# Triggers approval when agent behavior deviates from its baseline.
|
|
@@ -4059,6 +4097,10 @@ tier3_always_allow:
|
|
|
4059
4097
|
- dashboard_open
|
|
4060
4098
|
- sovereignty_profile_get
|
|
4061
4099
|
- governor_status
|
|
4100
|
+
- reputation_publish
|
|
4101
|
+
- sanctuary_policy_status
|
|
4102
|
+
- sanctuary_link_to_human
|
|
4103
|
+
- sanctuary_sign_challenge
|
|
4062
4104
|
|
|
4063
4105
|
# \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
|
|
4064
4106
|
# How Sanctuary reaches you when approval is needed.
|
|
@@ -10577,6 +10619,10 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
10577
10619
|
return { tools };
|
|
10578
10620
|
}
|
|
10579
10621
|
|
|
10622
|
+
// src/handshake/tools.ts
|
|
10623
|
+
init_identity();
|
|
10624
|
+
init_encoding();
|
|
10625
|
+
|
|
10580
10626
|
// src/handshake/protocol.ts
|
|
10581
10627
|
init_identity();
|
|
10582
10628
|
init_encoding();
|
|
@@ -10897,7 +10943,10 @@ function verifyAttestation(attestation, now) {
|
|
|
10897
10943
|
}
|
|
10898
10944
|
|
|
10899
10945
|
// src/handshake/tools.ts
|
|
10900
|
-
function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
10946
|
+
function createHandshakeTools(config, identityManager, masterKey, auditLog, options) {
|
|
10947
|
+
const autoPublishHandshakes = options?.autoPublishHandshakes ?? false;
|
|
10948
|
+
const verascoreUrl = options?.verascoreUrl ?? "https://verascore.ai";
|
|
10949
|
+
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
10901
10950
|
const sessions = /* @__PURE__ */ new Map();
|
|
10902
10951
|
const handshakeResults = /* @__PURE__ */ new Map();
|
|
10903
10952
|
const shrOpts = {
|
|
@@ -10969,10 +11018,86 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog) {
|
|
|
10969
11018
|
}
|
|
10970
11019
|
sessions.set(result.session.session_id, result.session);
|
|
10971
11020
|
auditLog.append("l4", "handshake_respond", shr.body.instance_id);
|
|
11021
|
+
let autoPublishResult;
|
|
11022
|
+
if (autoPublishHandshakes) {
|
|
11023
|
+
autoPublishResult = { attempted: true };
|
|
11024
|
+
try {
|
|
11025
|
+
const parsed = new URL(verascoreUrl);
|
|
11026
|
+
if (parsed.protocol !== "https:") {
|
|
11027
|
+
autoPublishResult.error = `verascore URL must use HTTPS (got ${parsed.protocol})`;
|
|
11028
|
+
} else {
|
|
11029
|
+
const attestationPayload = {
|
|
11030
|
+
type: "handshake",
|
|
11031
|
+
our_shr_signed_by: shr.signed_by,
|
|
11032
|
+
counterparty_signed_by: "redacted",
|
|
11033
|
+
session_id: result.session.session_id,
|
|
11034
|
+
responded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11035
|
+
};
|
|
11036
|
+
const responderIdentity = identityManager.get(shr.body.instance_id);
|
|
11037
|
+
if (!responderIdentity) {
|
|
11038
|
+
autoPublishResult.error = `responder identity ${shr.body.instance_id} not found; skipping auto-publish`;
|
|
11039
|
+
auditLog.append(
|
|
11040
|
+
"l4",
|
|
11041
|
+
"handshake_auto_publish",
|
|
11042
|
+
shr.body.instance_id,
|
|
11043
|
+
{ error: autoPublishResult.error },
|
|
11044
|
+
"failure"
|
|
11045
|
+
);
|
|
11046
|
+
} else {
|
|
11047
|
+
const payloadBytes = new TextEncoder().encode(
|
|
11048
|
+
JSON.stringify(attestationPayload)
|
|
11049
|
+
);
|
|
11050
|
+
const sigBytes = sign(
|
|
11051
|
+
payloadBytes,
|
|
11052
|
+
responderIdentity.encrypted_private_key,
|
|
11053
|
+
identityEncKey
|
|
11054
|
+
);
|
|
11055
|
+
const signatureB64 = toBase64url(sigBytes);
|
|
11056
|
+
const resp = await fetch(
|
|
11057
|
+
`${verascoreUrl.replace(/\/$/, "")}/api/publish`,
|
|
11058
|
+
{
|
|
11059
|
+
method: "POST",
|
|
11060
|
+
headers: { "Content-Type": "application/json" },
|
|
11061
|
+
body: JSON.stringify({
|
|
11062
|
+
agentId: shr.body.instance_id,
|
|
11063
|
+
publicKey: shr.signed_by,
|
|
11064
|
+
signature: signatureB64,
|
|
11065
|
+
type: "handshake",
|
|
11066
|
+
data: attestationPayload
|
|
11067
|
+
})
|
|
11068
|
+
}
|
|
11069
|
+
);
|
|
11070
|
+
autoPublishResult.ok = resp.ok;
|
|
11071
|
+
autoPublishResult.status = resp.status;
|
|
11072
|
+
auditLog.append(
|
|
11073
|
+
"l4",
|
|
11074
|
+
"handshake_auto_publish",
|
|
11075
|
+
shr.body.instance_id,
|
|
11076
|
+
{
|
|
11077
|
+
verascore_url: verascoreUrl,
|
|
11078
|
+
status: resp.status,
|
|
11079
|
+
ok: resp.ok
|
|
11080
|
+
},
|
|
11081
|
+
resp.ok ? "success" : "failure"
|
|
11082
|
+
);
|
|
11083
|
+
}
|
|
11084
|
+
}
|
|
11085
|
+
} catch (err) {
|
|
11086
|
+
autoPublishResult.error = err instanceof Error ? err.message : String(err);
|
|
11087
|
+
auditLog.append(
|
|
11088
|
+
"l4",
|
|
11089
|
+
"handshake_auto_publish",
|
|
11090
|
+
shr.body.instance_id,
|
|
11091
|
+
{ verascore_url: verascoreUrl, error: autoPublishResult.error },
|
|
11092
|
+
"failure"
|
|
11093
|
+
);
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
10972
11096
|
return toolResult({
|
|
10973
11097
|
session_id: result.session.session_id,
|
|
10974
11098
|
response: result.response,
|
|
10975
11099
|
instructions: "Send the 'response' object back to the initiator. When you receive their completion, pass it to sanctuary/handshake_status with this session_id.",
|
|
11100
|
+
auto_publish: autoPublishResult,
|
|
10976
11101
|
// SEC-ADD-03: Tag response — contains SHR data that will be sent to counterparty
|
|
10977
11102
|
_content_trust: "external"
|
|
10978
11103
|
});
|
|
@@ -15814,6 +15939,435 @@ function createGovernorTools(governor, auditLog) {
|
|
|
15814
15939
|
return { tools };
|
|
15815
15940
|
}
|
|
15816
15941
|
|
|
15942
|
+
// src/sanctuary-tools.ts
|
|
15943
|
+
init_identity();
|
|
15944
|
+
init_encoding();
|
|
15945
|
+
init_identity();
|
|
15946
|
+
function validateVerascoreUrl(urlStr, configuredUrl) {
|
|
15947
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
15948
|
+
"verascore.ai",
|
|
15949
|
+
"www.verascore.ai",
|
|
15950
|
+
"api.verascore.ai"
|
|
15951
|
+
]);
|
|
15952
|
+
try {
|
|
15953
|
+
allowed.add(new URL(configuredUrl).hostname);
|
|
15954
|
+
} catch {
|
|
15955
|
+
}
|
|
15956
|
+
try {
|
|
15957
|
+
const parsed = new URL(urlStr);
|
|
15958
|
+
if (parsed.protocol !== "https:") {
|
|
15959
|
+
return { ok: false, error: `Verascore URL must use HTTPS. Got: ${parsed.protocol}` };
|
|
15960
|
+
}
|
|
15961
|
+
if (!allowed.has(parsed.hostname)) {
|
|
15962
|
+
return {
|
|
15963
|
+
ok: false,
|
|
15964
|
+
error: `Verascore URL must point to a known Verascore host (${[...allowed].join(", ")}). Got: ${parsed.hostname}`
|
|
15965
|
+
};
|
|
15966
|
+
}
|
|
15967
|
+
return { ok: true };
|
|
15968
|
+
} catch {
|
|
15969
|
+
return { ok: false, error: `Invalid Verascore URL: ${urlStr}` };
|
|
15970
|
+
}
|
|
15971
|
+
}
|
|
15972
|
+
function createSanctuaryTools(opts) {
|
|
15973
|
+
const { config, identityManager, masterKey, auditLog, policy, keyProtection } = opts;
|
|
15974
|
+
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
15975
|
+
const tools = [
|
|
15976
|
+
// ─── sanctuary_bootstrap ───────────────────────────────────────────
|
|
15977
|
+
{
|
|
15978
|
+
name: "sanctuary/sanctuary_bootstrap",
|
|
15979
|
+
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.",
|
|
15980
|
+
inputSchema: {
|
|
15981
|
+
type: "object",
|
|
15982
|
+
properties: {
|
|
15983
|
+
label: {
|
|
15984
|
+
type: "string",
|
|
15985
|
+
description: "Human-readable label for the new identity (default: 'sovereign-agent')"
|
|
15986
|
+
},
|
|
15987
|
+
verascore_url: {
|
|
15988
|
+
type: "string",
|
|
15989
|
+
description: "Verascore base URL. Defaults to server config / SANCTUARY_VERASCORE_URL."
|
|
15990
|
+
},
|
|
15991
|
+
publish: {
|
|
15992
|
+
type: "boolean",
|
|
15993
|
+
description: "Whether to publish the SHR to Verascore. Defaults to true."
|
|
15994
|
+
}
|
|
15995
|
+
}
|
|
15996
|
+
},
|
|
15997
|
+
handler: async (args) => {
|
|
15998
|
+
const label = args.label || "sovereign-agent";
|
|
15999
|
+
const publish = args.publish === void 0 ? true : Boolean(args.publish);
|
|
16000
|
+
const verascoreUrl = args.verascore_url || config.verascore.url || "https://verascore.ai";
|
|
16001
|
+
const { publicIdentity, storedIdentity } = createIdentity(
|
|
16002
|
+
label,
|
|
16003
|
+
identityEncKey,
|
|
16004
|
+
keyProtection
|
|
16005
|
+
);
|
|
16006
|
+
await identityManager.save(storedIdentity);
|
|
16007
|
+
auditLog.append("l1", "sanctuary_bootstrap:identity_create", publicIdentity.identity_id, {
|
|
16008
|
+
label,
|
|
16009
|
+
did: publicIdentity.did
|
|
16010
|
+
});
|
|
16011
|
+
const shr = generateSHR(publicIdentity.identity_id, {
|
|
16012
|
+
config,
|
|
16013
|
+
identityManager,
|
|
16014
|
+
masterKey
|
|
16015
|
+
});
|
|
16016
|
+
if (typeof shr === "string") {
|
|
16017
|
+
return toolResult({
|
|
16018
|
+
error: `Identity created but SHR generation failed: ${shr}`,
|
|
16019
|
+
did: publicIdentity.did,
|
|
16020
|
+
identity_id: publicIdentity.identity_id
|
|
16021
|
+
});
|
|
16022
|
+
}
|
|
16023
|
+
const agentSlug = publicIdentity.did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
16024
|
+
const profileUrl = `${verascoreUrl.replace(/\/$/, "")}/agent/${publicIdentity.did}`;
|
|
16025
|
+
if (!publish || !config.verascore.auto_publish_to_verascore) {
|
|
16026
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16027
|
+
did: publicIdentity.did,
|
|
16028
|
+
published: false
|
|
16029
|
+
});
|
|
16030
|
+
return toolResult({
|
|
16031
|
+
did: publicIdentity.did,
|
|
16032
|
+
identity_id: publicIdentity.identity_id,
|
|
16033
|
+
profileUrl,
|
|
16034
|
+
tier: "self-attested",
|
|
16035
|
+
published: false
|
|
16036
|
+
});
|
|
16037
|
+
}
|
|
16038
|
+
const urlCheck = validateVerascoreUrl(verascoreUrl, config.verascore.url);
|
|
16039
|
+
if (!urlCheck.ok) {
|
|
16040
|
+
return toolResult({
|
|
16041
|
+
error: urlCheck.error,
|
|
16042
|
+
did: publicIdentity.did,
|
|
16043
|
+
identity_id: publicIdentity.identity_id
|
|
16044
|
+
});
|
|
16045
|
+
}
|
|
16046
|
+
const publishData = {
|
|
16047
|
+
sovereigntyLayers: shr.body.layers,
|
|
16048
|
+
capabilities: shr.body.capabilities,
|
|
16049
|
+
degradations: shr.body.degradations,
|
|
16050
|
+
did: publicIdentity.did,
|
|
16051
|
+
label
|
|
16052
|
+
};
|
|
16053
|
+
const payloadBytes = new TextEncoder().encode(JSON.stringify(publishData));
|
|
16054
|
+
let signatureB64;
|
|
16055
|
+
try {
|
|
16056
|
+
const sigBytes = sign(
|
|
16057
|
+
payloadBytes,
|
|
16058
|
+
storedIdentity.encrypted_private_key,
|
|
16059
|
+
identityEncKey
|
|
16060
|
+
);
|
|
16061
|
+
signatureB64 = toBase64url(sigBytes);
|
|
16062
|
+
} catch (err) {
|
|
16063
|
+
return toolResult({
|
|
16064
|
+
error: "Failed to sign bootstrap payload",
|
|
16065
|
+
details: err instanceof Error ? err.message : String(err),
|
|
16066
|
+
did: publicIdentity.did
|
|
16067
|
+
});
|
|
16068
|
+
}
|
|
16069
|
+
const body = {
|
|
16070
|
+
agentId: agentSlug,
|
|
16071
|
+
signature: signatureB64,
|
|
16072
|
+
publicKey: publicIdentity.public_key,
|
|
16073
|
+
type: "shr",
|
|
16074
|
+
data: publishData
|
|
16075
|
+
};
|
|
16076
|
+
try {
|
|
16077
|
+
const response = await fetch(`${verascoreUrl.replace(/\/$/, "")}/api/publish`, {
|
|
16078
|
+
method: "POST",
|
|
16079
|
+
headers: { "Content-Type": "application/json" },
|
|
16080
|
+
body: JSON.stringify(body)
|
|
16081
|
+
});
|
|
16082
|
+
const result = await response.json().catch(() => ({}));
|
|
16083
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16084
|
+
did: publicIdentity.did,
|
|
16085
|
+
verascore_url: verascoreUrl,
|
|
16086
|
+
status: response.status,
|
|
16087
|
+
published: response.ok
|
|
16088
|
+
});
|
|
16089
|
+
return toolResult({
|
|
16090
|
+
did: publicIdentity.did,
|
|
16091
|
+
identity_id: publicIdentity.identity_id,
|
|
16092
|
+
profileUrl,
|
|
16093
|
+
tier: "self-attested",
|
|
16094
|
+
published: response.ok,
|
|
16095
|
+
verascore_status: response.status,
|
|
16096
|
+
verascore_response: result
|
|
16097
|
+
});
|
|
16098
|
+
} catch (err) {
|
|
16099
|
+
auditLog.append("l4", "sanctuary_bootstrap", publicIdentity.identity_id, {
|
|
16100
|
+
did: publicIdentity.did,
|
|
16101
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16102
|
+
});
|
|
16103
|
+
return toolResult({
|
|
16104
|
+
did: publicIdentity.did,
|
|
16105
|
+
identity_id: publicIdentity.identity_id,
|
|
16106
|
+
profileUrl,
|
|
16107
|
+
tier: "self-attested",
|
|
16108
|
+
published: false,
|
|
16109
|
+
warning: `Identity created but Verascore publish failed: ${err instanceof Error ? err.message : String(err)}`
|
|
16110
|
+
});
|
|
16111
|
+
}
|
|
16112
|
+
}
|
|
16113
|
+
},
|
|
16114
|
+
// ─── sanctuary_policy_status ───────────────────────────────────────
|
|
16115
|
+
{
|
|
16116
|
+
name: "sanctuary/sanctuary_policy_status",
|
|
16117
|
+
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).",
|
|
16118
|
+
inputSchema: {
|
|
16119
|
+
type: "object",
|
|
16120
|
+
properties: {}
|
|
16121
|
+
},
|
|
16122
|
+
handler: async () => {
|
|
16123
|
+
const tier1 = [...policy.tier1_always_approve].sort();
|
|
16124
|
+
const tier3 = [...policy.tier3_always_allow].sort();
|
|
16125
|
+
const tier2Config = policy.tier2_anomaly;
|
|
16126
|
+
auditLog.append("l2", "sanctuary_policy_status", "system", {
|
|
16127
|
+
tier1_count: tier1.length,
|
|
16128
|
+
tier3_count: tier3.length
|
|
16129
|
+
});
|
|
16130
|
+
return toolResult({
|
|
16131
|
+
tier1,
|
|
16132
|
+
tier2: [],
|
|
16133
|
+
tier3,
|
|
16134
|
+
tier2_anomaly_config: tier2Config,
|
|
16135
|
+
counts: {
|
|
16136
|
+
tier1: tier1.length,
|
|
16137
|
+
tier2: 0,
|
|
16138
|
+
tier3: tier3.length
|
|
16139
|
+
},
|
|
16140
|
+
note: "Tier 2 is not a named list in Sanctuary \u2014 it is behavioral anomaly detection applied to all operations. See tier2_anomaly_config."
|
|
16141
|
+
});
|
|
16142
|
+
}
|
|
16143
|
+
},
|
|
16144
|
+
// ─── sanctuary_export_identity_bundle ──────────────────────────────
|
|
16145
|
+
{
|
|
16146
|
+
name: "sanctuary/sanctuary_export_identity_bundle",
|
|
16147
|
+
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.",
|
|
16148
|
+
inputSchema: {
|
|
16149
|
+
type: "object",
|
|
16150
|
+
properties: {
|
|
16151
|
+
identity_id: {
|
|
16152
|
+
type: "string",
|
|
16153
|
+
description: "Identity to export (defaults to primary identity)."
|
|
16154
|
+
},
|
|
16155
|
+
attestations: {
|
|
16156
|
+
type: "array",
|
|
16157
|
+
items: { type: "object" },
|
|
16158
|
+
description: "Optional list of attestation objects to include in the bundle."
|
|
16159
|
+
}
|
|
16160
|
+
}
|
|
16161
|
+
},
|
|
16162
|
+
handler: async (args) => {
|
|
16163
|
+
const identityId = args.identity_id;
|
|
16164
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
16165
|
+
if (!identity) {
|
|
16166
|
+
return toolResult({
|
|
16167
|
+
error: "No identity found. Create one with identity_create first."
|
|
16168
|
+
});
|
|
16169
|
+
}
|
|
16170
|
+
const shr = generateSHR(identity.identity_id, {
|
|
16171
|
+
config,
|
|
16172
|
+
identityManager,
|
|
16173
|
+
masterKey
|
|
16174
|
+
});
|
|
16175
|
+
const attestations = args.attestations ?? [];
|
|
16176
|
+
const body = {
|
|
16177
|
+
format: "SANCTUARY_IDENTITY_BUNDLE_V1",
|
|
16178
|
+
publicKey: identity.public_key,
|
|
16179
|
+
did: identity.did,
|
|
16180
|
+
identity_id: identity.identity_id,
|
|
16181
|
+
label: identity.label,
|
|
16182
|
+
key_type: identity.key_type,
|
|
16183
|
+
shr: typeof shr === "string" ? null : shr,
|
|
16184
|
+
attestations,
|
|
16185
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
16186
|
+
};
|
|
16187
|
+
const bodyBytes = new TextEncoder().encode(JSON.stringify(body));
|
|
16188
|
+
let signatureB64;
|
|
16189
|
+
try {
|
|
16190
|
+
const sigBytes = sign(
|
|
16191
|
+
bodyBytes,
|
|
16192
|
+
identity.encrypted_private_key,
|
|
16193
|
+
identityEncKey
|
|
16194
|
+
);
|
|
16195
|
+
signatureB64 = toBase64url(sigBytes);
|
|
16196
|
+
} catch (err) {
|
|
16197
|
+
return toolResult({
|
|
16198
|
+
error: "Failed to sign identity bundle.",
|
|
16199
|
+
details: err instanceof Error ? err.message : String(err)
|
|
16200
|
+
});
|
|
16201
|
+
}
|
|
16202
|
+
auditLog.append("l1", "sanctuary_export_identity_bundle", identity.identity_id, {
|
|
16203
|
+
did: identity.did,
|
|
16204
|
+
attestation_count: attestations.length
|
|
16205
|
+
});
|
|
16206
|
+
return toolResult({
|
|
16207
|
+
bundle: body,
|
|
16208
|
+
signature: signatureB64,
|
|
16209
|
+
signed_by: identity.did
|
|
16210
|
+
});
|
|
16211
|
+
}
|
|
16212
|
+
},
|
|
16213
|
+
// ─── sanctuary_link_to_human ───────────────────────────────────────
|
|
16214
|
+
{
|
|
16215
|
+
name: "sanctuary/sanctuary_link_to_human",
|
|
16216
|
+
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.",
|
|
16217
|
+
inputSchema: {
|
|
16218
|
+
type: "object",
|
|
16219
|
+
properties: {
|
|
16220
|
+
email: {
|
|
16221
|
+
type: "string",
|
|
16222
|
+
description: "Email address of the human to link this agent to."
|
|
16223
|
+
},
|
|
16224
|
+
verascore_url: {
|
|
16225
|
+
type: "string",
|
|
16226
|
+
description: "Verascore base URL. Defaults to server config."
|
|
16227
|
+
}
|
|
16228
|
+
},
|
|
16229
|
+
required: ["email"]
|
|
16230
|
+
},
|
|
16231
|
+
handler: async (args) => {
|
|
16232
|
+
const email = args.email;
|
|
16233
|
+
const verascoreUrl = args.verascore_url || config.verascore.url || "https://verascore.ai";
|
|
16234
|
+
const urlCheck = validateVerascoreUrl(verascoreUrl, config.verascore.url);
|
|
16235
|
+
if (!urlCheck.ok) {
|
|
16236
|
+
return toolResult({ ok: false, error: urlCheck.error });
|
|
16237
|
+
}
|
|
16238
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
|
|
16239
|
+
return toolResult({ ok: false, error: "Invalid email format." });
|
|
16240
|
+
}
|
|
16241
|
+
try {
|
|
16242
|
+
const response = await fetch(`${verascoreUrl.replace(/\/$/, "")}/api/auth/request`, {
|
|
16243
|
+
method: "POST",
|
|
16244
|
+
headers: { "Content-Type": "application/json" },
|
|
16245
|
+
body: JSON.stringify({ email })
|
|
16246
|
+
});
|
|
16247
|
+
await response.json().catch(() => ({}));
|
|
16248
|
+
auditLog.append("l4", "sanctuary_link_to_human", "system", {
|
|
16249
|
+
verascore_url: verascoreUrl,
|
|
16250
|
+
status: response.status,
|
|
16251
|
+
// Do not log the email to the audit trail — keep it local.
|
|
16252
|
+
email_domain: email.split("@")[1] ?? null
|
|
16253
|
+
});
|
|
16254
|
+
return toolResult({
|
|
16255
|
+
ok: response.ok,
|
|
16256
|
+
message: "Check your email for a login link. After logging in, visit verascore.ai to claim this agent's DID.",
|
|
16257
|
+
email_redacted: `***@${email.split("@")[1] ?? "***"}`,
|
|
16258
|
+
verascore_status: response.status
|
|
16259
|
+
});
|
|
16260
|
+
} catch (err) {
|
|
16261
|
+
return toolResult({
|
|
16262
|
+
ok: false,
|
|
16263
|
+
error: `Failed to reach Verascore at ${verascoreUrl}: ${err instanceof Error ? err.message : String(err)}`
|
|
16264
|
+
});
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
},
|
|
16268
|
+
// ─── sanctuary_sign_challenge ──────────────────────────────────────
|
|
16269
|
+
{
|
|
16270
|
+
name: "sanctuary/sanctuary_sign_challenge",
|
|
16271
|
+
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.",
|
|
16272
|
+
inputSchema: {
|
|
16273
|
+
type: "object",
|
|
16274
|
+
properties: {
|
|
16275
|
+
nonce: {
|
|
16276
|
+
type: "string",
|
|
16277
|
+
description: "The nonce / challenge string to sign."
|
|
16278
|
+
},
|
|
16279
|
+
purpose: {
|
|
16280
|
+
type: "string",
|
|
16281
|
+
description: "Domain-separation tag identifying what the signature will be used for (e.g. 'verascore-claim'). Required. Max 128 chars, printable ASCII only."
|
|
16282
|
+
},
|
|
16283
|
+
identity_id: {
|
|
16284
|
+
type: "string",
|
|
16285
|
+
description: "Identity to sign with (defaults to primary)."
|
|
16286
|
+
}
|
|
16287
|
+
},
|
|
16288
|
+
required: ["nonce", "purpose"]
|
|
16289
|
+
},
|
|
16290
|
+
handler: async (args) => {
|
|
16291
|
+
const nonce = args.nonce;
|
|
16292
|
+
const purpose = args.purpose;
|
|
16293
|
+
if (!nonce || nonce.length === 0) {
|
|
16294
|
+
return toolResult({ error: "nonce must be a non-empty string." });
|
|
16295
|
+
}
|
|
16296
|
+
if (nonce.length > 4096) {
|
|
16297
|
+
return toolResult({ error: "nonce exceeds maximum length (4096)." });
|
|
16298
|
+
}
|
|
16299
|
+
if (typeof purpose !== "string" || purpose.length === 0) {
|
|
16300
|
+
return toolResult({
|
|
16301
|
+
error: "purpose is required (domain-separation tag, e.g. 'verascore-claim')."
|
|
16302
|
+
});
|
|
16303
|
+
}
|
|
16304
|
+
if (purpose.length > 128) {
|
|
16305
|
+
return toolResult({ error: "purpose exceeds maximum length (128)." });
|
|
16306
|
+
}
|
|
16307
|
+
if (!/^[\x20-\x7E]+$/.test(purpose)) {
|
|
16308
|
+
return toolResult({
|
|
16309
|
+
error: "purpose must be printable ASCII only (no NUL, no non-ASCII)."
|
|
16310
|
+
});
|
|
16311
|
+
}
|
|
16312
|
+
const identityId = args.identity_id;
|
|
16313
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
16314
|
+
if (!identity) {
|
|
16315
|
+
return toolResult({
|
|
16316
|
+
error: "No identity found. Create one with identity_create first."
|
|
16317
|
+
});
|
|
16318
|
+
}
|
|
16319
|
+
const domainTag = "sanctuary-sign-challenge-v1";
|
|
16320
|
+
const enc = new TextEncoder();
|
|
16321
|
+
const tagBytes = enc.encode(domainTag);
|
|
16322
|
+
const purposeBytes = enc.encode(purpose);
|
|
16323
|
+
const nonceBytes = enc.encode(nonce);
|
|
16324
|
+
const sep = new Uint8Array([0]);
|
|
16325
|
+
const message = new Uint8Array(
|
|
16326
|
+
tagBytes.length + 1 + purposeBytes.length + 1 + nonceBytes.length
|
|
16327
|
+
);
|
|
16328
|
+
let offset = 0;
|
|
16329
|
+
message.set(tagBytes, offset);
|
|
16330
|
+
offset += tagBytes.length;
|
|
16331
|
+
message.set(sep, offset);
|
|
16332
|
+
offset += 1;
|
|
16333
|
+
message.set(purposeBytes, offset);
|
|
16334
|
+
offset += purposeBytes.length;
|
|
16335
|
+
message.set(sep, offset);
|
|
16336
|
+
offset += 1;
|
|
16337
|
+
message.set(nonceBytes, offset);
|
|
16338
|
+
let sigB64;
|
|
16339
|
+
try {
|
|
16340
|
+
const sig = sign(
|
|
16341
|
+
message,
|
|
16342
|
+
identity.encrypted_private_key,
|
|
16343
|
+
identityEncKey
|
|
16344
|
+
);
|
|
16345
|
+
sigB64 = toBase64url(sig);
|
|
16346
|
+
} catch (err) {
|
|
16347
|
+
return toolResult({
|
|
16348
|
+
error: "Failed to sign nonce.",
|
|
16349
|
+
details: err instanceof Error ? err.message : String(err)
|
|
16350
|
+
});
|
|
16351
|
+
}
|
|
16352
|
+
auditLog.append("l1", "sanctuary_sign_challenge", identity.identity_id, {
|
|
16353
|
+
did: identity.did,
|
|
16354
|
+
nonce_len: nonce.length,
|
|
16355
|
+
purpose
|
|
16356
|
+
});
|
|
16357
|
+
return toolResult({
|
|
16358
|
+
signature: sigB64,
|
|
16359
|
+
did: identity.did,
|
|
16360
|
+
public_key: identity.public_key,
|
|
16361
|
+
signed_by: identity.did,
|
|
16362
|
+
domain_tag: domainTag,
|
|
16363
|
+
purpose
|
|
16364
|
+
});
|
|
16365
|
+
}
|
|
16366
|
+
}
|
|
16367
|
+
];
|
|
16368
|
+
return { tools };
|
|
16369
|
+
}
|
|
16370
|
+
|
|
15817
16371
|
// src/index.ts
|
|
15818
16372
|
init_random();
|
|
15819
16373
|
init_encoding();
|
|
@@ -16299,14 +16853,19 @@ async function createSanctuaryServer(options) {
|
|
|
16299
16853
|
config,
|
|
16300
16854
|
identityManager,
|
|
16301
16855
|
masterKey,
|
|
16302
|
-
auditLog
|
|
16856
|
+
auditLog,
|
|
16857
|
+
{
|
|
16858
|
+
autoPublishHandshakes: config.verascore.auto_publish_handshakes,
|
|
16859
|
+
verascoreUrl: config.verascore.url
|
|
16860
|
+
}
|
|
16303
16861
|
);
|
|
16304
16862
|
const { tools: l4Tools} = createL4Tools(
|
|
16305
16863
|
storage,
|
|
16306
16864
|
masterKey,
|
|
16307
16865
|
identityManager,
|
|
16308
16866
|
auditLog,
|
|
16309
|
-
handshakeResults
|
|
16867
|
+
handshakeResults,
|
|
16868
|
+
config.verascore.url
|
|
16310
16869
|
);
|
|
16311
16870
|
const { tools: federationTools } = createFederationTools(
|
|
16312
16871
|
auditLog,
|
|
@@ -16391,6 +16950,14 @@ async function createSanctuaryServer(options) {
|
|
|
16391
16950
|
} : void 0;
|
|
16392
16951
|
const gate = new ApprovalGate(policy, baseline, approvalChannel, auditLog, injectionDetector, onInjectionAlert);
|
|
16393
16952
|
const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
|
|
16953
|
+
const { tools: sanctuaryMetaTools } = createSanctuaryTools({
|
|
16954
|
+
config,
|
|
16955
|
+
identityManager,
|
|
16956
|
+
masterKey,
|
|
16957
|
+
auditLog,
|
|
16958
|
+
policy,
|
|
16959
|
+
keyProtection
|
|
16960
|
+
});
|
|
16394
16961
|
const dashboardTools = [];
|
|
16395
16962
|
if (dashboard) {
|
|
16396
16963
|
dashboardTools.push({
|
|
@@ -16432,6 +16999,7 @@ async function createSanctuaryServer(options) {
|
|
|
16432
16999
|
...hardeningTools,
|
|
16433
17000
|
...profileTools,
|
|
16434
17001
|
...dashboardTools,
|
|
17002
|
+
...sanctuaryMetaTools,
|
|
16435
17003
|
manifestTool
|
|
16436
17004
|
];
|
|
16437
17005
|
let clientManager;
|