@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.js
CHANGED
|
@@ -557,6 +557,18 @@ var init_hashing = __esm({
|
|
|
557
557
|
init_encoding();
|
|
558
558
|
}
|
|
559
559
|
});
|
|
560
|
+
|
|
561
|
+
// src/core/identity.ts
|
|
562
|
+
var identity_exports = {};
|
|
563
|
+
__export(identity_exports, {
|
|
564
|
+
createIdentity: () => createIdentity,
|
|
565
|
+
generateIdentityId: () => generateIdentityId,
|
|
566
|
+
generateKeypair: () => generateKeypair,
|
|
567
|
+
publicKeyToDid: () => publicKeyToDid,
|
|
568
|
+
rotateKeys: () => rotateKeys,
|
|
569
|
+
sign: () => sign,
|
|
570
|
+
verify: () => verify
|
|
571
|
+
});
|
|
560
572
|
function generateKeypair() {
|
|
561
573
|
const privateKey = randomBytes(32);
|
|
562
574
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
@@ -1358,9 +1370,13 @@ var init_tools = __esm({
|
|
|
1358
1370
|
get encryptionKey() {
|
|
1359
1371
|
return derivePurposeKey(this.masterKey, "identity-encryption");
|
|
1360
1372
|
}
|
|
1361
|
-
/** Load identities from storage on startup
|
|
1373
|
+
/** Load identities from storage on startup.
|
|
1374
|
+
* Returns { total: number of encrypted files found, loaded: number successfully decrypted }.
|
|
1375
|
+
* A mismatch (total > 0, loaded === 0) indicates a wrong master key / missing passphrase.
|
|
1376
|
+
*/
|
|
1362
1377
|
async load() {
|
|
1363
1378
|
const entries = await this.storage.list("_identities");
|
|
1379
|
+
let failed = 0;
|
|
1364
1380
|
for (const entry of entries) {
|
|
1365
1381
|
const raw = await this.storage.read("_identities", entry.key);
|
|
1366
1382
|
if (!raw) continue;
|
|
@@ -1373,8 +1389,10 @@ var init_tools = __esm({
|
|
|
1373
1389
|
this.primaryIdentityId = identity.identity_id;
|
|
1374
1390
|
}
|
|
1375
1391
|
} catch {
|
|
1392
|
+
failed++;
|
|
1376
1393
|
}
|
|
1377
1394
|
}
|
|
1395
|
+
return { total: entries.length, loaded: this.identities.size, failed };
|
|
1378
1396
|
}
|
|
1379
1397
|
/** Save an identity to storage */
|
|
1380
1398
|
async save(identity) {
|
|
@@ -1617,6 +1635,7 @@ tier1_always_approve:
|
|
|
1617
1635
|
- reputation_import
|
|
1618
1636
|
- reputation_export
|
|
1619
1637
|
- bootstrap_provide_guarantee
|
|
1638
|
+
- reputation_publish
|
|
1620
1639
|
|
|
1621
1640
|
# \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
|
|
1622
1641
|
# Triggers approval when agent behavior deviates from its baseline.
|
|
@@ -1679,6 +1698,7 @@ tier3_always_allow:
|
|
|
1679
1698
|
- bridge_commit
|
|
1680
1699
|
- bridge_verify
|
|
1681
1700
|
- bridge_attest
|
|
1701
|
+
- dashboard_open
|
|
1682
1702
|
|
|
1683
1703
|
# \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
|
|
1684
1704
|
# How Sanctuary reaches you when approval is needed.
|
|
@@ -1731,7 +1751,9 @@ var init_loader = __esm({
|
|
|
1731
1751
|
"reputation_import",
|
|
1732
1752
|
"reputation_export",
|
|
1733
1753
|
"bootstrap_provide_guarantee",
|
|
1734
|
-
"decommission_certificate"
|
|
1754
|
+
"decommission_certificate",
|
|
1755
|
+
"reputation_publish"
|
|
1756
|
+
// SEC-039: Explicit Tier 1 — sends data to external API
|
|
1735
1757
|
],
|
|
1736
1758
|
tier2_anomaly: DEFAULT_TIER2,
|
|
1737
1759
|
tier3_always_allow: [
|
|
@@ -1783,7 +1805,9 @@ var init_loader = __esm({
|
|
|
1783
1805
|
"shr_gateway_export",
|
|
1784
1806
|
"bridge_commit",
|
|
1785
1807
|
"bridge_verify",
|
|
1786
|
-
"bridge_attest"
|
|
1808
|
+
"bridge_attest",
|
|
1809
|
+
"dashboard_open"
|
|
1810
|
+
// SEC-039: Explicit Tier 3 — only generates a URL
|
|
1787
1811
|
],
|
|
1788
1812
|
approval_channel: DEFAULT_CHANNEL
|
|
1789
1813
|
};
|
|
@@ -3359,7 +3383,9 @@ function generateDashboardHTML(options) {
|
|
|
3359
3383
|
|
|
3360
3384
|
<script>
|
|
3361
3385
|
// Constants
|
|
3362
|
-
|
|
3386
|
+
// SEC-038: Do NOT embed the long-lived auth token in page source.
|
|
3387
|
+
// Use only the session token stored in sessionStorage by the login flow.
|
|
3388
|
+
const AUTH_TOKEN = sessionStorage.getItem('authToken') || '';
|
|
3363
3389
|
const TIMEOUT_SECONDS = ${options.timeoutSeconds};
|
|
3364
3390
|
const API_BASE = '';
|
|
3365
3391
|
|
|
@@ -4921,7 +4947,7 @@ async function startStandaloneDashboard(options = {}) {
|
|
|
4921
4947
|
// Default to auto-open in standalone mode
|
|
4922
4948
|
});
|
|
4923
4949
|
const identityManager = new IdentityManager(storage, masterKey);
|
|
4924
|
-
await identityManager.load();
|
|
4950
|
+
const loadResult = await identityManager.load();
|
|
4925
4951
|
const shrOpts = { config, identityManager, masterKey };
|
|
4926
4952
|
const handshakeResults = /* @__PURE__ */ new Map();
|
|
4927
4953
|
dashboard.setDependencies({
|
|
@@ -4937,9 +4963,31 @@ async function startStandaloneDashboard(options = {}) {
|
|
|
4937
4963
|
await dashboard.start();
|
|
4938
4964
|
console.error(`Sanctuary Dashboard v${SANCTUARY_VERSION} (standalone mode)`);
|
|
4939
4965
|
console.error(`Storage: ${config.storage_path}`);
|
|
4940
|
-
|
|
4941
|
-
console.error(`Identities loaded: ${identityCount}`);
|
|
4966
|
+
console.error(`Identities loaded: ${loadResult.loaded}`);
|
|
4942
4967
|
console.error(`Listening: http://${dashboardHost}:${dashboardPort}`);
|
|
4968
|
+
if (loadResult.total > 0 && loadResult.loaded === 0) {
|
|
4969
|
+
console.error(
|
|
4970
|
+
`
|
|
4971
|
+
\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
|
|
4972
|
+
\u2551 \u26A0 WARNING: Encrypted identities found but NONE loaded \u2551
|
|
4973
|
+
\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
|
|
4974
|
+
\u2551 ${loadResult.total} encrypted identity file(s) found on disk \u2551
|
|
4975
|
+
\u2551 0 could be decrypted with the current master key \u2551
|
|
4976
|
+
\u2551 \u2551
|
|
4977
|
+
\u2551 This usually means SANCTUARY_PASSPHRASE is missing or \u2551
|
|
4978
|
+
\u2551 incorrect. The dashboard will show empty panels. \u2551
|
|
4979
|
+
\u2551 \u2551
|
|
4980
|
+
\u2551 To fix: restart with the correct SANCTUARY_PASSPHRASE: \u2551
|
|
4981
|
+
\u2551 SANCTUARY_PASSPHRASE=<your-passphrase> npx \\ \u2551
|
|
4982
|
+
\u2551 @sanctuary-framework/mcp-server dashboard \u2551
|
|
4983
|
+
\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
|
|
4984
|
+
`
|
|
4985
|
+
);
|
|
4986
|
+
} else if (loadResult.failed > 0) {
|
|
4987
|
+
console.error(
|
|
4988
|
+
`Warning: ${loadResult.failed} of ${loadResult.total} identity files could not be decrypted (possibly corrupted).`
|
|
4989
|
+
);
|
|
4990
|
+
}
|
|
4943
4991
|
const saveBaseline = () => {
|
|
4944
4992
|
baseline.save().catch(() => {
|
|
4945
4993
|
});
|
|
@@ -7148,6 +7196,24 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
7148
7196
|
}
|
|
7149
7197
|
const publishType = args.type;
|
|
7150
7198
|
const veracoreUrl = args.verascore_url || "https://verascore.ai";
|
|
7199
|
+
const ALLOWED_VERASCORE_HOSTS = ["verascore.ai", "www.verascore.ai", "api.verascore.ai"];
|
|
7200
|
+
try {
|
|
7201
|
+
const parsed = new URL(veracoreUrl);
|
|
7202
|
+
if (parsed.protocol !== "https:") {
|
|
7203
|
+
return toolResult({
|
|
7204
|
+
error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
|
|
7205
|
+
});
|
|
7206
|
+
}
|
|
7207
|
+
if (!ALLOWED_VERASCORE_HOSTS.includes(parsed.hostname)) {
|
|
7208
|
+
return toolResult({
|
|
7209
|
+
error: `verascore_url must point to a known Verascore domain (${ALLOWED_VERASCORE_HOSTS.join(", ")}). Got: ${parsed.hostname}`
|
|
7210
|
+
});
|
|
7211
|
+
}
|
|
7212
|
+
} catch {
|
|
7213
|
+
return toolResult({
|
|
7214
|
+
error: `verascore_url is not a valid URL: ${veracoreUrl}`
|
|
7215
|
+
});
|
|
7216
|
+
}
|
|
7151
7217
|
const agentId = args.verascore_agent_id || identity.did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
7152
7218
|
let publishData;
|
|
7153
7219
|
if (args.data) {
|
|
@@ -7177,24 +7243,21 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
|
|
|
7177
7243
|
return toolResult({ error: `Unknown publish type: ${publishType}` });
|
|
7178
7244
|
}
|
|
7179
7245
|
}
|
|
7180
|
-
const { sign:
|
|
7181
|
-
const payloadBytes =
|
|
7246
|
+
const { sign: identitySign } = await Promise.resolve().then(() => (init_identity(), identity_exports));
|
|
7247
|
+
const payloadBytes = new TextEncoder().encode(JSON.stringify(publishData));
|
|
7182
7248
|
let signatureB64;
|
|
7183
7249
|
try {
|
|
7184
|
-
const
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
]),
|
|
7191
|
-
format: "der",
|
|
7192
|
-
type: "pkcs8"
|
|
7193
|
-
});
|
|
7194
|
-
const sig = sign2(null, payloadBytes, privateKey);
|
|
7195
|
-
signatureB64 = sig.toString("base64url");
|
|
7250
|
+
const signingBytes = identitySign(
|
|
7251
|
+
payloadBytes,
|
|
7252
|
+
identity.encrypted_private_key,
|
|
7253
|
+
identityEncryptionKey
|
|
7254
|
+
);
|
|
7255
|
+
signatureB64 = toBase64url(signingBytes);
|
|
7196
7256
|
} catch (signError) {
|
|
7197
|
-
|
|
7257
|
+
return toolResult({
|
|
7258
|
+
error: "Failed to sign publish payload. Identity key may be corrupted.",
|
|
7259
|
+
details: signError instanceof Error ? signError.message : String(signError)
|
|
7260
|
+
});
|
|
7198
7261
|
}
|
|
7199
7262
|
const requestBody = {
|
|
7200
7263
|
agentId,
|
|
@@ -13016,7 +13079,29 @@ async function createSanctuaryServer(options) {
|
|
|
13016
13079
|
keyProtection,
|
|
13017
13080
|
auditLog
|
|
13018
13081
|
);
|
|
13019
|
-
await identityManager.load();
|
|
13082
|
+
const loadResult = await identityManager.load();
|
|
13083
|
+
if (loadResult.total > 0 && loadResult.loaded === 0) {
|
|
13084
|
+
console.error(
|
|
13085
|
+
`
|
|
13086
|
+
\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
|
|
13087
|
+
\u2551 \u26A0 WARNING: Encrypted identities found but NONE loaded \u2551
|
|
13088
|
+
\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
|
|
13089
|
+
\u2551 ${loadResult.total} encrypted identity file(s) found on disk \u2551
|
|
13090
|
+
\u2551 0 could be decrypted with the current master key \u2551
|
|
13091
|
+
\u2551 \u2551
|
|
13092
|
+
\u2551 This usually means SANCTUARY_PASSPHRASE is missing or \u2551
|
|
13093
|
+
\u2551 incorrect. The server will start but with NO identity data. \u2551
|
|
13094
|
+
\u2551 \u2551
|
|
13095
|
+
\u2551 To fix: set SANCTUARY_PASSPHRASE to the passphrase used \u2551
|
|
13096
|
+
\u2551 when this Sanctuary instance was first configured. \u2551
|
|
13097
|
+
\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
|
|
13098
|
+
`
|
|
13099
|
+
);
|
|
13100
|
+
} else if (loadResult.failed > 0) {
|
|
13101
|
+
console.error(
|
|
13102
|
+
`Warning: ${loadResult.failed} of ${loadResult.total} identity files could not be decrypted (possibly corrupted).`
|
|
13103
|
+
);
|
|
13104
|
+
}
|
|
13020
13105
|
const l2Tools = [
|
|
13021
13106
|
{
|
|
13022
13107
|
name: "sanctuary/exec_attest",
|