@sanctuary-framework/mcp-server 0.5.12 → 0.5.14

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 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);
@@ -1347,7 +1359,9 @@ var init_tools = __esm({
1347
1359
  "_bridge",
1348
1360
  "_federation",
1349
1361
  "_handshake",
1350
- "_shr"
1362
+ "_shr",
1363
+ "_sovereignty_profile",
1364
+ "_context_gate_policies"
1351
1365
  ];
1352
1366
  IdentityManager = class {
1353
1367
  storage;
@@ -1626,6 +1640,8 @@ tier1_always_approve:
1626
1640
  - reputation_import
1627
1641
  - reputation_export
1628
1642
  - bootstrap_provide_guarantee
1643
+ - reputation_publish
1644
+ - sovereignty_profile_update
1629
1645
 
1630
1646
  # \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
1631
1647
  # Triggers approval when agent behavior deviates from its baseline.
@@ -1688,6 +1704,9 @@ tier3_always_allow:
1688
1704
  - bridge_commit
1689
1705
  - bridge_verify
1690
1706
  - bridge_attest
1707
+ - dashboard_open
1708
+ - sovereignty_profile_get
1709
+ - sovereignty_profile_generate_prompt
1691
1710
 
1692
1711
  # \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
1693
1712
  # How Sanctuary reaches you when approval is needed.
@@ -1740,7 +1759,11 @@ var init_loader = __esm({
1740
1759
  "reputation_import",
1741
1760
  "reputation_export",
1742
1761
  "bootstrap_provide_guarantee",
1743
- "decommission_certificate"
1762
+ "decommission_certificate",
1763
+ "reputation_publish",
1764
+ // SEC-039: Explicit Tier 1 — sends data to external API
1765
+ "sovereignty_profile_update"
1766
+ // Changes enforcement behavior — always requires approval
1744
1767
  ],
1745
1768
  tier2_anomaly: DEFAULT_TIER2,
1746
1769
  tier3_always_allow: [
@@ -1792,7 +1815,11 @@ var init_loader = __esm({
1792
1815
  "shr_gateway_export",
1793
1816
  "bridge_commit",
1794
1817
  "bridge_verify",
1795
- "bridge_attest"
1818
+ "bridge_attest",
1819
+ "dashboard_open",
1820
+ // SEC-039: Explicit Tier 3 — only generates a URL
1821
+ "sovereignty_profile_get",
1822
+ "sovereignty_profile_generate_prompt"
1796
1823
  ],
1797
1824
  approval_channel: DEFAULT_CHANNEL
1798
1825
  };
@@ -3009,6 +3036,133 @@ function generateDashboardHTML(options) {
3009
3036
  background-color: #e03c3c;
3010
3037
  }
3011
3038
 
3039
+ /* Sovereignty Profile Panel */
3040
+ .profile-panel {
3041
+ background-color: var(--surface);
3042
+ border: 1px solid var(--border);
3043
+ border-radius: 8px;
3044
+ padding: 20px;
3045
+ }
3046
+
3047
+ .profile-panel .panel-header {
3048
+ display: flex;
3049
+ justify-content: space-between;
3050
+ align-items: center;
3051
+ margin-bottom: 16px;
3052
+ }
3053
+
3054
+ .profile-panel .panel-title {
3055
+ font-size: 14px;
3056
+ font-weight: 600;
3057
+ color: var(--text-primary);
3058
+ }
3059
+
3060
+ .profile-cards {
3061
+ display: grid;
3062
+ grid-template-columns: repeat(5, 1fr);
3063
+ gap: 12px;
3064
+ margin-bottom: 16px;
3065
+ }
3066
+
3067
+ .profile-card {
3068
+ background-color: var(--bg);
3069
+ border: 1px solid var(--border);
3070
+ border-radius: 6px;
3071
+ padding: 14px;
3072
+ display: flex;
3073
+ flex-direction: column;
3074
+ gap: 8px;
3075
+ }
3076
+
3077
+ .profile-card-name {
3078
+ font-size: 12px;
3079
+ font-weight: 600;
3080
+ color: var(--text-primary);
3081
+ }
3082
+
3083
+ .profile-card-desc {
3084
+ font-size: 11px;
3085
+ color: var(--text-secondary);
3086
+ line-height: 1.4;
3087
+ }
3088
+
3089
+ .profile-badge {
3090
+ display: inline-flex;
3091
+ align-items: center;
3092
+ gap: 4px;
3093
+ padding: 2px 8px;
3094
+ border-radius: 4px;
3095
+ font-size: 10px;
3096
+ font-weight: 600;
3097
+ width: fit-content;
3098
+ }
3099
+
3100
+ .profile-badge.enabled {
3101
+ background-color: rgba(63, 185, 80, 0.15);
3102
+ color: var(--green);
3103
+ }
3104
+
3105
+ .profile-badge.disabled {
3106
+ background-color: rgba(139, 148, 158, 0.15);
3107
+ color: var(--text-secondary);
3108
+ }
3109
+
3110
+ .prompt-section {
3111
+ margin-top: 12px;
3112
+ }
3113
+
3114
+ .prompt-textarea {
3115
+ width: 100%;
3116
+ min-height: 120px;
3117
+ background-color: var(--bg);
3118
+ border: 1px solid var(--border);
3119
+ border-radius: 6px;
3120
+ color: var(--text-primary);
3121
+ font-family: 'JetBrains Mono', monospace;
3122
+ font-size: 12px;
3123
+ padding: 12px;
3124
+ resize: vertical;
3125
+ margin-top: 8px;
3126
+ }
3127
+
3128
+ .prompt-actions {
3129
+ display: flex;
3130
+ gap: 8px;
3131
+ margin-top: 8px;
3132
+ }
3133
+
3134
+ .prompt-btn {
3135
+ padding: 6px 12px;
3136
+ border: 1px solid var(--border);
3137
+ border-radius: 4px;
3138
+ background-color: var(--surface);
3139
+ color: var(--text-primary);
3140
+ font-size: 12px;
3141
+ cursor: pointer;
3142
+ }
3143
+
3144
+ .prompt-btn:hover {
3145
+ background-color: var(--muted);
3146
+ }
3147
+
3148
+ .prompt-btn.primary {
3149
+ background-color: var(--blue);
3150
+ color: var(--bg);
3151
+ border-color: var(--blue);
3152
+ }
3153
+
3154
+ @media (max-width: 900px) {
3155
+ .profile-cards {
3156
+ grid-template-columns: repeat(2, 1fr);
3157
+ }
3158
+ }
3159
+
3160
+ @media (max-width: 500px) {
3161
+ .profile-cards {
3162
+ grid-template-columns: 1fr;
3163
+ }
3164
+ }
3165
+
3012
3166
  /* Threat Panel */
3013
3167
  .threat-panel {
3014
3168
  background-color: var(--surface);
@@ -3347,6 +3501,48 @@ function generateDashboardHTML(options) {
3347
3501
  </div>
3348
3502
  </div>
3349
3503
 
3504
+ <!-- Sovereignty Profile Panel -->
3505
+ <div class="profile-panel" id="sovereignty-profile-panel">
3506
+ <div class="panel-header">
3507
+ <div class="panel-title">Sovereignty Profile</div>
3508
+ <span class="card-value" id="profile-updated-at" style="font-size: 11px; color: var(--text-secondary);">\u2014</span>
3509
+ </div>
3510
+ <div class="profile-cards" id="profile-cards">
3511
+ <div class="profile-card" data-feature="audit_logging">
3512
+ <div class="profile-card-name">Audit Logging</div>
3513
+ <div class="profile-badge disabled" id="badge-audit_logging">OFF</div>
3514
+ <div class="profile-card-desc">Encrypted audit trail of all tool calls</div>
3515
+ </div>
3516
+ <div class="profile-card" data-feature="injection_detection">
3517
+ <div class="profile-card-name">Injection Detection</div>
3518
+ <div class="profile-badge disabled" id="badge-injection_detection">OFF</div>
3519
+ <div class="profile-card-desc">Scans tool arguments for prompt injection</div>
3520
+ </div>
3521
+ <div class="profile-card" data-feature="context_gating">
3522
+ <div class="profile-card-name">Context Gating</div>
3523
+ <div class="profile-badge disabled" id="badge-context_gating">OFF</div>
3524
+ <div class="profile-card-desc">Controls context flow to remote providers</div>
3525
+ </div>
3526
+ <div class="profile-card" data-feature="approval_gate">
3527
+ <div class="profile-card-name">Approval Gates</div>
3528
+ <div class="profile-badge disabled" id="badge-approval_gate">OFF</div>
3529
+ <div class="profile-card-desc">Human approval for high-risk operations</div>
3530
+ </div>
3531
+ <div class="profile-card" data-feature="zk_proofs">
3532
+ <div class="profile-card-name">ZK Proofs</div>
3533
+ <div class="profile-badge disabled" id="badge-zk_proofs">OFF</div>
3534
+ <div class="profile-card-desc">Prove claims without revealing data</div>
3535
+ </div>
3536
+ </div>
3537
+ <div class="prompt-section">
3538
+ <div class="prompt-actions">
3539
+ <button class="prompt-btn primary" id="generate-prompt-btn">Generate System Prompt</button>
3540
+ <button class="prompt-btn" id="copy-prompt-btn" style="display:none;">Copy</button>
3541
+ </div>
3542
+ <textarea class="prompt-textarea" id="system-prompt-output" readonly style="display:none;" placeholder="Click 'Generate System Prompt' to create an agent instruction snippet..."></textarea>
3543
+ </div>
3544
+ </div>
3545
+
3350
3546
  <!-- Threat Panel -->
3351
3547
  <div class="threat-panel collapsed">
3352
3548
  <div class="threat-header">
@@ -3368,7 +3564,9 @@ function generateDashboardHTML(options) {
3368
3564
 
3369
3565
  <script>
3370
3566
  // Constants
3371
- const AUTH_TOKEN = '${options.authToken || ""}' || sessionStorage.getItem('authToken') || '';
3567
+ // SEC-038: Do NOT embed the long-lived auth token in page source.
3568
+ // Use only the session token stored in sessionStorage by the login flow.
3569
+ const AUTH_TOKEN = sessionStorage.getItem('authToken') || '';
3372
3570
  const TIMEOUT_SECONDS = ${options.timeoutSeconds};
3373
3571
  const API_BASE = '';
3374
3572
 
@@ -3379,6 +3577,7 @@ function generateDashboardHTML(options) {
3379
3577
  handshakes: [],
3380
3578
  shr: null,
3381
3579
  status: null,
3580
+ systemPrompt: null,
3382
3581
  };
3383
3582
 
3384
3583
  let pendingRequests = new Map();
@@ -3831,6 +4030,10 @@ function generateDashboardHTML(options) {
3831
4030
  removePendingRequest(data.requestId);
3832
4031
  });
3833
4032
 
4033
+ eventSource.addEventListener('sovereignty-profile-update', () => {
4034
+ updateSovereigntyProfile();
4035
+ });
4036
+
3834
4037
  eventSource.onerror = () => {
3835
4038
  console.error('SSE error');
3836
4039
  setTimeout(setupSSE, 5000);
@@ -3987,6 +4190,58 @@ function generateDashboardHTML(options) {
3987
4190
  document.getElementById('pending-overlay').classList.toggle('show');
3988
4191
  });
3989
4192
 
4193
+ // Sovereignty Profile
4194
+ async function updateSovereigntyProfile() {
4195
+ try {
4196
+ const data = await fetchAPI('/api/sovereignty-profile');
4197
+ if (data && data.profile) {
4198
+ const features = data.profile.features;
4199
+ for (const [key, value] of Object.entries(features)) {
4200
+ const badge = document.getElementById('badge-' + key);
4201
+ if (badge) {
4202
+ const enabled = value && value.enabled;
4203
+ badge.textContent = enabled ? 'ON' : 'OFF';
4204
+ badge.className = 'profile-badge ' + (enabled ? 'enabled' : 'disabled');
4205
+ }
4206
+ }
4207
+ const updatedEl = document.getElementById('profile-updated-at');
4208
+ if (updatedEl && data.profile.updated_at) {
4209
+ updatedEl.textContent = 'Updated: ' + new Date(data.profile.updated_at).toLocaleString();
4210
+ }
4211
+ // Cache the prompt
4212
+ if (data.system_prompt) {
4213
+ apiState.systemPrompt = data.system_prompt;
4214
+ }
4215
+ }
4216
+ } catch (e) {
4217
+ // Profile not available
4218
+ }
4219
+ }
4220
+
4221
+ document.getElementById('generate-prompt-btn').addEventListener('click', async () => {
4222
+ const data = await fetchAPI('/api/sovereignty-profile');
4223
+ if (data && data.system_prompt) {
4224
+ const textarea = document.getElementById('system-prompt-output');
4225
+ const copyBtn = document.getElementById('copy-prompt-btn');
4226
+ textarea.value = data.system_prompt;
4227
+ textarea.style.display = 'block';
4228
+ copyBtn.style.display = 'inline-flex';
4229
+ }
4230
+ });
4231
+
4232
+ document.getElementById('copy-prompt-btn').addEventListener('click', async () => {
4233
+ const textarea = document.getElementById('system-prompt-output');
4234
+ try {
4235
+ await navigator.clipboard.writeText(textarea.value);
4236
+ const btn = document.getElementById('copy-prompt-btn');
4237
+ const original = btn.textContent;
4238
+ btn.textContent = 'Copied!';
4239
+ setTimeout(() => { btn.textContent = original; }, 2000);
4240
+ } catch (err) {
4241
+ console.error('Copy failed:', err);
4242
+ }
4243
+ });
4244
+
3990
4245
  // Initialize
3991
4246
  async function initialize() {
3992
4247
  if (!AUTH_TOKEN) {
@@ -4001,6 +4256,7 @@ function generateDashboardHTML(options) {
4001
4256
  updateHandshakes(),
4002
4257
  updateSHR(),
4003
4258
  updateStatus(),
4259
+ updateSovereigntyProfile(),
4004
4260
  ]);
4005
4261
 
4006
4262
  // Setup SSE for real-time updates
@@ -4020,12 +4276,92 @@ var init_dashboard_html = __esm({
4020
4276
  "src/principal-policy/dashboard-html.ts"() {
4021
4277
  }
4022
4278
  });
4279
+
4280
+ // src/system-prompt-generator.ts
4281
+ function generateSystemPrompt(profile) {
4282
+ const activeFeatures = [];
4283
+ const inactiveFeatures = [];
4284
+ const featureKeys = [
4285
+ "audit_logging",
4286
+ "injection_detection",
4287
+ "context_gating",
4288
+ "approval_gate",
4289
+ "zk_proofs"
4290
+ ];
4291
+ for (const key of featureKeys) {
4292
+ const featureConfig = profile.features[key];
4293
+ const info = FEATURE_INFO[key];
4294
+ if (featureConfig.enabled) {
4295
+ let desc = `- ${info.name}: ${info.activeDescription}`;
4296
+ if (key === "injection_detection" && "sensitivity" in featureConfig && featureConfig.sensitivity) {
4297
+ desc += ` Sensitivity: ${featureConfig.sensitivity}.`;
4298
+ }
4299
+ if (key === "context_gating" && "policy_id" in featureConfig && featureConfig.policy_id) {
4300
+ desc += ` Active policy: ${featureConfig.policy_id}.`;
4301
+ }
4302
+ activeFeatures.push(desc);
4303
+ } else {
4304
+ inactiveFeatures.push(info.disabledDescription);
4305
+ }
4306
+ }
4307
+ const lines = [
4308
+ "You are protected by Sanctuary sovereignty infrastructure. The following protections are active:",
4309
+ ""
4310
+ ];
4311
+ if (activeFeatures.length > 0) {
4312
+ lines.push(...activeFeatures);
4313
+ } else {
4314
+ lines.push("- No features are currently enabled. Contact your operator to configure protections.");
4315
+ }
4316
+ if (inactiveFeatures.length > 0) {
4317
+ lines.push("");
4318
+ lines.push(
4319
+ `Optional tools available but not currently enabled: ${inactiveFeatures.join(", ")}.`
4320
+ );
4321
+ }
4322
+ return lines.join("\n");
4323
+ }
4324
+ var FEATURE_INFO;
4325
+ var init_system_prompt_generator = __esm({
4326
+ "src/system-prompt-generator.ts"() {
4327
+ FEATURE_INFO = {
4328
+ audit_logging: {
4329
+ name: "Audit Logging",
4330
+ activeDescription: "All your tool calls are logged to an encrypted audit trail. No action needed \u2014 this is automatic.",
4331
+ disabledDescription: "audit logging (sanctuary/monitor_audit_log)"
4332
+ },
4333
+ injection_detection: {
4334
+ name: "Injection Detection",
4335
+ activeDescription: "Your tool call arguments are scanned for prompt injection attempts. No action needed \u2014 this is automatic.",
4336
+ disabledDescription: "injection detection"
4337
+ },
4338
+ context_gating: {
4339
+ name: "Context Gating",
4340
+ activeDescription: "Before making outbound calls to remote providers, filter your context through sanctuary/context_gate_filter to ensure minimum-necessary disclosure.",
4341
+ toolNames: ["sanctuary/context_gate_filter", "sanctuary/context_gate_set_policy"],
4342
+ disabledDescription: "context gating (sanctuary/context_gate_filter)"
4343
+ },
4344
+ approval_gate: {
4345
+ name: "Approval Gates",
4346
+ activeDescription: "High-risk operations require human approval before execution. Tier 1 operations always require approval; Tier 2 operations trigger approval on anomaly detection.",
4347
+ disabledDescription: "approval gates"
4348
+ },
4349
+ zk_proofs: {
4350
+ name: "Zero-Knowledge Proofs",
4351
+ activeDescription: "You can prove claims about your data without revealing the underlying values. Use sanctuary/zk_commit to create commitments, sanctuary/zk_prove for proofs of knowledge, and sanctuary/zk_range_prove for range proofs.",
4352
+ toolNames: ["sanctuary/zk_commit", "sanctuary/zk_prove", "sanctuary/zk_range_prove"],
4353
+ disabledDescription: "zero-knowledge proofs (sanctuary/zk_commit, sanctuary/zk_prove)"
4354
+ }
4355
+ };
4356
+ }
4357
+ });
4023
4358
  var SESSION_TTL_REMOTE_MS, SESSION_TTL_LOCAL_MS, MAX_SESSIONS, RATE_LIMIT_WINDOW_MS, RATE_LIMIT_GENERAL, RATE_LIMIT_DECISIONS, MAX_RATE_LIMIT_ENTRIES, DashboardApprovalChannel;
4024
4359
  var init_dashboard = __esm({
4025
4360
  "src/principal-policy/dashboard.ts"() {
4026
4361
  init_config();
4027
4362
  init_generator();
4028
4363
  init_dashboard_html();
4364
+ init_system_prompt_generator();
4029
4365
  SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
4030
4366
  SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
4031
4367
  MAX_SESSIONS = 1e3;
@@ -4045,6 +4381,7 @@ var init_dashboard = __esm({
4045
4381
  handshakeResults = null;
4046
4382
  shrOpts = null;
4047
4383
  _sanctuaryConfig = null;
4384
+ profileStore = null;
4048
4385
  dashboardHTML;
4049
4386
  loginHTML;
4050
4387
  authToken;
@@ -4084,6 +4421,7 @@ var init_dashboard = __esm({
4084
4421
  if (deps.handshakeResults) this.handshakeResults = deps.handshakeResults;
4085
4422
  if (deps.shrOpts) this.shrOpts = deps.shrOpts;
4086
4423
  if (deps.sanctuaryConfig) this._sanctuaryConfig = deps.sanctuaryConfig;
4424
+ if (deps.profileStore) this.profileStore = deps.profileStore;
4087
4425
  }
4088
4426
  /**
4089
4427
  * Mark this dashboard as running in standalone mode.
@@ -4431,6 +4769,10 @@ var init_dashboard = __esm({
4431
4769
  this.handleHandshakes(res);
4432
4770
  } else if (method === "GET" && url.pathname === "/api/shr") {
4433
4771
  this.handleSHR(res);
4772
+ } else if (method === "GET" && url.pathname === "/api/sovereignty-profile") {
4773
+ this.handleSovereigntyProfileGet(res);
4774
+ } else if (method === "POST" && url.pathname === "/api/sovereignty-profile") {
4775
+ this.handleSovereigntyProfileUpdate(req, res);
4434
4776
  } else if (method === "POST" && url.pathname.startsWith("/api/approve/")) {
4435
4777
  if (!this.checkRateLimit(req, res, "decisions")) return;
4436
4778
  const id = url.pathname.slice("/api/approve/".length);
@@ -4722,6 +5064,61 @@ data: ${JSON.stringify(initData)}
4722
5064
  res.writeHead(200, { "Content-Type": "application/json" });
4723
5065
  res.end(JSON.stringify(shr));
4724
5066
  }
5067
+ // ── Sovereignty Profile API ─────────────────────────────────────────
5068
+ handleSovereigntyProfileGet(res) {
5069
+ if (!this.profileStore) {
5070
+ res.writeHead(200, { "Content-Type": "application/json" });
5071
+ res.end(JSON.stringify({ error: "Sovereignty Profile not available" }));
5072
+ return;
5073
+ }
5074
+ try {
5075
+ const profile = this.profileStore.get();
5076
+ const prompt = generateSystemPrompt(profile);
5077
+ res.writeHead(200, { "Content-Type": "application/json" });
5078
+ res.end(JSON.stringify({ profile, system_prompt: prompt }));
5079
+ } catch {
5080
+ res.writeHead(500, { "Content-Type": "application/json" });
5081
+ res.end(JSON.stringify({ error: "Failed to read sovereignty profile" }));
5082
+ }
5083
+ }
5084
+ handleSovereigntyProfileUpdate(req, res) {
5085
+ if (!this.profileStore) {
5086
+ res.writeHead(400, { "Content-Type": "application/json" });
5087
+ res.end(JSON.stringify({ error: "Sovereignty Profile not available" }));
5088
+ return;
5089
+ }
5090
+ let body = "";
5091
+ let destroyed = false;
5092
+ req.on("data", (chunk) => {
5093
+ body += chunk.toString();
5094
+ if (body.length > 16384) {
5095
+ destroyed = true;
5096
+ res.writeHead(413, { "Content-Type": "application/json" });
5097
+ res.end(JSON.stringify({ error: "Request body too large" }));
5098
+ req.destroy();
5099
+ }
5100
+ });
5101
+ req.on("end", async () => {
5102
+ if (destroyed) return;
5103
+ try {
5104
+ const updates = JSON.parse(body);
5105
+ const updated = await this.profileStore.update(updates);
5106
+ const prompt = generateSystemPrompt(updated);
5107
+ if (this.auditLog) {
5108
+ this.auditLog.append("l2", "sovereignty_profile_update_dashboard", "dashboard", {
5109
+ changes: updates,
5110
+ features_enabled: Object.entries(updated.features).filter(([, v]) => v.enabled).map(([k]) => k)
5111
+ });
5112
+ }
5113
+ this.broadcastSSE("sovereignty-profile-update", { profile: updated, system_prompt: prompt });
5114
+ res.writeHead(200, { "Content-Type": "application/json" });
5115
+ res.end(JSON.stringify({ profile: updated, system_prompt: prompt }));
5116
+ } catch {
5117
+ res.writeHead(400, { "Content-Type": "application/json" });
5118
+ res.end(JSON.stringify({ error: "Invalid JSON body" }));
5119
+ }
5120
+ });
5121
+ }
4725
5122
  // ── SSE Broadcasting ────────────────────────────────────────────────
4726
5123
  broadcastSSE(event, data) {
4727
5124
  const message = `event: ${event}
@@ -4824,6 +5221,148 @@ data: ${JSON.stringify(data)}
4824
5221
  }
4825
5222
  });
4826
5223
 
5224
+ // src/sovereignty-profile.ts
5225
+ function createDefaultProfile() {
5226
+ return {
5227
+ version: 1,
5228
+ features: {
5229
+ audit_logging: { enabled: true },
5230
+ injection_detection: { enabled: true },
5231
+ context_gating: { enabled: false },
5232
+ approval_gate: { enabled: false },
5233
+ zk_proofs: { enabled: false }
5234
+ },
5235
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
5236
+ };
5237
+ }
5238
+ var NAMESPACE, PROFILE_KEY, HKDF_DOMAIN, SovereigntyProfileStore;
5239
+ var init_sovereignty_profile = __esm({
5240
+ "src/sovereignty-profile.ts"() {
5241
+ init_encryption();
5242
+ init_key_derivation();
5243
+ init_encoding();
5244
+ NAMESPACE = "_sovereignty_profile";
5245
+ PROFILE_KEY = "active";
5246
+ HKDF_DOMAIN = "sovereignty-profile";
5247
+ SovereigntyProfileStore = class {
5248
+ storage;
5249
+ encryptionKey;
5250
+ profile = null;
5251
+ constructor(storage, masterKey) {
5252
+ this.storage = storage;
5253
+ this.encryptionKey = derivePurposeKey(masterKey, HKDF_DOMAIN);
5254
+ }
5255
+ /**
5256
+ * Load the active sovereignty profile from encrypted storage.
5257
+ * Creates the default profile on first run.
5258
+ */
5259
+ async load() {
5260
+ if (this.profile) return this.profile;
5261
+ const raw = await this.storage.read(NAMESPACE, PROFILE_KEY);
5262
+ if (raw) {
5263
+ try {
5264
+ const encrypted = JSON.parse(bytesToString(raw));
5265
+ const decrypted = decrypt(encrypted, this.encryptionKey);
5266
+ this.profile = JSON.parse(bytesToString(decrypted));
5267
+ return this.profile;
5268
+ } catch {
5269
+ }
5270
+ }
5271
+ this.profile = createDefaultProfile();
5272
+ await this.persist();
5273
+ return this.profile;
5274
+ }
5275
+ /**
5276
+ * Get the current profile. Must call load() first.
5277
+ */
5278
+ get() {
5279
+ if (!this.profile) {
5280
+ throw new Error("SovereigntyProfileStore: call load() before get()");
5281
+ }
5282
+ return this.profile;
5283
+ }
5284
+ /**
5285
+ * Apply a partial update to the profile.
5286
+ * Returns the updated profile.
5287
+ */
5288
+ async update(updates) {
5289
+ if (!this.profile) {
5290
+ await this.load();
5291
+ }
5292
+ const features = this.profile.features;
5293
+ if (updates.audit_logging !== void 0) {
5294
+ if (updates.audit_logging.enabled !== void 0) {
5295
+ if (typeof updates.audit_logging.enabled !== "boolean") {
5296
+ throw new Error("audit_logging.enabled must be a boolean");
5297
+ }
5298
+ features.audit_logging.enabled = updates.audit_logging.enabled;
5299
+ }
5300
+ }
5301
+ if (updates.injection_detection !== void 0) {
5302
+ if (updates.injection_detection.enabled !== void 0) {
5303
+ if (typeof updates.injection_detection.enabled !== "boolean") {
5304
+ throw new Error("injection_detection.enabled must be a boolean");
5305
+ }
5306
+ features.injection_detection.enabled = updates.injection_detection.enabled;
5307
+ }
5308
+ if (updates.injection_detection.sensitivity !== void 0) {
5309
+ const valid = ["low", "medium", "high"];
5310
+ if (!valid.includes(updates.injection_detection.sensitivity)) {
5311
+ throw new Error("injection_detection.sensitivity must be low, medium, or high");
5312
+ }
5313
+ features.injection_detection.sensitivity = updates.injection_detection.sensitivity;
5314
+ }
5315
+ }
5316
+ if (updates.context_gating !== void 0) {
5317
+ if (updates.context_gating.enabled !== void 0) {
5318
+ if (typeof updates.context_gating.enabled !== "boolean") {
5319
+ throw new Error("context_gating.enabled must be a boolean");
5320
+ }
5321
+ features.context_gating.enabled = updates.context_gating.enabled;
5322
+ }
5323
+ if (updates.context_gating.policy_id !== void 0) {
5324
+ if (typeof updates.context_gating.policy_id !== "string" || updates.context_gating.policy_id.length > 256) {
5325
+ throw new Error("context_gating.policy_id must be a string of 256 characters or fewer");
5326
+ }
5327
+ features.context_gating.policy_id = updates.context_gating.policy_id;
5328
+ }
5329
+ }
5330
+ if (updates.approval_gate !== void 0) {
5331
+ if (updates.approval_gate.enabled !== void 0) {
5332
+ if (typeof updates.approval_gate.enabled !== "boolean") {
5333
+ throw new Error("approval_gate.enabled must be a boolean");
5334
+ }
5335
+ features.approval_gate.enabled = updates.approval_gate.enabled;
5336
+ }
5337
+ }
5338
+ if (updates.zk_proofs !== void 0) {
5339
+ if (updates.zk_proofs.enabled !== void 0) {
5340
+ if (typeof updates.zk_proofs.enabled !== "boolean") {
5341
+ throw new Error("zk_proofs.enabled must be a boolean");
5342
+ }
5343
+ features.zk_proofs.enabled = updates.zk_proofs.enabled;
5344
+ }
5345
+ }
5346
+ this.profile.updated_at = (/* @__PURE__ */ new Date()).toISOString();
5347
+ await this.persist();
5348
+ return this.profile;
5349
+ }
5350
+ /**
5351
+ * Persist the current profile to encrypted storage.
5352
+ */
5353
+ async persist() {
5354
+ const serialized = stringToBytes(JSON.stringify(this.profile));
5355
+ const encrypted = encrypt(serialized, this.encryptionKey);
5356
+ await this.storage.write(
5357
+ NAMESPACE,
5358
+ PROFILE_KEY,
5359
+ stringToBytes(JSON.stringify(encrypted))
5360
+ );
5361
+ }
5362
+ };
5363
+ }
5364
+ });
5365
+
4827
5366
  // src/dashboard-standalone.ts
4828
5367
  var dashboard_standalone_exports = {};
4829
5368
  __export(dashboard_standalone_exports, {
@@ -4933,6 +5472,8 @@ async function startStandaloneDashboard(options = {}) {
4933
5472
  const loadResult = await identityManager.load();
4934
5473
  const shrOpts = { config, identityManager, masterKey };
4935
5474
  const handshakeResults = /* @__PURE__ */ new Map();
5475
+ const profileStore = new SovereigntyProfileStore(storage, masterKey);
5476
+ await profileStore.load();
4936
5477
  dashboard.setDependencies({
4937
5478
  policy,
4938
5479
  baseline,
@@ -4940,7 +5481,8 @@ async function startStandaloneDashboard(options = {}) {
4940
5481
  identityManager,
4941
5482
  handshakeResults,
4942
5483
  shrOpts,
4943
- sanctuaryConfig: config
5484
+ sanctuaryConfig: config,
5485
+ profileStore
4944
5486
  });
4945
5487
  dashboard.setStandaloneMode(true);
4946
5488
  await dashboard.start();
@@ -4991,6 +5533,7 @@ var init_dashboard_standalone = __esm({
4991
5533
  init_random();
4992
5534
  init_encoding();
4993
5535
  init_tools();
5536
+ init_sovereignty_profile();
4994
5537
  }
4995
5538
  });
4996
5539
 
@@ -5017,7 +5560,9 @@ var RESERVED_NAMESPACE_PREFIXES = [
5017
5560
  "_bridge",
5018
5561
  "_federation",
5019
5562
  "_handshake",
5020
- "_shr"
5563
+ "_shr",
5564
+ "_sovereignty_profile",
5565
+ "_context_gate_policies"
5021
5566
  ];
5022
5567
  var StateStore = class {
5023
5568
  storage;
@@ -7179,6 +7724,24 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
7179
7724
  }
7180
7725
  const publishType = args.type;
7181
7726
  const veracoreUrl = args.verascore_url || "https://verascore.ai";
7727
+ const ALLOWED_VERASCORE_HOSTS = ["verascore.ai", "www.verascore.ai", "api.verascore.ai"];
7728
+ try {
7729
+ const parsed = new URL(veracoreUrl);
7730
+ if (parsed.protocol !== "https:") {
7731
+ return toolResult({
7732
+ error: `verascore_url must use HTTPS. Got: ${parsed.protocol}`
7733
+ });
7734
+ }
7735
+ if (!ALLOWED_VERASCORE_HOSTS.includes(parsed.hostname)) {
7736
+ return toolResult({
7737
+ error: `verascore_url must point to a known Verascore domain (${ALLOWED_VERASCORE_HOSTS.join(", ")}). Got: ${parsed.hostname}`
7738
+ });
7739
+ }
7740
+ } catch {
7741
+ return toolResult({
7742
+ error: `verascore_url is not a valid URL: ${veracoreUrl}`
7743
+ });
7744
+ }
7182
7745
  const agentId = args.verascore_agent_id || identity.did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
7183
7746
  let publishData;
7184
7747
  if (args.data) {
@@ -7208,24 +7771,21 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
7208
7771
  return toolResult({ error: `Unknown publish type: ${publishType}` });
7209
7772
  }
7210
7773
  }
7211
- const { sign: sign2, createPrivateKey } = await import('crypto');
7212
- const payloadBytes = Buffer.from(JSON.stringify(publishData), "utf-8");
7774
+ const { sign: identitySign } = await Promise.resolve().then(() => (init_identity(), identity_exports));
7775
+ const payloadBytes = new TextEncoder().encode(JSON.stringify(publishData));
7213
7776
  let signatureB64;
7214
7777
  try {
7215
- const signingKey = derivePurposeKey(masterKey, "verascore-publish");
7216
- const privateKey = createPrivateKey({
7217
- key: Buffer.concat([
7218
- Buffer.from("302e020100300506032b657004220420", "hex"),
7219
- // Ed25519 DER PKCS8 prefix
7220
- Buffer.from(signingKey.slice(0, 32))
7221
- ]),
7222
- format: "der",
7223
- type: "pkcs8"
7224
- });
7225
- const sig = sign2(null, payloadBytes, privateKey);
7226
- signatureB64 = sig.toString("base64url");
7778
+ const signingBytes = identitySign(
7779
+ payloadBytes,
7780
+ identity.encrypted_private_key,
7781
+ identityEncryptionKey
7782
+ );
7783
+ signatureB64 = toBase64url(signingBytes);
7227
7784
  } catch (signError) {
7228
- signatureB64 = toBase64url(new Uint8Array(64));
7785
+ return toolResult({
7786
+ error: "Failed to sign publish payload. Identity key may be corrupted.",
7787
+ details: signError instanceof Error ? signError.message : String(signError)
7788
+ });
7229
7789
  }
7230
7790
  const requestBody = {
7231
7791
  agentId,
@@ -12941,12 +13501,147 @@ function createL2HardeningTools(storagePath, auditLog) {
12941
13501
  ];
12942
13502
  }
12943
13503
 
13504
+ // src/index.ts
13505
+ init_sovereignty_profile();
13506
+
13507
+ // src/sovereignty-profile-tools.ts
13508
+ init_router();
13509
+ init_system_prompt_generator();
13510
+ function createSovereigntyProfileTools(profileStore, auditLog) {
13511
+ const tools = [
13512
+ // ── Get Profile ──────────────────────────────────────────────────
13513
+ {
13514
+ name: "sanctuary/sovereignty_profile_get",
13515
+ description: "Get the current Sovereignty Profile \u2014 shows which Sanctuary features are active (audit logging, injection detection, context gating, approval gates, ZK proofs) and their configuration.",
13516
+ inputSchema: {
13517
+ type: "object",
13518
+ properties: {}
13519
+ },
13520
+ handler: async () => {
13521
+ const profile = profileStore.get();
13522
+ auditLog.append("l2", "sovereignty_profile_get", "system", {
13523
+ features_enabled: Object.entries(profile.features).filter(([, v]) => v.enabled).map(([k]) => k)
13524
+ });
13525
+ return toolResult({
13526
+ profile,
13527
+ message: "Current Sovereignty Profile. Use sovereignty_profile_update to change settings."
13528
+ });
13529
+ }
13530
+ },
13531
+ // ── Update Profile ───────────────────────────────────────────────
13532
+ {
13533
+ name: "sanctuary/sovereignty_profile_update",
13534
+ description: "Update the Sovereignty Profile feature toggles. This changes which Sanctuary protections are active. Requires human approval (Tier 1) because it modifies enforcement behavior. Pass only the features you want to change \u2014 unspecified features remain unchanged.",
13535
+ inputSchema: {
13536
+ type: "object",
13537
+ properties: {
13538
+ audit_logging: {
13539
+ type: "object",
13540
+ properties: {
13541
+ enabled: { type: "boolean" }
13542
+ },
13543
+ description: "Toggle audit logging on/off"
13544
+ },
13545
+ injection_detection: {
13546
+ type: "object",
13547
+ properties: {
13548
+ enabled: { type: "boolean" },
13549
+ sensitivity: {
13550
+ type: "string",
13551
+ enum: ["low", "medium", "high"],
13552
+ description: "Detection sensitivity threshold"
13553
+ }
13554
+ },
13555
+ description: "Toggle injection detection and set sensitivity"
13556
+ },
13557
+ context_gating: {
13558
+ type: "object",
13559
+ properties: {
13560
+ enabled: { type: "boolean" },
13561
+ policy_id: {
13562
+ type: "string",
13563
+ description: "ID of the context-gating policy to use"
13564
+ }
13565
+ },
13566
+ description: "Toggle context gating and set active policy"
13567
+ },
13568
+ approval_gate: {
13569
+ type: "object",
13570
+ properties: {
13571
+ enabled: { type: "boolean" }
13572
+ },
13573
+ description: "Toggle approval gates on/off"
13574
+ },
13575
+ zk_proofs: {
13576
+ type: "object",
13577
+ properties: {
13578
+ enabled: { type: "boolean" }
13579
+ },
13580
+ description: "Toggle zero-knowledge proofs on/off"
13581
+ }
13582
+ }
13583
+ },
13584
+ handler: async (args) => {
13585
+ const updates = {};
13586
+ if (args.audit_logging !== void 0) {
13587
+ updates.audit_logging = args.audit_logging;
13588
+ }
13589
+ if (args.injection_detection !== void 0) {
13590
+ updates.injection_detection = args.injection_detection;
13591
+ }
13592
+ if (args.context_gating !== void 0) {
13593
+ updates.context_gating = args.context_gating;
13594
+ }
13595
+ if (args.approval_gate !== void 0) {
13596
+ updates.approval_gate = args.approval_gate;
13597
+ }
13598
+ if (args.zk_proofs !== void 0) {
13599
+ updates.zk_proofs = args.zk_proofs;
13600
+ }
13601
+ const updated = await profileStore.update(updates);
13602
+ auditLog.append("l2", "sovereignty_profile_update", "system", {
13603
+ changes: updates,
13604
+ features_enabled: Object.entries(updated.features).filter(([, v]) => v.enabled).map(([k]) => k)
13605
+ });
13606
+ return toolResult({
13607
+ profile: updated,
13608
+ message: "Sovereignty Profile updated. Changes take effect immediately."
13609
+ });
13610
+ }
13611
+ },
13612
+ // ── Generate System Prompt ───────────────────────────────────────
13613
+ {
13614
+ name: "sanctuary/sovereignty_profile_generate_prompt",
13615
+ description: "Generate a system prompt snippet based on the active Sovereignty Profile. The snippet instructs an agent on which Sanctuary features are active and how to use them. Copy and paste this into your agent's system configuration.",
13616
+ inputSchema: {
13617
+ type: "object",
13618
+ properties: {}
13619
+ },
13620
+ handler: async () => {
13621
+ const profile = profileStore.get();
13622
+ const prompt = generateSystemPrompt(profile);
13623
+ auditLog.append("l2", "sovereignty_profile_generate_prompt", "system", {
13624
+ features_enabled: Object.entries(profile.features).filter(([, v]) => v.enabled).map(([k]) => k)
13625
+ });
13626
+ return toolResult({
13627
+ system_prompt: prompt,
13628
+ token_estimate: Math.ceil(prompt.length / 4),
13629
+ message: "Copy the system_prompt text above and paste it into your agent's system configuration. It will update dynamically as you toggle features."
13630
+ });
13631
+ }
13632
+ }
13633
+ ];
13634
+ return { tools };
13635
+ }
13636
+
12944
13637
  // src/index.ts
12945
13638
  init_key_derivation();
12946
13639
  init_random();
12947
13640
  init_encoding();
12948
13641
  init_config();
12949
13642
  init_audit_log();
13643
+ init_sovereignty_profile();
13644
+ init_system_prompt_generator();
12950
13645
  init_filesystem();
12951
13646
  init_baseline();
12952
13647
  init_loader();
@@ -13307,6 +14002,9 @@ async function createSanctuaryServer(options) {
13307
14002
  const { tools: auditTools } = createAuditTools(config);
13308
14003
  const { tools: contextGateTools, enforcer: contextGateEnforcer } = createContextGateTools(storage, masterKey, auditLog);
13309
14004
  const hardeningTools = createL2HardeningTools(config.storage_path, auditLog);
14005
+ const profileStore = new SovereigntyProfileStore(storage, masterKey);
14006
+ await profileStore.load();
14007
+ const { tools: profileTools } = createSovereigntyProfileTools(profileStore, auditLog);
13310
14008
  const policy = await loadPrincipalPolicy(config.storage_path);
13311
14009
  const baseline = new BaselineTracker(storage, masterKey);
13312
14010
  await baseline.load();
@@ -13334,7 +14032,8 @@ async function createSanctuaryServer(options) {
13334
14032
  identityManager,
13335
14033
  handshakeResults,
13336
14034
  shrOpts: { config, identityManager, masterKey },
13337
- sanctuaryConfig: config
14035
+ sanctuaryConfig: config,
14036
+ profileStore
13338
14037
  });
13339
14038
  await dashboard.start();
13340
14039
  approvalChannel = dashboard;
@@ -13411,6 +14110,7 @@ async function createSanctuaryServer(options) {
13411
14110
  ...auditTools,
13412
14111
  ...contextGateTools,
13413
14112
  ...hardeningTools,
14113
+ ...profileTools,
13414
14114
  ...dashboardTools,
13415
14115
  manifestTool
13416
14116
  ];