@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.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
- const AUTH_TOKEN = '${options.authToken || ""}' || sessionStorage.getItem('authToken') || '';
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
- const identityCount = identityManager.list().length;
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: sign2, createPrivateKey } = await import('crypto');
7181
- const payloadBytes = Buffer.from(JSON.stringify(publishData), "utf-8");
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 signingKey = derivePurposeKey(masterKey, "verascore-publish");
7185
- const privateKey = createPrivateKey({
7186
- key: Buffer.concat([
7187
- Buffer.from("302e020100300506032b657004220420", "hex"),
7188
- // Ed25519 DER PKCS8 prefix
7189
- Buffer.from(signingKey.slice(0, 32))
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
- signatureB64 = toBase64url(new Uint8Array(64));
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",