@sanctuary-framework/mcp-server 0.5.11 → 0.5.13
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 +108 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +108 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +278 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +278 -170
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -560,6 +560,18 @@ var init_hashing = __esm({
|
|
|
560
560
|
init_encoding();
|
|
561
561
|
}
|
|
562
562
|
});
|
|
563
|
+
|
|
564
|
+
// src/core/identity.ts
|
|
565
|
+
var identity_exports = {};
|
|
566
|
+
__export(identity_exports, {
|
|
567
|
+
createIdentity: () => createIdentity,
|
|
568
|
+
generateIdentityId: () => generateIdentityId,
|
|
569
|
+
generateKeypair: () => generateKeypair,
|
|
570
|
+
publicKeyToDid: () => publicKeyToDid,
|
|
571
|
+
rotateKeys: () => rotateKeys,
|
|
572
|
+
sign: () => sign,
|
|
573
|
+
verify: () => verify
|
|
574
|
+
});
|
|
563
575
|
function generateKeypair() {
|
|
564
576
|
const privateKey = randomBytes(32);
|
|
565
577
|
const publicKey = ed25519.ed25519.getPublicKey(privateKey);
|
|
@@ -1361,9 +1373,13 @@ var init_tools = __esm({
|
|
|
1361
1373
|
get encryptionKey() {
|
|
1362
1374
|
return derivePurposeKey(this.masterKey, "identity-encryption");
|
|
1363
1375
|
}
|
|
1364
|
-
/** Load identities from storage on startup
|
|
1376
|
+
/** Load identities from storage on startup.
|
|
1377
|
+
* Returns { total: number of encrypted files found, loaded: number successfully decrypted }.
|
|
1378
|
+
* A mismatch (total > 0, loaded === 0) indicates a wrong master key / missing passphrase.
|
|
1379
|
+
*/
|
|
1365
1380
|
async load() {
|
|
1366
1381
|
const entries = await this.storage.list("_identities");
|
|
1382
|
+
let failed = 0;
|
|
1367
1383
|
for (const entry of entries) {
|
|
1368
1384
|
const raw = await this.storage.read("_identities", entry.key);
|
|
1369
1385
|
if (!raw) continue;
|
|
@@ -1376,8 +1392,10 @@ var init_tools = __esm({
|
|
|
1376
1392
|
this.primaryIdentityId = identity.identity_id;
|
|
1377
1393
|
}
|
|
1378
1394
|
} catch {
|
|
1395
|
+
failed++;
|
|
1379
1396
|
}
|
|
1380
1397
|
}
|
|
1398
|
+
return { total: entries.length, loaded: this.identities.size, failed };
|
|
1381
1399
|
}
|
|
1382
1400
|
/** Save an identity to storage */
|
|
1383
1401
|
async save(identity) {
|
|
@@ -1620,6 +1638,7 @@ tier1_always_approve:
|
|
|
1620
1638
|
- reputation_import
|
|
1621
1639
|
- reputation_export
|
|
1622
1640
|
- bootstrap_provide_guarantee
|
|
1641
|
+
- reputation_publish
|
|
1623
1642
|
|
|
1624
1643
|
# \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
|
|
1625
1644
|
# Triggers approval when agent behavior deviates from its baseline.
|
|
@@ -1682,6 +1701,7 @@ tier3_always_allow:
|
|
|
1682
1701
|
- bridge_commit
|
|
1683
1702
|
- bridge_verify
|
|
1684
1703
|
- bridge_attest
|
|
1704
|
+
- dashboard_open
|
|
1685
1705
|
|
|
1686
1706
|
# \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
|
|
1687
1707
|
# How Sanctuary reaches you when approval is needed.
|
|
@@ -1734,7 +1754,9 @@ var init_loader = __esm({
|
|
|
1734
1754
|
"reputation_import",
|
|
1735
1755
|
"reputation_export",
|
|
1736
1756
|
"bootstrap_provide_guarantee",
|
|
1737
|
-
"decommission_certificate"
|
|
1757
|
+
"decommission_certificate",
|
|
1758
|
+
"reputation_publish"
|
|
1759
|
+
// SEC-039: Explicit Tier 1 — sends data to external API
|
|
1738
1760
|
],
|
|
1739
1761
|
tier2_anomaly: DEFAULT_TIER2,
|
|
1740
1762
|
tier3_always_allow: [
|
|
@@ -1786,7 +1808,9 @@ var init_loader = __esm({
|
|
|
1786
1808
|
"shr_gateway_export",
|
|
1787
1809
|
"bridge_commit",
|
|
1788
1810
|
"bridge_verify",
|
|
1789
|
-
"bridge_attest"
|
|
1811
|
+
"bridge_attest",
|
|
1812
|
+
"dashboard_open"
|
|
1813
|
+
// SEC-039: Explicit Tier 3 — only generates a URL
|
|
1790
1814
|
],
|
|
1791
1815
|
approval_channel: DEFAULT_CHANNEL
|
|
1792
1816
|
};
|
|
@@ -3362,7 +3386,9 @@ function generateDashboardHTML(options) {
|
|
|
3362
3386
|
|
|
3363
3387
|
<script>
|
|
3364
3388
|
// Constants
|
|
3365
|
-
|
|
3389
|
+
// SEC-038: Do NOT embed the long-lived auth token in page source.
|
|
3390
|
+
// Use only the session token stored in sessionStorage by the login flow.
|
|
3391
|
+
const AUTH_TOKEN = sessionStorage.getItem('authToken') || '';
|
|
3366
3392
|
const TIMEOUT_SECONDS = ${options.timeoutSeconds};
|
|
3367
3393
|
const API_BASE = '';
|
|
3368
3394
|
|
|
@@ -4924,7 +4950,7 @@ async function startStandaloneDashboard(options = {}) {
|
|
|
4924
4950
|
// Default to auto-open in standalone mode
|
|
4925
4951
|
});
|
|
4926
4952
|
const identityManager = new IdentityManager(storage, masterKey);
|
|
4927
|
-
await identityManager.load();
|
|
4953
|
+
const loadResult = await identityManager.load();
|
|
4928
4954
|
const shrOpts = { config, identityManager, masterKey };
|
|
4929
4955
|
const handshakeResults = /* @__PURE__ */ new Map();
|
|
4930
4956
|
dashboard.setDependencies({
|
|
@@ -4940,9 +4966,31 @@ async function startStandaloneDashboard(options = {}) {
|
|
|
4940
4966
|
await dashboard.start();
|
|
4941
4967
|
console.error(`Sanctuary Dashboard v${SANCTUARY_VERSION} (standalone mode)`);
|
|
4942
4968
|
console.error(`Storage: ${config.storage_path}`);
|
|
4943
|
-
|
|
4944
|
-
console.error(`Identities loaded: ${identityCount}`);
|
|
4969
|
+
console.error(`Identities loaded: ${loadResult.loaded}`);
|
|
4945
4970
|
console.error(`Listening: http://${dashboardHost}:${dashboardPort}`);
|
|
4971
|
+
if (loadResult.total > 0 && loadResult.loaded === 0) {
|
|
4972
|
+
console.error(
|
|
4973
|
+
`
|
|
4974
|
+
\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
|
|
4975
|
+
\u2551 \u26A0 WARNING: Encrypted identities found but NONE loaded \u2551
|
|
4976
|
+
\u2560\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\u2563
|
|
4977
|
+
\u2551 ${loadResult.total} encrypted identity file(s) found on disk \u2551
|
|
4978
|
+
\u2551 0 could be decrypted with the current master key \u2551
|
|
4979
|
+
\u2551 \u2551
|
|
4980
|
+
\u2551 This usually means SANCTUARY_PASSPHRASE is missing or \u2551
|
|
4981
|
+
\u2551 incorrect. The dashboard will show empty panels. \u2551
|
|
4982
|
+
\u2551 \u2551
|
|
4983
|
+
\u2551 To fix: restart with the correct SANCTUARY_PASSPHRASE: \u2551
|
|
4984
|
+
\u2551 SANCTUARY_PASSPHRASE=<your-passphrase> npx \\ \u2551
|
|
4985
|
+
\u2551 @sanctuary-framework/mcp-server dashboard \u2551
|
|
4986
|
+
\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
|
|
4987
|
+
`
|
|
4988
|
+
);
|
|
4989
|
+
} else if (loadResult.failed > 0) {
|
|
4990
|
+
console.error(
|
|
4991
|
+
`Warning: ${loadResult.failed} of ${loadResult.total} identity files could not be decrypted (possibly corrupted).`
|
|
4992
|
+
);
|
|
4993
|
+
}
|
|
4946
4994
|
const saveBaseline = () => {
|
|
4947
4995
|
baseline.save().catch(() => {
|
|
4948
4996
|
});
|
|
@@ -7151,6 +7199,24 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
7151
7199
|
}
|
|
7152
7200
|
const publishType = args.type;
|
|
7153
7201
|
const veracoreUrl = args.verascore_url || "https://verascore.ai";
|
|
7202
|
+
const ALLOWED_VERASCORE_HOSTS = ["verascore.ai", "www.verascore.ai", "api.verascore.ai"];
|
|
7203
|
+
try {
|
|
7204
|
+
const parsed = new URL(veracoreUrl);
|
|
7205
|
+
if (parsed.protocol !== "https:") {
|
|
7206
|
+
return toolResult({
|
|
7207
|
+
error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
|
|
7208
|
+
});
|
|
7209
|
+
}
|
|
7210
|
+
if (!ALLOWED_VERASCORE_HOSTS.includes(parsed.hostname)) {
|
|
7211
|
+
return toolResult({
|
|
7212
|
+
error: `verascore_url must point to a known Verascore domain (${ALLOWED_VERASCORE_HOSTS.join(", ")}). Got: ${parsed.hostname}`
|
|
7213
|
+
});
|
|
7214
|
+
}
|
|
7215
|
+
} catch {
|
|
7216
|
+
return toolResult({
|
|
7217
|
+
error: `verascore_url is not a valid URL: ${veracoreUrl}`
|
|
7218
|
+
});
|
|
7219
|
+
}
|
|
7154
7220
|
const agentId = args.verascore_agent_id || identity.did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
7155
7221
|
let publishData;
|
|
7156
7222
|
if (args.data) {
|
|
@@ -7180,24 +7246,21 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
7180
7246
|
return toolResult({ error: `Unknown publish type: ${publishType}` });
|
|
7181
7247
|
}
|
|
7182
7248
|
}
|
|
7183
|
-
const { sign:
|
|
7184
|
-
const payloadBytes =
|
|
7249
|
+
const { sign: identitySign } = await Promise.resolve().then(() => (init_identity(), identity_exports));
|
|
7250
|
+
const payloadBytes = new TextEncoder().encode(JSON.stringify(publishData));
|
|
7185
7251
|
let signatureB64;
|
|
7186
7252
|
try {
|
|
7187
|
-
const
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
]),
|
|
7194
|
-
format: "der",
|
|
7195
|
-
type: "pkcs8"
|
|
7196
|
-
});
|
|
7197
|
-
const sig = sign2(null, payloadBytes, privateKey);
|
|
7198
|
-
signatureB64 = sig.toString("base64url");
|
|
7253
|
+
const signingBytes = identitySign(
|
|
7254
|
+
payloadBytes,
|
|
7255
|
+
identity.encrypted_private_key,
|
|
7256
|
+
identityEncryptionKey
|
|
7257
|
+
);
|
|
7258
|
+
signatureB64 = toBase64url(signingBytes);
|
|
7199
7259
|
} catch (signError) {
|
|
7200
|
-
|
|
7260
|
+
return toolResult({
|
|
7261
|
+
error: "Failed to sign publish payload. Identity key may be corrupted.",
|
|
7262
|
+
details: signError instanceof Error ? signError.message : String(signError)
|
|
7263
|
+
});
|
|
7201
7264
|
}
|
|
7202
7265
|
const requestBody = {
|
|
7203
7266
|
agentId,
|
|
@@ -13019,7 +13082,29 @@ async function createSanctuaryServer(options) {
|
|
|
13019
13082
|
keyProtection,
|
|
13020
13083
|
auditLog
|
|
13021
13084
|
);
|
|
13022
|
-
await identityManager.load();
|
|
13085
|
+
const loadResult = await identityManager.load();
|
|
13086
|
+
if (loadResult.total > 0 && loadResult.loaded === 0) {
|
|
13087
|
+
console.error(
|
|
13088
|
+
`
|
|
13089
|
+
\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
|
|
13090
|
+
\u2551 \u26A0 WARNING: Encrypted identities found but NONE loaded \u2551
|
|
13091
|
+
\u2560\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\u2563
|
|
13092
|
+
\u2551 ${loadResult.total} encrypted identity file(s) found on disk \u2551
|
|
13093
|
+
\u2551 0 could be decrypted with the current master key \u2551
|
|
13094
|
+
\u2551 \u2551
|
|
13095
|
+
\u2551 This usually means SANCTUARY_PASSPHRASE is missing or \u2551
|
|
13096
|
+
\u2551 incorrect. The server will start but with NO identity data. \u2551
|
|
13097
|
+
\u2551 \u2551
|
|
13098
|
+
\u2551 To fix: set SANCTUARY_PASSPHRASE to the passphrase used \u2551
|
|
13099
|
+
\u2551 when this Sanctuary instance was first configured. \u2551
|
|
13100
|
+
\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
|
|
13101
|
+
`
|
|
13102
|
+
);
|
|
13103
|
+
} else if (loadResult.failed > 0) {
|
|
13104
|
+
console.error(
|
|
13105
|
+
`Warning: ${loadResult.failed} of ${loadResult.total} identity files could not be decrypted (possibly corrupted).`
|
|
13106
|
+
);
|
|
13107
|
+
}
|
|
13023
13108
|
const l2Tools = [
|
|
13024
13109
|
{
|
|
13025
13110
|
name: "sanctuary/exec_attest",
|