@sanctuary-framework/mcp-server 0.6.1 → 0.7.0

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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { mkdir, readFile, writeFile, chmod, access, stat, unlink, readdir } from 'fs/promises';
2
+ import { mkdir, readFile, writeFile, chmod, access, stat, unlink, readdir, copyFile } from 'fs/promises';
3
3
  import { join } from 'path';
4
4
  import { homedir, platform } from 'os';
5
5
  import { createRequire } from 'module';
@@ -962,7 +962,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
962
962
  const tools = [
963
963
  // ── Identity Tools ──────────────────────────────────────────────────
964
964
  {
965
- name: "sanctuary/identity_create",
965
+ name: "identity_create",
966
966
  description: "Create a new sovereign identity (Ed25519 keypair). The private key is encrypted and never exposed.",
967
967
  inputSchema: {
968
968
  type: "object",
@@ -997,7 +997,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
997
997
  }
998
998
  },
999
999
  {
1000
- name: "sanctuary/identity_list",
1000
+ name: "identity_list",
1001
1001
  description: "List all managed sovereign identities.",
1002
1002
  inputSchema: {
1003
1003
  type: "object",
@@ -1022,7 +1022,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1022
1022
  }
1023
1023
  },
1024
1024
  {
1025
- name: "sanctuary/identity_sign",
1025
+ name: "identity_sign",
1026
1026
  description: "Sign data with a managed identity. The private key is decrypted in memory only during signing.",
1027
1027
  inputSchema: {
1028
1028
  type: "object",
@@ -1060,7 +1060,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1060
1060
  }
1061
1061
  },
1062
1062
  {
1063
- name: "sanctuary/identity_verify",
1063
+ name: "identity_verify",
1064
1064
  description: "Verify an Ed25519 signature. Provide either identity_id or public_key.",
1065
1065
  inputSchema: {
1066
1066
  type: "object",
@@ -1109,7 +1109,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1109
1109
  }
1110
1110
  },
1111
1111
  {
1112
- name: "sanctuary/identity_rotate",
1112
+ name: "identity_rotate",
1113
1113
  description: "Rotate keys for an identity. Generates a new keypair and signs a rotation event with the old key for verifiable chain.",
1114
1114
  inputSchema: {
1115
1115
  type: "object",
@@ -1142,7 +1142,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1142
1142
  },
1143
1143
  // ── State Tools ─────────────────────────────────────────────────────
1144
1144
  {
1145
- name: "sanctuary/state_write",
1145
+ name: "state_write",
1146
1146
  description: "Write encrypted state to the sovereign store. Value is encrypted with a namespace-specific key. The write is signed by the active identity.",
1147
1147
  inputSchema: {
1148
1148
  type: "object",
@@ -1199,7 +1199,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1199
1199
  }
1200
1200
  },
1201
1201
  {
1202
- name: "sanctuary/state_read",
1202
+ name: "state_read",
1203
1203
  description: "Read and decrypt state from the sovereign store. Verifies integrity via Merkle proof and signature.",
1204
1204
  inputSchema: {
1205
1205
  type: "object",
@@ -1240,7 +1240,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1240
1240
  }
1241
1241
  },
1242
1242
  {
1243
- name: "sanctuary/state_list",
1243
+ name: "state_list",
1244
1244
  description: "List keys in a namespace (metadata only \u2014 no decryption).",
1245
1245
  inputSchema: {
1246
1246
  type: "object",
@@ -1272,7 +1272,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1272
1272
  }
1273
1273
  },
1274
1274
  {
1275
- name: "sanctuary/state_delete",
1275
+ name: "state_delete",
1276
1276
  description: "Securely delete state. Overwrites file with random bytes before removal (right to deletion, S1.6).",
1277
1277
  inputSchema: {
1278
1278
  type: "object",
@@ -1304,7 +1304,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1304
1304
  }
1305
1305
  },
1306
1306
  {
1307
- name: "sanctuary/state_export",
1307
+ name: "state_export",
1308
1308
  description: "Export state as an encrypted, portable bundle for migration.",
1309
1309
  inputSchema: {
1310
1310
  type: "object",
@@ -1324,7 +1324,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1324
1324
  }
1325
1325
  },
1326
1326
  {
1327
- name: "sanctuary/state_import",
1327
+ name: "state_import",
1328
1328
  description: "Import a previously exported state bundle.",
1329
1329
  inputSchema: {
1330
1330
  type: "object",
@@ -1735,8 +1735,6 @@ tier3_always_allow:
1735
1735
  - governor_status
1736
1736
  - reputation_publish
1737
1737
  - sanctuary_policy_status
1738
- - sanctuary_link_to_human
1739
- - sanctuary_sign_challenge
1740
1738
 
1741
1739
  # \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
1742
1740
  # How Sanctuary reaches you when approval is needed.
@@ -5070,6 +5068,807 @@ var init_dashboard_html = __esm({
5070
5068
  }
5071
5069
  });
5072
5070
 
5071
+ // src/cocoon/fortress-view.ts
5072
+ function generateFortressViewHTML(options) {
5073
+ return `<!DOCTYPE html>
5074
+ <html lang="en">
5075
+ <head>
5076
+ <meta charset="UTF-8">
5077
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5078
+ <title>Sanctuary \u2014 Fortress View</title>
5079
+ <style>
5080
+ :root {
5081
+ --bg: #0d1117;
5082
+ --surface: #161b22;
5083
+ --surface-raised: #1c2128;
5084
+ --border: #30363d;
5085
+ --text-primary: #e6edf3;
5086
+ --text-secondary: #8b949e;
5087
+ --text-muted: #484f58;
5088
+ --green: #3fb950;
5089
+ --green-dim: #238636;
5090
+ --amber: #d29922;
5091
+ --amber-dim: #9e6a03;
5092
+ --red: #f85149;
5093
+ --red-dim: #da3633;
5094
+ --blue: #58a6ff;
5095
+ --blue-dim: #1f6feb;
5096
+ }
5097
+
5098
+ * { margin: 0; padding: 0; box-sizing: border-box; }
5099
+
5100
+ body {
5101
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
5102
+ background-color: var(--bg);
5103
+ color: var(--text-primary);
5104
+ min-height: 100vh;
5105
+ }
5106
+
5107
+ /* \u2500\u2500 Header \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 */
5108
+ .fortress-header {
5109
+ display: flex;
5110
+ align-items: center;
5111
+ justify-content: space-between;
5112
+ padding: 16px 24px;
5113
+ border-bottom: 1px solid var(--border);
5114
+ background: var(--surface);
5115
+ }
5116
+
5117
+ .fortress-brand {
5118
+ display: flex;
5119
+ align-items: center;
5120
+ gap: 12px;
5121
+ }
5122
+
5123
+ .fortress-brand .shield {
5124
+ font-size: 28px;
5125
+ color: var(--blue);
5126
+ }
5127
+
5128
+ .fortress-brand h1 {
5129
+ font-size: 18px;
5130
+ font-weight: 600;
5131
+ letter-spacing: -0.5px;
5132
+ }
5133
+
5134
+ .fortress-brand .version {
5135
+ font-size: 12px;
5136
+ color: var(--text-secondary);
5137
+ }
5138
+
5139
+ .header-actions {
5140
+ display: flex;
5141
+ gap: 8px;
5142
+ }
5143
+
5144
+ .header-actions button {
5145
+ padding: 6px 16px;
5146
+ border-radius: 6px;
5147
+ border: 1px solid var(--border);
5148
+ background: var(--surface);
5149
+ color: var(--text-primary);
5150
+ font-size: 13px;
5151
+ cursor: pointer;
5152
+ transition: background 0.15s;
5153
+ }
5154
+
5155
+ .header-actions button:hover {
5156
+ background: var(--surface-raised);
5157
+ }
5158
+
5159
+ .header-actions .pause-btn {
5160
+ border-color: var(--red-dim);
5161
+ color: var(--red);
5162
+ }
5163
+
5164
+ .header-actions .pause-btn:hover {
5165
+ background: rgba(248, 81, 73, 0.1);
5166
+ }
5167
+
5168
+ .header-actions .pause-btn.paused {
5169
+ background: var(--red-dim);
5170
+ color: white;
5171
+ }
5172
+
5173
+ /* \u2500\u2500 Tab bar \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 */
5174
+ .tab-bar {
5175
+ display: flex;
5176
+ border-bottom: 1px solid var(--border);
5177
+ background: var(--surface);
5178
+ padding: 0 24px;
5179
+ }
5180
+
5181
+ .tab-bar button {
5182
+ padding: 10px 16px;
5183
+ border: none;
5184
+ background: none;
5185
+ color: var(--text-secondary);
5186
+ font-size: 14px;
5187
+ cursor: pointer;
5188
+ border-bottom: 2px solid transparent;
5189
+ transition: all 0.15s;
5190
+ }
5191
+
5192
+ .tab-bar button:hover {
5193
+ color: var(--text-primary);
5194
+ }
5195
+
5196
+ .tab-bar button.active {
5197
+ color: var(--text-primary);
5198
+ border-bottom-color: var(--blue);
5199
+ }
5200
+
5201
+ /* \u2500\u2500 Content \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 */
5202
+ .fortress-content { padding: 24px; }
5203
+
5204
+ /* \u2500\u2500 Status Banner \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 */
5205
+ .status-banner {
5206
+ display: flex;
5207
+ align-items: center;
5208
+ gap: 16px;
5209
+ padding: 20px 24px;
5210
+ border-radius: 8px;
5211
+ border: 1px solid var(--border);
5212
+ background: var(--surface);
5213
+ margin-bottom: 24px;
5214
+ }
5215
+
5216
+ .status-indicator {
5217
+ width: 48px;
5218
+ height: 48px;
5219
+ border-radius: 50%;
5220
+ display: flex;
5221
+ align-items: center;
5222
+ justify-content: center;
5223
+ font-size: 24px;
5224
+ flex-shrink: 0;
5225
+ }
5226
+
5227
+ .status-indicator.green { background: rgba(63, 185, 80, 0.15); color: var(--green); }
5228
+ .status-indicator.amber { background: rgba(210, 153, 34, 0.15); color: var(--amber); }
5229
+ .status-indicator.red { background: rgba(248, 81, 73, 0.15); color: var(--red); }
5230
+
5231
+ .status-info h2 {
5232
+ font-size: 18px;
5233
+ font-weight: 600;
5234
+ margin-bottom: 4px;
5235
+ }
5236
+
5237
+ .status-info p {
5238
+ font-size: 14px;
5239
+ color: var(--text-secondary);
5240
+ }
5241
+
5242
+ .status-stats {
5243
+ display: flex;
5244
+ gap: 24px;
5245
+ margin-left: auto;
5246
+ }
5247
+
5248
+ .stat {
5249
+ text-align: center;
5250
+ }
5251
+
5252
+ .stat .value {
5253
+ font-size: 24px;
5254
+ font-weight: 600;
5255
+ font-variant-numeric: tabular-nums;
5256
+ }
5257
+
5258
+ .stat .label {
5259
+ font-size: 11px;
5260
+ color: var(--text-secondary);
5261
+ text-transform: uppercase;
5262
+ letter-spacing: 0.5px;
5263
+ }
5264
+
5265
+ /* \u2500\u2500 Two-column layout \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 */
5266
+ .fortress-grid {
5267
+ display: grid;
5268
+ grid-template-columns: 1fr 360px;
5269
+ gap: 24px;
5270
+ }
5271
+
5272
+ @media (max-width: 900px) {
5273
+ .fortress-grid { grid-template-columns: 1fr; }
5274
+ }
5275
+
5276
+ /* \u2500\u2500 Feed \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 */
5277
+ .feed-panel {
5278
+ background: var(--surface);
5279
+ border: 1px solid var(--border);
5280
+ border-radius: 8px;
5281
+ overflow: hidden;
5282
+ }
5283
+
5284
+ .panel-header {
5285
+ display: flex;
5286
+ align-items: center;
5287
+ justify-content: space-between;
5288
+ padding: 12px 16px;
5289
+ border-bottom: 1px solid var(--border);
5290
+ }
5291
+
5292
+ .panel-header h3 {
5293
+ font-size: 14px;
5294
+ font-weight: 600;
5295
+ }
5296
+
5297
+ .feed-list {
5298
+ max-height: 600px;
5299
+ overflow-y: auto;
5300
+ scroll-behavior: smooth;
5301
+ }
5302
+
5303
+ .feed-item {
5304
+ display: flex;
5305
+ align-items: flex-start;
5306
+ gap: 10px;
5307
+ padding: 10px 16px;
5308
+ border-bottom: 1px solid var(--border);
5309
+ font-size: 13px;
5310
+ transition: background 0.1s;
5311
+ }
5312
+
5313
+ .feed-item:hover {
5314
+ background: var(--surface-raised);
5315
+ }
5316
+
5317
+ .feed-dot {
5318
+ width: 8px;
5319
+ height: 8px;
5320
+ border-radius: 50%;
5321
+ margin-top: 5px;
5322
+ flex-shrink: 0;
5323
+ }
5324
+
5325
+ .feed-dot.green { background: var(--green); }
5326
+ .feed-dot.amber { background: var(--amber); }
5327
+ .feed-dot.red { background: var(--red); }
5328
+
5329
+ .feed-detail {
5330
+ flex: 1;
5331
+ min-width: 0;
5332
+ }
5333
+
5334
+ .feed-tool {
5335
+ font-family: 'SF Mono', 'Fira Code', monospace;
5336
+ font-size: 12px;
5337
+ color: var(--blue);
5338
+ word-break: break-all;
5339
+ }
5340
+
5341
+ .feed-decision {
5342
+ font-size: 12px;
5343
+ color: var(--text-secondary);
5344
+ margin-top: 2px;
5345
+ }
5346
+
5347
+ .feed-time {
5348
+ font-size: 11px;
5349
+ color: var(--text-muted);
5350
+ flex-shrink: 0;
5351
+ white-space: nowrap;
5352
+ }
5353
+
5354
+ .feed-empty {
5355
+ padding: 40px 16px;
5356
+ text-align: center;
5357
+ color: var(--text-muted);
5358
+ font-size: 14px;
5359
+ }
5360
+
5361
+ /* \u2500\u2500 Alerts Panel \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 */
5362
+ .alerts-panel {
5363
+ background: var(--surface);
5364
+ border: 1px solid var(--border);
5365
+ border-radius: 8px;
5366
+ overflow: hidden;
5367
+ }
5368
+
5369
+ .alert-item {
5370
+ padding: 12px 16px;
5371
+ border-bottom: 1px solid var(--border);
5372
+ }
5373
+
5374
+ .alert-item .alert-title {
5375
+ font-size: 13px;
5376
+ font-weight: 500;
5377
+ margin-bottom: 4px;
5378
+ }
5379
+
5380
+ .alert-item .alert-desc {
5381
+ font-size: 12px;
5382
+ color: var(--text-secondary);
5383
+ margin-bottom: 8px;
5384
+ }
5385
+
5386
+ .alert-actions {
5387
+ display: flex;
5388
+ gap: 8px;
5389
+ }
5390
+
5391
+ .alert-actions button {
5392
+ padding: 4px 12px;
5393
+ border-radius: 4px;
5394
+ border: 1px solid var(--border);
5395
+ font-size: 12px;
5396
+ cursor: pointer;
5397
+ transition: all 0.15s;
5398
+ }
5399
+
5400
+ .approve-btn {
5401
+ background: var(--green-dim);
5402
+ color: white;
5403
+ border-color: var(--green-dim) !important;
5404
+ }
5405
+
5406
+ .approve-btn:hover { opacity: 0.9; }
5407
+
5408
+ .deny-btn {
5409
+ background: none;
5410
+ color: var(--red);
5411
+ border-color: var(--red-dim) !important;
5412
+ }
5413
+
5414
+ .deny-btn:hover {
5415
+ background: rgba(248, 81, 73, 0.1);
5416
+ }
5417
+
5418
+ .alerts-empty {
5419
+ padding: 40px 16px;
5420
+ text-align: center;
5421
+ color: var(--text-muted);
5422
+ font-size: 14px;
5423
+ }
5424
+
5425
+ /* \u2500\u2500 Servers panel \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 */
5426
+ .servers-panel {
5427
+ margin-top: 16px;
5428
+ }
5429
+
5430
+ .server-row {
5431
+ display: flex;
5432
+ align-items: center;
5433
+ gap: 8px;
5434
+ padding: 8px 16px;
5435
+ border-bottom: 1px solid var(--border);
5436
+ font-size: 13px;
5437
+ }
5438
+
5439
+ .server-status-dot {
5440
+ width: 8px;
5441
+ height: 8px;
5442
+ border-radius: 50%;
5443
+ }
5444
+
5445
+ .server-status-dot.connected { background: var(--green); }
5446
+ .server-status-dot.connecting { background: var(--amber); }
5447
+ .server-status-dot.disconnected, .server-status-dot.error { background: var(--red); }
5448
+
5449
+ .server-name {
5450
+ font-family: 'SF Mono', 'Fira Code', monospace;
5451
+ font-size: 12px;
5452
+ }
5453
+
5454
+ .server-tier {
5455
+ margin-left: auto;
5456
+ font-size: 11px;
5457
+ color: var(--text-secondary);
5458
+ }
5459
+ </style>
5460
+ </head>
5461
+ <body>
5462
+ <!-- Header -->
5463
+ <div class="fortress-header">
5464
+ <div class="fortress-brand">
5465
+ <div class="shield">&#x1F6E1;</div>
5466
+ <div>
5467
+ <h1>Sanctuary Cocoon</h1>
5468
+ <div class="version">v${esc(options.serverVersion)}</div>
5469
+ </div>
5470
+ </div>
5471
+ <div class="header-actions">
5472
+ <button class="pause-btn" id="pause-btn" title="Pause agent \u2014 requires approval for all operations">Pause Agent</button>
5473
+ <button id="advanced-btn">Advanced</button>
5474
+ </div>
5475
+ </div>
5476
+
5477
+ <!-- Tab bar -->
5478
+ <div class="tab-bar">
5479
+ <button class="active" data-tab="fortress">Fortress</button>
5480
+ <button data-tab="advanced">Advanced</button>
5481
+ </div>
5482
+
5483
+ <!-- Fortress View -->
5484
+ <div class="fortress-content" id="fortress-tab">
5485
+ <!-- Status Banner -->
5486
+ <div class="status-banner" id="status-banner">
5487
+ <div class="status-indicator green" id="status-indicator">&#x2713;</div>
5488
+ <div class="status-info">
5489
+ <h2 id="status-title">Agent Protected</h2>
5490
+ <p id="status-subtitle">${options.upstreamServerCount} server${options.upstreamServerCount !== 1 ? "s" : ""} monitored. All systems nominal.</p>
5491
+ </div>
5492
+ <div class="status-stats">
5493
+ <div class="stat">
5494
+ <div class="value" id="stat-total">0</div>
5495
+ <div class="label">Calls</div>
5496
+ </div>
5497
+ <div class="stat">
5498
+ <div class="value" id="stat-blocked">0</div>
5499
+ <div class="label">Blocked</div>
5500
+ </div>
5501
+ <div class="stat">
5502
+ <div class="value" id="stat-pending">0</div>
5503
+ <div class="label">Pending</div>
5504
+ </div>
5505
+ </div>
5506
+ </div>
5507
+
5508
+ <!-- Two-column layout -->
5509
+ <div class="fortress-grid">
5510
+ <!-- Live Feed -->
5511
+ <div class="feed-panel">
5512
+ <div class="panel-header">
5513
+ <h3>Live Activity</h3>
5514
+ <span style="font-size: 12px; color: var(--text-muted);" id="feed-count">0 events</span>
5515
+ </div>
5516
+ <div class="feed-list" id="feed-list">
5517
+ <div class="feed-empty">Waiting for tool calls...</div>
5518
+ </div>
5519
+ </div>
5520
+
5521
+ <!-- Right column: Alerts + Servers -->
5522
+ <div>
5523
+ <!-- Alerts -->
5524
+ <div class="alerts-panel">
5525
+ <div class="panel-header">
5526
+ <h3>Needs Attention</h3>
5527
+ <span style="font-size: 12px; color: var(--text-muted);" id="alert-count">0</span>
5528
+ </div>
5529
+ <div id="alerts-list">
5530
+ <div class="alerts-empty">No pending actions</div>
5531
+ </div>
5532
+ </div>
5533
+
5534
+ <!-- Servers -->
5535
+ <div class="alerts-panel servers-panel">
5536
+ <div class="panel-header">
5537
+ <h3>Upstream Servers</h3>
5538
+ </div>
5539
+ <div id="servers-list">
5540
+ <div class="alerts-empty">No servers configured</div>
5541
+ </div>
5542
+ </div>
5543
+ </div>
5544
+ </div>
5545
+ </div>
5546
+
5547
+ <script>
5548
+ // \u2500\u2500 State \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\u2500\u2500\u2500
5549
+ const API_BASE = window.location.origin;
5550
+ const SESSION_TOKEN = sessionStorage.getItem('sanctuary_session') || '';
5551
+ const MAX_FEED_ITEMS = 50;
5552
+
5553
+ let feedItems = [];
5554
+ let totalCalls = 0;
5555
+ let blockedCalls = 0;
5556
+ let pendingApprovals = [];
5557
+ let upstreamServers = [];
5558
+ let paused = false;
5559
+
5560
+ // \u2500\u2500 SSE Connection \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
5561
+ function connectSSE() {
5562
+ const url = API_BASE + '/events' + (SESSION_TOKEN ? '?session=' + SESSION_TOKEN : '');
5563
+ const eventSource = new EventSource(url);
5564
+
5565
+ eventSource.addEventListener('proxy-call', (e) => {
5566
+ try {
5567
+ const data = JSON.parse(e.data);
5568
+ addFeedItem(data);
5569
+ } catch {}
5570
+ });
5571
+
5572
+ eventSource.addEventListener('proxy-server-status', (e) => {
5573
+ try {
5574
+ const data = JSON.parse(e.data);
5575
+ updateServerStatus(data.server, data.state, data.tool_count, data.error);
5576
+ } catch {}
5577
+ });
5578
+
5579
+ eventSource.addEventListener('injection-alert', (e) => {
5580
+ try {
5581
+ const data = JSON.parse(e.data);
5582
+ addFeedItem({
5583
+ tool: data.tool_name || 'unknown',
5584
+ server: 'detection',
5585
+ decision: 'blocked',
5586
+ reason: 'Injection detected: ' + (data.signals || []).join(', '),
5587
+ timestamp: new Date().toISOString(),
5588
+ });
5589
+ } catch {}
5590
+ });
5591
+
5592
+ eventSource.addEventListener('approval-request', (e) => {
5593
+ try {
5594
+ const data = JSON.parse(e.data);
5595
+ addPendingApproval(data);
5596
+ } catch {}
5597
+ });
5598
+
5599
+ eventSource.addEventListener('approval-resolved', (e) => {
5600
+ try {
5601
+ const data = JSON.parse(e.data);
5602
+ removePendingApproval(data.id);
5603
+ } catch {}
5604
+ });
5605
+
5606
+ eventSource.onerror = () => {
5607
+ eventSource.close();
5608
+ setTimeout(connectSSE, 3000);
5609
+ };
5610
+ }
5611
+
5612
+ // \u2500\u2500 Feed \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\u2500\u2500\u2500\u2500
5613
+ function addFeedItem(data) {
5614
+ totalCalls++;
5615
+ if (data.decision === 'blocked' || data.decision === 'denied') {
5616
+ blockedCalls++;
5617
+ }
5618
+
5619
+ feedItems.unshift({
5620
+ tool: data.tool || 'unknown',
5621
+ server: data.server || '',
5622
+ decision: data.decision || 'allowed',
5623
+ reason: data.reason || '',
5624
+ time: data.timestamp || new Date().toISOString(),
5625
+ });
5626
+
5627
+ if (feedItems.length > MAX_FEED_ITEMS) {
5628
+ feedItems = feedItems.slice(0, MAX_FEED_ITEMS);
5629
+ }
5630
+
5631
+ renderFeed();
5632
+ updateStats();
5633
+ updateStatus();
5634
+ }
5635
+
5636
+ function renderFeed() {
5637
+ const container = document.getElementById('feed-list');
5638
+ if (feedItems.length === 0) {
5639
+ container.innerHTML = '<div class="feed-empty">Waiting for tool calls...</div>';
5640
+ return;
5641
+ }
5642
+
5643
+ container.innerHTML = feedItems.map(item => {
5644
+ const dotColor = item.decision === 'allowed' ? 'green'
5645
+ : item.decision === 'pending' ? 'amber' : 'red';
5646
+ const decisionText = item.decision === 'allowed' ? 'Auto-allowed'
5647
+ : item.decision === 'pending' ? 'Awaiting approval'
5648
+ : item.decision === 'blocked' ? 'Blocked' : item.decision;
5649
+ const timeStr = new Date(item.time).toLocaleTimeString();
5650
+
5651
+ return '<div class="feed-item">' +
5652
+ '<div class="feed-dot ' + dotColor + '"></div>' +
5653
+ '<div class="feed-detail">' +
5654
+ '<div class="feed-tool">' + esc(item.tool) + '</div>' +
5655
+ '<div class="feed-decision">' + esc(decisionText) +
5656
+ (item.reason ? ' \u2014 ' + esc(item.reason) : '') + '</div>' +
5657
+ '</div>' +
5658
+ '<div class="feed-time">' + esc(timeStr) + '</div>' +
5659
+ '</div>';
5660
+ }).join('');
5661
+
5662
+ document.getElementById('feed-count').textContent = feedItems.length + ' events';
5663
+ }
5664
+
5665
+ // \u2500\u2500 Alerts (Pending Approvals) \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
5666
+ function addPendingApproval(data) {
5667
+ pendingApprovals.push(data);
5668
+ renderAlerts();
5669
+ updateStats();
5670
+ updateStatus();
5671
+ }
5672
+
5673
+ function removePendingApproval(id) {
5674
+ pendingApprovals = pendingApprovals.filter(a => a.id !== id);
5675
+ renderAlerts();
5676
+ updateStats();
5677
+ updateStatus();
5678
+ }
5679
+
5680
+ function renderAlerts() {
5681
+ const container = document.getElementById('alerts-list');
5682
+ if (pendingApprovals.length === 0) {
5683
+ container.innerHTML = '<div class="alerts-empty">No pending actions</div>';
5684
+ document.getElementById('alert-count').textContent = '0';
5685
+ return;
5686
+ }
5687
+
5688
+ document.getElementById('alert-count').textContent = pendingApprovals.length.toString();
5689
+
5690
+ container.innerHTML = pendingApprovals.map(approval => {
5691
+ return '<div class="alert-item">' +
5692
+ '<div class="alert-title">Approval required: ' + esc(approval.operation || approval.tool_name || 'unknown') + '</div>' +
5693
+ '<div class="alert-desc">' + esc(approval.reason || 'This operation requires your approval before it can proceed.') + '</div>' +
5694
+ '<div class="alert-actions">' +
5695
+ '<button class="approve-btn" onclick="handleApproval(\\'' + esc(approval.id) + '\\', true)">Approve</button>' +
5696
+ '<button class="deny-btn" onclick="handleApproval(\\'' + esc(approval.id) + '\\', false)">Deny</button>' +
5697
+ '</div>' +
5698
+ '</div>';
5699
+ }).join('');
5700
+ }
5701
+
5702
+ async function handleApproval(id, approved) {
5703
+ const endpoint = approved ? '/api/approve/' : '/api/deny/';
5704
+ try {
5705
+ await fetch(API_BASE + endpoint + id, {
5706
+ method: 'POST',
5707
+ headers: SESSION_TOKEN ? { 'Authorization': 'Bearer ' + SESSION_TOKEN } : {},
5708
+ });
5709
+ removePendingApproval(id);
5710
+ } catch (err) {
5711
+ console.error('Approval action failed:', err);
5712
+ }
5713
+ }
5714
+
5715
+ // \u2500\u2500 Servers \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\u2500
5716
+ function updateServerStatus(serverName, state, toolCount, error) {
5717
+ const existing = upstreamServers.find(s => s.name === serverName);
5718
+ if (existing) {
5719
+ existing.state = state;
5720
+ existing.tool_count = toolCount;
5721
+ existing.error = error;
5722
+ } else {
5723
+ upstreamServers.push({ name: serverName, state, tool_count: toolCount, error });
5724
+ }
5725
+ renderServers();
5726
+ updateStatus();
5727
+ }
5728
+
5729
+ function renderServers() {
5730
+ const container = document.getElementById('servers-list');
5731
+ if (upstreamServers.length === 0) {
5732
+ container.innerHTML = '<div class="alerts-empty">No servers configured</div>';
5733
+ return;
5734
+ }
5735
+
5736
+ container.innerHTML = upstreamServers.map(server => {
5737
+ const stateClass = server.state || 'disconnected';
5738
+ const stateLabel = server.state === 'connected' ? 'Connected'
5739
+ : server.state === 'connecting' ? 'Connecting...'
5740
+ : server.state === 'error' ? 'Error' : 'Disconnected';
5741
+
5742
+ return '<div class="server-row">' +
5743
+ '<div class="server-status-dot ' + stateClass + '"></div>' +
5744
+ '<span class="server-name">' + esc(server.name) + '</span>' +
5745
+ '<span class="server-tier">' + esc(stateLabel) +
5746
+ (server.tool_count ? ' (' + server.tool_count + ' tools)' : '') + '</span>' +
5747
+ '</div>';
5748
+ }).join('');
5749
+ }
5750
+
5751
+ // \u2500\u2500 Status Banner \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
5752
+ function updateStats() {
5753
+ document.getElementById('stat-total').textContent = totalCalls.toString();
5754
+ document.getElementById('stat-blocked').textContent = blockedCalls.toString();
5755
+ document.getElementById('stat-pending').textContent = pendingApprovals.length.toString();
5756
+ }
5757
+
5758
+ function updateStatus() {
5759
+ const indicator = document.getElementById('status-indicator');
5760
+ const title = document.getElementById('status-title');
5761
+ const subtitle = document.getElementById('status-subtitle');
5762
+
5763
+ const hasErrors = upstreamServers.some(s => s.state === 'error');
5764
+ const hasPending = pendingApprovals.length > 0;
5765
+ const hasBlocked = blockedCalls > 0;
5766
+
5767
+ if (paused) {
5768
+ indicator.className = 'status-indicator red';
5769
+ indicator.innerHTML = '&#x23F8;';
5770
+ title.textContent = 'Agent Paused';
5771
+ subtitle.textContent = 'All operations require approval. Click Resume to restore normal mode.';
5772
+ } else if (hasErrors) {
5773
+ indicator.className = 'status-indicator red';
5774
+ indicator.innerHTML = '&#x26A0;';
5775
+ title.textContent = 'Connection Issues';
5776
+ subtitle.textContent = 'One or more upstream servers have errors.';
5777
+ } else if (hasPending) {
5778
+ indicator.className = 'status-indicator amber';
5779
+ indicator.innerHTML = '&#x23F3;';
5780
+ title.textContent = 'Action Required';
5781
+ subtitle.textContent = pendingApprovals.length + ' operation' + (pendingApprovals.length > 1 ? 's' : '') + ' awaiting your approval.';
5782
+ } else {
5783
+ indicator.className = 'status-indicator green';
5784
+ indicator.innerHTML = '&#x2713;';
5785
+ title.textContent = 'Agent Protected';
5786
+ const serverCount = upstreamServers.filter(s => s.state === 'connected').length || ${options.upstreamServerCount};
5787
+ subtitle.textContent = serverCount + ' server' + (serverCount !== 1 ? 's' : '') + ' monitored. All systems nominal.';
5788
+ }
5789
+ }
5790
+
5791
+ // \u2500\u2500 Pause/Resume \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
5792
+ document.getElementById('pause-btn').addEventListener('click', () => {
5793
+ paused = !paused;
5794
+ const btn = document.getElementById('pause-btn');
5795
+ if (paused) {
5796
+ btn.textContent = 'Resume Agent';
5797
+ btn.classList.add('paused');
5798
+ } else {
5799
+ btn.textContent = 'Pause Agent';
5800
+ btn.classList.remove('paused');
5801
+ }
5802
+ updateStatus();
5803
+ // TODO: POST to /api/cocoon/pause to set all tiers to 1
5804
+ });
5805
+
5806
+ // \u2500\u2500 Tab switching \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
5807
+ document.getElementById('advanced-btn').addEventListener('click', () => {
5808
+ window.location.href = '/dashboard?session=' + SESSION_TOKEN;
5809
+ });
5810
+
5811
+ document.querySelectorAll('.tab-bar button').forEach(btn => {
5812
+ btn.addEventListener('click', () => {
5813
+ const tab = btn.dataset.tab;
5814
+ if (tab === 'advanced') {
5815
+ window.location.href = '/dashboard?session=' + SESSION_TOKEN;
5816
+ }
5817
+ });
5818
+ });
5819
+
5820
+ // \u2500\u2500 Escape helper \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
5821
+ function esc(str) {
5822
+ if (!str) return '';
5823
+ const d = document.createElement('div');
5824
+ d.textContent = String(str);
5825
+ return d.innerHTML;
5826
+ }
5827
+
5828
+ // \u2500\u2500 Init \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\u2500\u2500
5829
+ async function init() {
5830
+ // Load initial server state
5831
+ try {
5832
+ const resp = await fetch(API_BASE + '/api/proxy/servers', {
5833
+ headers: SESSION_TOKEN ? { 'Authorization': 'Bearer ' + SESSION_TOKEN } : {},
5834
+ });
5835
+ if (resp.ok) {
5836
+ const data = await resp.json();
5837
+ upstreamServers = data.servers || [];
5838
+ renderServers();
5839
+ }
5840
+ } catch {}
5841
+
5842
+ // Load pending approvals
5843
+ try {
5844
+ const resp = await fetch(API_BASE + '/api/pending', {
5845
+ headers: SESSION_TOKEN ? { 'Authorization': 'Bearer ' + SESSION_TOKEN } : {},
5846
+ });
5847
+ if (resp.ok) {
5848
+ const data = await resp.json();
5849
+ pendingApprovals = data.pending || [];
5850
+ renderAlerts();
5851
+ updateStats();
5852
+ }
5853
+ } catch {}
5854
+
5855
+ updateStatus();
5856
+ connectSSE();
5857
+ }
5858
+
5859
+ init();
5860
+ </script>
5861
+ </body>
5862
+ </html>`;
5863
+ }
5864
+ function esc(str) {
5865
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5866
+ }
5867
+ var init_fortress_view = __esm({
5868
+ "src/cocoon/fortress-view.ts"() {
5869
+ }
5870
+ });
5871
+
5073
5872
  // src/system-prompt-generator.ts
5074
5873
  function generateSystemPrompt(profile) {
5075
5874
  const activeFeatures = [];
@@ -5131,12 +5930,12 @@ function buildQuickStart(activeKeys) {
5131
5930
  const items = [];
5132
5931
  if (activeKeys.includes("context_gating")) {
5133
5932
  items.push(
5134
- "1. ALWAYS call sanctuary/context_gate_filter before sending context to external APIs."
5933
+ "1. ALWAYS call context_gate_filter before sending context to external APIs."
5135
5934
  );
5136
5935
  }
5137
5936
  if (activeKeys.includes("zk_proofs")) {
5138
5937
  items.push(
5139
- `${items.length + 1}. Use sanctuary/zk_commit to prove claims without revealing underlying data.`
5938
+ `${items.length + 1}. Use zk_commit to prove claims without revealing underlying data.`
5140
5939
  );
5141
5940
  }
5142
5941
  if (activeKeys.includes("approval_gate")) {
@@ -5162,9 +5961,9 @@ var init_system_prompt_generator = __esm({
5162
5961
  FEATURE_INFO = {
5163
5962
  audit_logging: {
5164
5963
  name: "Audit Logging",
5165
- activeDescription: "All your tool calls are logged to an encrypted audit trail. No action needed \u2014 this is automatic. You can query the log with sanctuary/monitor_audit_log if you need to review past activity.",
5166
- toolNames: ["sanctuary/monitor_audit_log"],
5167
- disabledDescription: "audit logging (sanctuary/monitor_audit_log)",
5964
+ activeDescription: "All your tool calls are logged to an encrypted audit trail. No action needed \u2014 this is automatic. You can query the log with monitor_audit_log if you need to review past activity.",
5965
+ toolNames: ["monitor_audit_log"],
5966
+ disabledDescription: "audit logging (monitor_audit_log)",
5168
5967
  usageExample: "Automatic \u2014 every tool call you make is recorded. No explicit action required."
5169
5968
  },
5170
5969
  injection_detection: {
@@ -5175,16 +5974,16 @@ var init_system_prompt_generator = __esm({
5175
5974
  },
5176
5975
  context_gating: {
5177
5976
  name: "Context Gating",
5178
- activeDescription: "Before sending context to any external API (LLM inference, tool APIs, logging services), call sanctuary/context_gate_filter to strip sensitive fields. Use sanctuary/context_gate_set_policy to define filtering rules, or sanctuary/context_gate_apply_template for presets.",
5977
+ activeDescription: "Before sending context to any external API (LLM inference, tool APIs, logging services), call context_gate_filter to strip sensitive fields. Use context_gate_set_policy to define filtering rules, or context_gate_apply_template for presets.",
5179
5978
  toolNames: [
5180
- "sanctuary/context_gate_filter",
5181
- "sanctuary/context_gate_set_policy",
5182
- "sanctuary/context_gate_apply_template",
5183
- "sanctuary/context_gate_recommend",
5184
- "sanctuary/context_gate_list_policies"
5979
+ "context_gate_filter",
5980
+ "context_gate_set_policy",
5981
+ "context_gate_apply_template",
5982
+ "context_gate_recommend",
5983
+ "context_gate_list_policies"
5185
5984
  ],
5186
- disabledDescription: "context gating (sanctuary/context_gate_filter)",
5187
- usageExample: "Before calling an external API, run: sanctuary/context_gate_filter with your context object and policy_id to get a filtered version."
5985
+ disabledDescription: "context gating (context_gate_filter)",
5986
+ usageExample: "Before calling an external API, run: context_gate_filter with your context object and policy_id to get a filtered version."
5188
5987
  },
5189
5988
  approval_gate: {
5190
5989
  name: "Approval Gates",
@@ -5194,15 +5993,15 @@ var init_system_prompt_generator = __esm({
5194
5993
  },
5195
5994
  zk_proofs: {
5196
5995
  name: "Zero-Knowledge Proofs",
5197
- activeDescription: "You can prove claims about your data without revealing the underlying values. Use sanctuary/zk_commit to create a Pedersen commitment, sanctuary/zk_prove (Schnorr proof) to prove you know a committed value, and sanctuary/zk_range_prove to prove a value falls within a range \u2014 all without disclosing the actual data. For simpler SHA-256 commitments, use sanctuary/proof_commitment.",
5996
+ activeDescription: "You can prove claims about your data without revealing the underlying values. Use zk_commit to create a Pedersen commitment, zk_prove (Schnorr proof) to prove you know a committed value, and zk_range_prove to prove a value falls within a range \u2014 all without disclosing the actual data. For simpler SHA-256 commitments, use proof_commitment.",
5198
5997
  toolNames: [
5199
- "sanctuary/zk_commit",
5200
- "sanctuary/zk_prove",
5201
- "sanctuary/zk_range_prove",
5202
- "sanctuary/proof_commitment"
5998
+ "zk_commit",
5999
+ "zk_prove",
6000
+ "zk_range_prove",
6001
+ "proof_commitment"
5203
6002
  ],
5204
- disabledDescription: "zero-knowledge proofs (sanctuary/zk_commit, sanctuary/zk_prove)",
5205
- usageExample: "To prove a claim without revealing data: first sanctuary/zk_commit to commit, then sanctuary/zk_prove or sanctuary/zk_range_prove to generate a verifiable proof."
6003
+ disabledDescription: "zero-knowledge proofs (zk_commit, zk_prove)",
6004
+ usageExample: "To prove a claim without revealing data: first zk_commit to commit, then zk_prove or zk_range_prove to generate a verifiable proof."
5206
6005
  }
5207
6006
  };
5208
6007
  }
@@ -5213,6 +6012,7 @@ var init_dashboard = __esm({
5213
6012
  init_config();
5214
6013
  init_generator();
5215
6014
  init_dashboard_html();
6015
+ init_fortress_view();
5216
6016
  init_system_prompt_generator();
5217
6017
  SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
5218
6018
  SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
@@ -5236,6 +6036,7 @@ var init_dashboard = __esm({
5236
6036
  profileStore = null;
5237
6037
  clientManager = null;
5238
6038
  dashboardHTML;
6039
+ fortressHTML = null;
5239
6040
  loginHTML;
5240
6041
  authToken;
5241
6042
  useTLS;
@@ -5621,8 +6422,14 @@ var init_dashboard = __esm({
5621
6422
  if (!this.checkAuth(req, url, res)) return;
5622
6423
  if (!this.checkRateLimit(req, res, "general")) return;
5623
6424
  try {
5624
- if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
5625
- this.serveDashboard(res);
6425
+ if (method === "GET" && url.pathname === "/fortress") {
6426
+ this.serveFortressView(res);
6427
+ } else if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
6428
+ if (this.fortressHTML) {
6429
+ this.serveFortressView(res);
6430
+ } else {
6431
+ this.serveDashboard(res);
6432
+ }
5626
6433
  } else if (method === "GET" && url.pathname === "/events") {
5627
6434
  this.handleSSE(req, res);
5628
6435
  } else if (method === "GET" && url.pathname === "/api/status") {
@@ -5717,6 +6524,35 @@ var init_dashboard = __esm({
5717
6524
  });
5718
6525
  res.end(this.dashboardHTML);
5719
6526
  }
6527
+ serveFortressView(res) {
6528
+ if (!this.fortressHTML) {
6529
+ this.serveDashboard(res);
6530
+ return;
6531
+ }
6532
+ res.writeHead(200, {
6533
+ "Content-Type": "text/html; charset=utf-8",
6534
+ "Cache-Control": "no-cache"
6535
+ });
6536
+ res.end(this.fortressHTML);
6537
+ }
6538
+ /**
6539
+ * Enable Fortress View (Cocoon mode) with the given upstream server count.
6540
+ * Once enabled, the root path `/` serves the Fortress View instead of the
6541
+ * standard dashboard. The standard dashboard remains available at `/dashboard`.
6542
+ */
6543
+ enableFortressView(upstreamServerCount) {
6544
+ this.fortressHTML = generateFortressViewHTML({
6545
+ serverVersion: SANCTUARY_VERSION,
6546
+ authToken: this.authToken,
6547
+ upstreamServerCount
6548
+ });
6549
+ }
6550
+ /**
6551
+ * Broadcast a proxy call event to connected dashboards (Fortress View feed).
6552
+ */
6553
+ broadcastProxyCall(data) {
6554
+ this.broadcastSSE("proxy-call", data);
6555
+ }
5720
6556
  handleSSE(req, res) {
5721
6557
  res.writeHead(200, {
5722
6558
  "Content-Type": "text/event-stream",
@@ -6361,6 +7197,445 @@ var init_sovereignty_profile = __esm({
6361
7197
  };
6362
7198
  }
6363
7199
  });
7200
+ async function backupConfig(configPath) {
7201
+ await mkdir(BACKUP_DIR, { recursive: true, mode: 448 });
7202
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7203
+ const backupPath = join(BACKUP_DIR, `config-backup-${timestamp}.json`);
7204
+ await copyFile(configPath, backupPath);
7205
+ return backupPath;
7206
+ }
7207
+ async function restoreConfig(backupPath, targetPath) {
7208
+ await copyFile(backupPath, targetPath);
7209
+ }
7210
+ async function findLatestBackup() {
7211
+ const metaPath = join(BACKUP_DIR, "cocoon-meta.json");
7212
+ try {
7213
+ const raw = await readFile(metaPath, "utf-8");
7214
+ const meta = JSON.parse(raw);
7215
+ return {
7216
+ backupPath: meta.backupPath,
7217
+ originalPath: meta.originalPath
7218
+ };
7219
+ } catch {
7220
+ return null;
7221
+ }
7222
+ }
7223
+ async function saveCocoonMeta(meta) {
7224
+ await mkdir(BACKUP_DIR, { recursive: true, mode: 448 });
7225
+ const metaPath = join(BACKUP_DIR, "cocoon-meta.json");
7226
+ await writeFile(metaPath, JSON.stringify(meta, null, 2), { mode: 384 });
7227
+ }
7228
+ async function detectAgentConfig(platform2, configPath) {
7229
+ if (configPath) {
7230
+ return readConfigFile(configPath, platform2 ?? "generic");
7231
+ }
7232
+ if (platform2) {
7233
+ const paths = PLATFORM_PATHS[platform2];
7234
+ for (const path of paths) {
7235
+ const config = await readConfigFile(path, platform2);
7236
+ if (config) return config;
7237
+ }
7238
+ return null;
7239
+ }
7240
+ for (const [plat, paths] of Object.entries(PLATFORM_PATHS)) {
7241
+ for (const path of paths) {
7242
+ const config = await readConfigFile(path, plat);
7243
+ if (config) return config;
7244
+ }
7245
+ }
7246
+ return null;
7247
+ }
7248
+ async function readConfigFile(path, platform2) {
7249
+ try {
7250
+ await access(path);
7251
+ } catch {
7252
+ return null;
7253
+ }
7254
+ try {
7255
+ const raw = await readFile(path, "utf-8");
7256
+ const config = JSON.parse(raw);
7257
+ const servers = extractServers(config, platform2);
7258
+ return { platform: platform2, configPath: path, servers, rawConfig: config };
7259
+ } catch {
7260
+ return null;
7261
+ }
7262
+ }
7263
+ function extractServers(config, platform2) {
7264
+ if (!config || typeof config !== "object") return [];
7265
+ const servers = [];
7266
+ const obj = config;
7267
+ if (platform2 === "openclaw" || platform2 === "generic") {
7268
+ const mcp = obj.mcp;
7269
+ const nestedServers = mcp?.servers;
7270
+ if (nestedServers && typeof nestedServers === "object") {
7271
+ for (const [name, serverConfig] of Object.entries(nestedServers)) {
7272
+ const entry = parseServerEntry(name, serverConfig);
7273
+ if (entry) servers.push(entry);
7274
+ }
7275
+ }
7276
+ if (servers.length === 0) {
7277
+ const mcpServers = obj.mcpServers;
7278
+ if (mcpServers && typeof mcpServers === "object") {
7279
+ for (const [name, serverConfig] of Object.entries(mcpServers)) {
7280
+ const entry = parseServerEntry(name, serverConfig);
7281
+ if (entry) servers.push(entry);
7282
+ }
7283
+ }
7284
+ }
7285
+ }
7286
+ if (platform2 === "claude-code") {
7287
+ const mcpServers = obj.mcpServers;
7288
+ if (mcpServers && typeof mcpServers === "object") {
7289
+ for (const [name, serverConfig] of Object.entries(mcpServers)) {
7290
+ if (name.toLowerCase().includes("sanctuary")) continue;
7291
+ const entry = parseServerEntry(name, serverConfig);
7292
+ if (entry) servers.push(entry);
7293
+ }
7294
+ }
7295
+ }
7296
+ if (platform2 === "cursor") {
7297
+ const mcpServers = obj.mcpServers;
7298
+ if (mcpServers && typeof mcpServers === "object") {
7299
+ for (const [name, serverConfig] of Object.entries(mcpServers)) {
7300
+ if (name.toLowerCase().includes("sanctuary")) continue;
7301
+ const entry = parseServerEntry(name, serverConfig);
7302
+ if (entry) servers.push(entry);
7303
+ }
7304
+ }
7305
+ }
7306
+ return servers;
7307
+ }
7308
+ function parseServerEntry(name, config) {
7309
+ if (!config || typeof config !== "object") return null;
7310
+ const c = config;
7311
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "-").substring(0, 128);
7312
+ if (!safeName) return null;
7313
+ if (c.url && typeof c.url === "string") {
7314
+ return {
7315
+ name: safeName,
7316
+ transport: "sse",
7317
+ url: c.url,
7318
+ env: extractEnv(c.env)
7319
+ };
7320
+ }
7321
+ if (c.command && typeof c.command === "string") {
7322
+ return {
7323
+ name: safeName,
7324
+ transport: "stdio",
7325
+ command: c.command,
7326
+ args: Array.isArray(c.args) ? c.args.filter((a) => typeof a === "string") : void 0,
7327
+ env: extractEnv(c.env)
7328
+ };
7329
+ }
7330
+ return null;
7331
+ }
7332
+ function extractEnv(env) {
7333
+ if (!env || typeof env !== "object") return void 0;
7334
+ const result = {};
7335
+ for (const [k, v] of Object.entries(env)) {
7336
+ if (typeof v === "string") result[k] = v;
7337
+ }
7338
+ return Object.keys(result).length > 0 ? result : void 0;
7339
+ }
7340
+ async function rewriteConfigForCocoon(agentConfig, sanctuaryCommand, sanctuaryArgs, sanctuaryEnv) {
7341
+ const raw = agentConfig.rawConfig;
7342
+ let existingServers = {};
7343
+ if (agentConfig.platform === "openclaw") {
7344
+ const existingMcp = raw.mcp ?? {};
7345
+ existingServers = existingMcp.servers ?? {};
7346
+ } else {
7347
+ existingServers = raw.mcpServers ?? {};
7348
+ }
7349
+ let resolvedEnv = sanctuaryEnv;
7350
+ if (!resolvedEnv) {
7351
+ const existingSanctuary = existingServers.sanctuary;
7352
+ if (existingSanctuary?.env && typeof existingSanctuary.env === "object") {
7353
+ const extracted = extractEnv(existingSanctuary.env);
7354
+ if (extracted) resolvedEnv = extracted;
7355
+ }
7356
+ }
7357
+ const sanctuaryEntry = {
7358
+ command: sanctuaryCommand,
7359
+ args: sanctuaryArgs
7360
+ };
7361
+ if (resolvedEnv && Object.keys(resolvedEnv).length > 0) {
7362
+ sanctuaryEntry.env = resolvedEnv;
7363
+ }
7364
+ let rewritten;
7365
+ if (agentConfig.platform === "openclaw") {
7366
+ const existingMcp = raw.mcp ?? {};
7367
+ rewritten = {
7368
+ ...raw,
7369
+ mcp: {
7370
+ ...existingMcp,
7371
+ servers: {
7372
+ ...existingServers,
7373
+ sanctuary: sanctuaryEntry
7374
+ }
7375
+ }
7376
+ };
7377
+ delete rewritten.mcpServers;
7378
+ } else {
7379
+ rewritten = {
7380
+ ...raw,
7381
+ mcpServers: {
7382
+ ...existingServers,
7383
+ sanctuary: sanctuaryEntry
7384
+ }
7385
+ };
7386
+ }
7387
+ await writeFile(agentConfig.configPath, JSON.stringify(rewritten, null, 2), { mode: 384 });
7388
+ return agentConfig.configPath;
7389
+ }
7390
+ var PLATFORM_PATHS, BACKUP_DIR;
7391
+ var init_config_reader = __esm({
7392
+ "src/cocoon/config-reader.ts"() {
7393
+ PLATFORM_PATHS = {
7394
+ "openclaw": [
7395
+ join(homedir(), ".openclaw", "openclaw.json"),
7396
+ join(homedir(), ".openclaw", "config.json"),
7397
+ join(homedir(), "Library", "Application Support", "OpenClaw", "openclaw.json"),
7398
+ join(homedir(), "Library", "Application Support", "OpenClaw", "config.json")
7399
+ ],
7400
+ "claude-code": [
7401
+ join(homedir(), ".claude", "settings.json"),
7402
+ join(homedir(), ".config", "claude-code", "settings.json")
7403
+ ],
7404
+ "cursor": [
7405
+ join(homedir(), ".cursor", "mcp.json")
7406
+ ],
7407
+ "generic": []
7408
+ };
7409
+ BACKUP_DIR = join(homedir(), ".sanctuary", "backup");
7410
+ }
7411
+ });
7412
+
7413
+ // src/cocoon/cli.ts
7414
+ var cli_exports = {};
7415
+ __export(cli_exports, {
7416
+ COCOON_GOVERNOR_DEFAULTS: () => COCOON_GOVERNOR_DEFAULTS,
7417
+ parseCocoonArgs: () => parseCocoonArgs,
7418
+ runCocoon: () => runCocoon
7419
+ });
7420
+ async function runCocoon(options) {
7421
+ if (options.unwrap) {
7422
+ await unwrap();
7423
+ return;
7424
+ }
7425
+ let platform2;
7426
+ if (options.openclaw) platform2 = "openclaw";
7427
+ else if (options.claudeCode) platform2 = "claude-code";
7428
+ else if (options.cursor) platform2 = "cursor";
7429
+ const agentConfig = await detectAgentConfig(platform2, options.wrap);
7430
+ if (!agentConfig) {
7431
+ if (platform2) {
7432
+ console.error(`Could not find ${platform2} configuration. Check that the agent is installed.`);
7433
+ } else if (options.wrap) {
7434
+ console.error(`Could not read config file: ${options.wrap}`);
7435
+ } else {
7436
+ console.error("Could not auto-detect any agent configuration.");
7437
+ console.error("Use --openclaw, --claude-code, --cursor, or --wrap /path/to/config.json");
7438
+ }
7439
+ process.exit(1);
7440
+ }
7441
+ if (agentConfig.servers.length === 0) {
7442
+ console.error(`Found ${agentConfig.platform} config at ${agentConfig.configPath}, but no MCP servers configured.`);
7443
+ process.exit(1);
7444
+ }
7445
+ console.error(`
7446
+ Sanctuary Cocoon
7447
+ `);
7448
+ console.error(` Platform: ${agentConfig.platform}`);
7449
+ console.error(` Config: ${agentConfig.configPath}`);
7450
+ console.error(` MCP servers found: ${agentConfig.servers.length}
7451
+ `);
7452
+ const upstreamServers = convertToUpstreamServers(agentConfig.servers);
7453
+ for (const server of upstreamServers) {
7454
+ const overrideCount = Object.keys(server.tool_overrides ?? {}).length;
7455
+ console.error(` \u2192 ${server.name} (${server.transport.type}) \u2014 default: Tier ${server.default_tier}`);
7456
+ if (overrideCount > 0) {
7457
+ console.error(` ${overrideCount} tool-specific tier overrides`);
7458
+ }
7459
+ }
7460
+ if (options.dryRun) {
7461
+ console.error(`
7462
+ Dry run \u2014 no changes made.
7463
+ `);
7464
+ return;
7465
+ }
7466
+ const storagePath = join(homedir(), ".sanctuary");
7467
+ await mkdir(storagePath, { recursive: true, mode: 448 });
7468
+ const profile = createCocoonProfile(upstreamServers);
7469
+ const cocoonConfigPath = join(storagePath, "cocoon-profile.json");
7470
+ await writeFile(cocoonConfigPath, JSON.stringify(profile, null, 2), { mode: 384 });
7471
+ const backupPath = await backupConfig(agentConfig.configPath);
7472
+ await saveCocoonMeta({
7473
+ backupPath,
7474
+ originalPath: agentConfig.configPath,
7475
+ platform: agentConfig.platform,
7476
+ wrappedAt: (/* @__PURE__ */ new Date()).toISOString()
7477
+ });
7478
+ console.error(`
7479
+ Original config backed up to: ${backupPath}`);
7480
+ await rewriteConfigForCocoon(
7481
+ agentConfig,
7482
+ "npx",
7483
+ [
7484
+ "@sanctuary-framework/mcp-server",
7485
+ ...options.passphrase ? ["--passphrase", options.passphrase] : []
7486
+ ]
7487
+ );
7488
+ console.error(` Agent config rewritten to route through Sanctuary`);
7489
+ const dashboardPort = options.port ?? 3501;
7490
+ console.error(`
7491
+ Your agent is now protected.`);
7492
+ console.error(` All tool calls are being logged and scanned.`);
7493
+ console.error(`
7494
+ To view the dashboard, run in a separate terminal:`);
7495
+ console.error(` npx @sanctuary-framework/mcp-server dashboard --port ${dashboardPort}`);
7496
+ console.error(`
7497
+ To restore: npx @sanctuary-framework/cocoon --unwrap
7498
+ `);
7499
+ }
7500
+ async function unwrap() {
7501
+ const meta = await findLatestBackup();
7502
+ if (!meta) {
7503
+ console.error("No Cocoon wrapping found to restore.");
7504
+ console.error("Run --wrap or --openclaw first.");
7505
+ process.exit(1);
7506
+ }
7507
+ try {
7508
+ await access(meta.backupPath);
7509
+ } catch {
7510
+ console.error(`Backup file not found: ${meta.backupPath}`);
7511
+ process.exit(1);
7512
+ }
7513
+ await restoreConfig(meta.backupPath, meta.originalPath);
7514
+ console.error(`
7515
+ Sanctuary Cocoon \u2014 Unwrapped`);
7516
+ console.error(` Original config restored to: ${meta.originalPath}`);
7517
+ console.error(` Backup preserved at: ${meta.backupPath}
7518
+ `);
7519
+ }
7520
+ function convertToUpstreamServers(servers) {
7521
+ return servers.map((server) => {
7522
+ const upstream = {
7523
+ name: server.name,
7524
+ transport: server.transport === "sse" ? { type: "sse", url: server.url } : {
7525
+ type: "stdio",
7526
+ command: server.command,
7527
+ ...server.args ? { args: server.args } : {},
7528
+ ...server.env ? { env: server.env } : {}
7529
+ },
7530
+ enabled: true,
7531
+ default_tier: 2
7532
+ };
7533
+ return upstream;
7534
+ });
7535
+ }
7536
+ function createCocoonProfile(upstreamServers) {
7537
+ return {
7538
+ version: 1,
7539
+ features: {
7540
+ audit_logging: { enabled: true },
7541
+ // Non-negotiable
7542
+ injection_detection: { enabled: true },
7543
+ // Non-negotiable
7544
+ context_gating: { enabled: false },
7545
+ // Can enable later
7546
+ approval_gate: { enabled: true },
7547
+ // Core enforcement — always ON
7548
+ zk_proofs: { enabled: false }
7549
+ // Not needed for Cocoon
7550
+ },
7551
+ upstream_servers: upstreamServers,
7552
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
7553
+ };
7554
+ }
7555
+ function parseCocoonArgs(argv) {
7556
+ const options = {};
7557
+ for (let i = 0; i < argv.length; i++) {
7558
+ switch (argv[i]) {
7559
+ case "--wrap":
7560
+ options.wrap = argv[++i];
7561
+ break;
7562
+ case "--openclaw":
7563
+ options.openclaw = true;
7564
+ break;
7565
+ case "--claude-code":
7566
+ options.claudeCode = true;
7567
+ break;
7568
+ case "--cursor":
7569
+ options.cursor = true;
7570
+ break;
7571
+ case "--unwrap":
7572
+ options.unwrap = true;
7573
+ break;
7574
+ case "--passphrase":
7575
+ options.passphrase = argv[++i];
7576
+ break;
7577
+ case "--port":
7578
+ options.port = parseInt(argv[++i], 10);
7579
+ break;
7580
+ case "--dry-run":
7581
+ options.dryRun = true;
7582
+ break;
7583
+ case "--help":
7584
+ case "-h":
7585
+ printCocoonHelp();
7586
+ process.exit(0);
7587
+ }
7588
+ }
7589
+ return options;
7590
+ }
7591
+ function printCocoonHelp() {
7592
+ console.log(`
7593
+ Sanctuary Cocoon \u2014 Wrap any agent in sovereignty protection
7594
+
7595
+ Usage:
7596
+ npx @sanctuary-framework/cocoon --openclaw # Wrap OpenClaw agent
7597
+ npx @sanctuary-framework/cocoon --claude-code # Wrap Claude Code
7598
+ npx @sanctuary-framework/cocoon --cursor # Wrap Cursor
7599
+ npx @sanctuary-framework/cocoon --wrap config.json # Wrap generic MCP config
7600
+ npx @sanctuary-framework/cocoon --unwrap # Restore original config
7601
+
7602
+ Options:
7603
+ --openclaw Auto-detect and wrap OpenClaw agent
7604
+ --claude-code Auto-detect and wrap Claude Code
7605
+ --cursor Auto-detect and wrap Cursor
7606
+ --wrap <path> Wrap a specific MCP config file
7607
+ --unwrap Restore original config from backup
7608
+ --passphrase <p> Encryption passphrase
7609
+ --port <port> Dashboard port (default: 3501)
7610
+ --dry-run Show what would happen without making changes
7611
+ --help, -h Show this help
7612
+
7613
+ What happens:
7614
+ 1. Reads your agent's MCP server configuration
7615
+ 2. Backs up the original config to ~/.sanctuary/backup/
7616
+ 3. Rewrites the config so your agent routes through Sanctuary
7617
+ 4. All tool calls are logged, scanned for injection, and rate-limited
7618
+ 5. Dangerous operations require your approval via the dashboard
7619
+
7620
+ Rollback:
7621
+ --unwrap restores the original config from backup.
7622
+ Backups are preserved and never deleted.
7623
+ `);
7624
+ }
7625
+ var COCOON_GOVERNOR_DEFAULTS;
7626
+ var init_cli = __esm({
7627
+ "src/cocoon/cli.ts"() {
7628
+ init_config_reader();
7629
+ COCOON_GOVERNOR_DEFAULTS = {
7630
+ volume_limit: 200,
7631
+ // 200 calls per 10-minute window
7632
+ rate_limit_per_tool: 20,
7633
+ // 20 calls/min per individual tool
7634
+ lifetime_limit: 1e3
7635
+ // 1000 total calls per session
7636
+ };
7637
+ }
7638
+ });
6364
7639
 
6365
7640
  // src/dashboard-standalone.ts
6366
7641
  var dashboard_standalone_exports = {};
@@ -7412,7 +8687,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7412
8687
  const tools = [
7413
8688
  // ─── Commitment Schemes ───────────────────────────────────────────────
7414
8689
  {
7415
- name: "sanctuary/proof_commitment",
8690
+ name: "proof_commitment",
7416
8691
  description: "Create a cryptographic commitment to a value. The commitment hides the value until you choose to reveal it. Returns the commitment hash and a blinding factor (store securely).",
7417
8692
  inputSchema: {
7418
8693
  type: "object",
@@ -7447,7 +8722,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7447
8722
  }
7448
8723
  },
7449
8724
  {
7450
- name: "sanctuary/proof_reveal",
8725
+ name: "proof_reveal",
7451
8726
  description: "Verify a previously committed value by revealing it with the blinding factor. Returns whether the revealed value matches the commitment.",
7452
8727
  inputSchema: {
7453
8728
  type: "object",
@@ -7485,7 +8760,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7485
8760
  },
7486
8761
  // ─── Disclosure Policies ──────────────────────────────────────────────
7487
8762
  {
7488
- name: "sanctuary/disclosure_set_policy",
8763
+ name: "disclosure_set_policy",
7489
8764
  description: "Define a disclosure policy that controls what an agent will and will not disclose in different interaction contexts. Rules specify which fields may be disclosed, which must be withheld, and which require cryptographic proof.",
7490
8765
  inputSchema: {
7491
8766
  type: "object",
@@ -7560,7 +8835,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7560
8835
  }
7561
8836
  },
7562
8837
  {
7563
- name: "sanctuary/disclosure_evaluate",
8838
+ name: "disclosure_evaluate",
7564
8839
  description: "Evaluate a disclosure request against an active policy. Returns per-field decisions: disclose, withhold, proof, or ask-principal.",
7565
8840
  inputSchema: {
7566
8841
  type: "object",
@@ -7636,7 +8911,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7636
8911
  },
7637
8912
  // ─── ZK Proof Tools ───────────────────────────────────────────────────
7638
8913
  {
7639
- name: "sanctuary/zk_commit",
8914
+ name: "zk_commit",
7640
8915
  description: "Create a Pedersen commitment to a numeric value on Ristretto255. Unlike SHA-256 commitments, Pedersen commitments support zero-knowledge proofs: you can prove properties about the committed value without revealing it.",
7641
8916
  inputSchema: {
7642
8917
  type: "object",
@@ -7667,7 +8942,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7667
8942
  }
7668
8943
  },
7669
8944
  {
7670
- name: "sanctuary/zk_prove",
8945
+ name: "zk_prove",
7671
8946
  description: "Create a zero-knowledge proof of knowledge for a Pedersen commitment. Proves you know the value and blinding factor without revealing either. Uses a Schnorr sigma protocol with Fiat-Shamir transform.",
7672
8947
  inputSchema: {
7673
8948
  type: "object",
@@ -7708,7 +8983,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7708
8983
  }
7709
8984
  },
7710
8985
  {
7711
- name: "sanctuary/zk_verify",
8986
+ name: "zk_verify",
7712
8987
  description: "Verify a zero-knowledge proof of knowledge for a Pedersen commitment. Checks that the prover knows the commitment's opening without learning anything.",
7713
8988
  inputSchema: {
7714
8989
  type: "object",
@@ -7736,7 +9011,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7736
9011
  }
7737
9012
  },
7738
9013
  {
7739
- name: "sanctuary/zk_range_prove",
9014
+ name: "zk_range_prove",
7740
9015
  description: "Create a zero-knowledge range proof: prove that a committed value is within [min, max] without revealing the exact value. Uses bit-decomposition with OR-proofs on Ristretto255.",
7741
9016
  inputSchema: {
7742
9017
  type: "object",
@@ -7786,7 +9061,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7786
9061
  }
7787
9062
  },
7788
9063
  {
7789
- name: "sanctuary/zk_range_verify",
9064
+ name: "zk_range_verify",
7790
9065
  description: "Verify a zero-knowledge range proof \u2014 confirms a committed value is within the claimed range without learning the value.",
7791
9066
  inputSchema: {
7792
9067
  type: "object",
@@ -8239,7 +9514,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8239
9514
  const tools = [
8240
9515
  // ─── Reputation Recording ─────────────────────────────────────────
8241
9516
  {
8242
- name: "sanctuary/reputation_record",
9517
+ name: "reputation_record",
8243
9518
  description: "Record an interaction outcome as a signed attestation. Creates an EAS-compatible attestation signed by the specified identity.",
8244
9519
  inputSchema: {
8245
9520
  type: "object",
@@ -8332,7 +9607,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8332
9607
  },
8333
9608
  // ─── Reputation Query ─────────────────────────────────────────────
8334
9609
  {
8335
- name: "sanctuary/reputation_query",
9610
+ name: "reputation_query",
8336
9611
  description: "Query aggregated reputation data with filtering. Returns summary statistics, never raw interaction details.",
8337
9612
  inputSchema: {
8338
9613
  type: "object",
@@ -8380,7 +9655,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8380
9655
  },
8381
9656
  // ─── Reputation Export ─────────────────────────────────────────────
8382
9657
  {
8383
- name: "sanctuary/reputation_export",
9658
+ name: "reputation_export",
8384
9659
  description: "Export a portable reputation bundle (SANCTUARY_REP_V1). Includes all signed attestations for independent verification.",
8385
9660
  inputSchema: {
8386
9661
  type: "object",
@@ -8439,7 +9714,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8439
9714
  },
8440
9715
  // ─── Reputation Import ────────────────────────────────────────────
8441
9716
  {
8442
- name: "sanctuary/reputation_import",
9717
+ name: "reputation_import",
8443
9718
  description: "Import a reputation bundle from another Sanctuary instance. Verifies all attestation signatures by default.",
8444
9719
  inputSchema: {
8445
9720
  type: "object",
@@ -8491,7 +9766,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8491
9766
  },
8492
9767
  // ─── Sovereignty-Weighted Query ──────────────────────────────────
8493
9768
  {
8494
- name: "sanctuary/reputation_query_weighted",
9769
+ name: "reputation_query_weighted",
8495
9770
  description: "Query reputation with sovereignty-weighted scoring. Attestations from verified-sovereign agents carry full weight (1.0); unverified attestations carry reduced weight (0.2). Returns both the weighted score and tier distribution.",
8496
9771
  inputSchema: {
8497
9772
  type: "object",
@@ -8547,7 +9822,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8547
9822
  },
8548
9823
  // ─── Trust Bootstrap: Escrow ──────────────────────────────────────
8549
9824
  {
8550
- name: "sanctuary/bootstrap_create_escrow",
9825
+ name: "bootstrap_create_escrow",
8551
9826
  description: "Create an escrow record for trust bootstrapping. Allows new participants with no reputation to transact safely.",
8552
9827
  inputSchema: {
8553
9828
  type: "object",
@@ -8606,7 +9881,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8606
9881
  },
8607
9882
  // ─── Trust Bootstrap: Guarantee ───────────────────────────────────
8608
9883
  {
8609
- name: "sanctuary/bootstrap_provide_guarantee",
9884
+ name: "bootstrap_provide_guarantee",
8610
9885
  description: "A principal provides a signed reputation guarantee for a new agent. The guarantee certificate can be presented to counterparties.",
8611
9886
  inputSchema: {
8612
9887
  type: "object",
@@ -8684,7 +9959,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8684
9959
  },
8685
9960
  // ─── Verascore Reputation Publish ────────────────────────────────
8686
9961
  {
8687
- name: "sanctuary/reputation_publish",
9962
+ name: "reputation_publish",
8688
9963
  description: "Publish sovereignty data to Verascore (verascore.ai) \u2014 the agent reputation platform. Sends SHR data, handshake attestations, or sovereignty updates. The data is signed with the agent's Ed25519 key for verification. Requires a Verascore agent profile (claimed or stub) to exist.",
8689
9964
  inputSchema: {
8690
9965
  type: "object",
@@ -9306,7 +10581,7 @@ var InjectionDetector = class {
9306
10581
  }
9307
10582
  /**
9308
10583
  * Scan tool arguments for injection signals.
9309
- * @param toolName Full tool name (e.g., "sanctuary/state_read")
10584
+ * @param toolName Full tool name (e.g., "state_read")
9310
10585
  * @param args Tool arguments
9311
10586
  * @returns DetectionResult with all detected signals
9312
10587
  */
@@ -10307,7 +11582,7 @@ var ApprovalGate = class {
10307
11582
  /**
10308
11583
  * Evaluate a tool call against the Principal Policy.
10309
11584
  *
10310
- * @param toolName - Full MCP tool name (e.g., "sanctuary/state_export")
11585
+ * @param toolName - Full MCP tool name (e.g., "state_export")
10311
11586
  * @param args - Tool call arguments (for context extraction)
10312
11587
  * @returns GateResult indicating whether the call is allowed
10313
11588
  */
@@ -10577,7 +11852,7 @@ init_router();
10577
11852
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
10578
11853
  return [
10579
11854
  {
10580
- name: "sanctuary/principal_policy_view",
11855
+ name: "principal_policy_view",
10581
11856
  description: "View the current Principal Policy \u2014 the human-controlled rules governing what operations require approval. Read-only.",
10582
11857
  inputSchema: {
10583
11858
  type: "object",
@@ -10615,7 +11890,7 @@ function createPrincipalPolicyTools(policy, baseline, auditLog) {
10615
11890
  }
10616
11891
  },
10617
11892
  {
10618
- name: "sanctuary/principal_baseline_view",
11893
+ name: "principal_baseline_view",
10619
11894
  description: "View the current behavioral baseline \u2014 the session profile used for anomaly detection. Shows known namespaces, counterparties, and tool call counts. Read-only.",
10620
11895
  inputSchema: {
10621
11896
  type: "object",
@@ -10964,7 +12239,7 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
10964
12239
  };
10965
12240
  const tools = [
10966
12241
  {
10967
- name: "sanctuary/shr_generate",
12242
+ name: "shr_generate",
10968
12243
  description: "Generate a signed Sovereignty Health Report (SHR) \u2014 a machine-readable, cryptographically signed advertisement of this instance's sovereignty posture. Present this to counterparties to prove your sovereignty capabilities.",
10969
12244
  inputSchema: {
10970
12245
  type: "object",
@@ -10993,7 +12268,7 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
10993
12268
  }
10994
12269
  },
10995
12270
  {
10996
- name: "sanctuary/shr_verify",
12271
+ name: "shr_verify",
10997
12272
  description: "Verify a counterparty's Sovereignty Health Report (SHR). Checks signature validity, temporal validity, and assesses sovereignty level.",
10998
12273
  inputSchema: {
10999
12274
  type: "object",
@@ -11019,7 +12294,7 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
11019
12294
  }
11020
12295
  },
11021
12296
  {
11022
- name: "sanctuary/shr_gateway_export",
12297
+ name: "shr_gateway_export",
11023
12298
  description: "Export this instance's Sovereignty Health Report formatted for Ping Identity's Agent Gateway or other identity providers. Transforms the SHR into an authorization context with sovereignty scores, capability flags, and recommended access constraints.",
11024
12299
  inputSchema: {
11025
12300
  type: "object",
@@ -11412,7 +12687,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11412
12687
  };
11413
12688
  const tools = [
11414
12689
  {
11415
- name: "sanctuary/handshake_initiate",
12690
+ name: "handshake_initiate",
11416
12691
  description: "Initiate a sovereignty handshake with a counterparty. Generates a challenge containing this instance's signed SHR and a cryptographic nonce. Send the returned challenge to the counterparty.",
11417
12692
  inputSchema: {
11418
12693
  type: "object",
@@ -11439,7 +12714,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11439
12714
  }
11440
12715
  },
11441
12716
  {
11442
- name: "sanctuary/handshake_respond",
12717
+ name: "handshake_respond",
11443
12718
  description: "Respond to an incoming sovereignty handshake challenge. Verifies the initiator's SHR, signs their nonce, and returns our SHR with a counter-nonce.",
11444
12719
  inputSchema: {
11445
12720
  type: "object",
@@ -11560,7 +12835,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11560
12835
  }
11561
12836
  },
11562
12837
  {
11563
- name: "sanctuary/handshake_complete",
12838
+ name: "handshake_complete",
11564
12839
  description: "Complete a sovereignty handshake (initiator side). Verifies the responder's SHR and nonce signature, signs their nonce, and produces the final result.",
11565
12840
  inputSchema: {
11566
12841
  type: "object",
@@ -11615,7 +12890,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11615
12890
  }
11616
12891
  },
11617
12892
  {
11618
- name: "sanctuary/handshake_status",
12893
+ name: "handshake_status",
11619
12894
  description: "Check the status of a handshake session, or verify a completion message (responder side).",
11620
12895
  inputSchema: {
11621
12896
  type: "object",
@@ -11665,7 +12940,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11665
12940
  },
11666
12941
  // ─── Streamlined Exchange ─────────────────────────────────────────
11667
12942
  {
11668
- name: "sanctuary/handshake_exchange",
12943
+ name: "handshake_exchange",
11669
12944
  description: "One-shot sovereignty exchange. Accepts a counterparty's signed SHR, verifies it, generates our SHR, and produces a signed attestation artifact \u2014 all in a single call. Returns a shareable attestation with human-readable summary. Use this instead of the 4-step handshake protocol when you want a quick, portable sovereignty verification (e.g., for social posting or async exchanges).",
11670
12945
  inputSchema: {
11671
12946
  type: "object",
@@ -11732,7 +13007,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11732
13007
  }
11733
13008
  },
11734
13009
  {
11735
- name: "sanctuary/handshake_verify_attestation",
13010
+ name: "handshake_verify_attestation",
11736
13011
  description: "Verify a signed attestation artifact from another agent. Checks the Ed25519 signature, temporal validity, and structural integrity.",
11737
13012
  inputSchema: {
11738
13013
  type: "object",
@@ -11944,7 +13219,7 @@ function createFederationTools(auditLog, handshakeResults) {
11944
13219
  const tools = [
11945
13220
  // ─── Peer Management ──────────────────────────────────────────────
11946
13221
  {
11947
- name: "sanctuary/federation_peers",
13222
+ name: "federation_peers",
11948
13223
  description: "List known federation peers, register a peer from a completed handshake, or remove a peer. Every peer MUST enter through a verified handshake \u2014 no self-registration allowed.",
11949
13224
  inputSchema: {
11950
13225
  type: "object",
@@ -12047,7 +13322,7 @@ function createFederationTools(auditLog, handshakeResults) {
12047
13322
  },
12048
13323
  // ─── Trust Evaluation ─────────────────────────────────────────────
12049
13324
  {
12050
- name: "sanctuary/federation_trust_evaluate",
13325
+ name: "federation_trust_evaluate",
12051
13326
  description: "Evaluate the trust level of a federation peer. Considers handshake status, sovereignty tier, reputation score, and mutual attestation history. Returns a composite trust assessment.",
12052
13327
  inputSchema: {
12053
13328
  type: "object",
@@ -12082,7 +13357,7 @@ function createFederationTools(auditLog, handshakeResults) {
12082
13357
  },
12083
13358
  // ─── Federation Status ────────────────────────────────────────────
12084
13359
  {
12085
- name: "sanctuary/federation_status",
13360
+ name: "federation_status",
12086
13361
  description: "Overview of federation state: total peers, active connections, trust distribution, and readiness for cross-instance operations.",
12087
13362
  inputSchema: {
12088
13363
  type: "object",
@@ -12295,7 +13570,7 @@ function createBridgeTools(storage, masterKey, identityManager, auditLog, handsh
12295
13570
  const tools = [
12296
13571
  // ─── bridge_commit ─────────────────────────────────────────────────
12297
13572
  {
12298
- name: "sanctuary/bridge_commit",
13573
+ name: "bridge_commit",
12299
13574
  description: "Create a cryptographic commitment binding a Concordia negotiation outcome to Sanctuary's L3 proof layer. The commitment includes a SHA-256 hash of the canonical outcome (hiding + binding), an Ed25519 signature by the committer's identity, and an optional Pedersen commitment on the round count for zero-knowledge range proofs. This is the Sanctuary side of the Concordia bridge \u2014 call this when a Concordia `accept` fires.",
12300
13575
  inputSchema: {
12301
13576
  type: "object",
@@ -12397,7 +13672,7 @@ function createBridgeTools(storage, masterKey, identityManager, auditLog, handsh
12397
13672
  },
12398
13673
  // ─── bridge_verify ───────────────────────────────────────────────────
12399
13674
  {
12400
- name: "sanctuary/bridge_verify",
13675
+ name: "bridge_verify",
12401
13676
  description: "Verify a bridge commitment against a revealed Concordia negotiation outcome. Checks SHA-256 commitment validity, Ed25519 signature, session ID match, terms hash integrity, and Pedersen commitment (if present). Use this to confirm that a counterparty's claimed negotiation outcome matches what was cryptographically committed.",
12402
13677
  inputSchema: {
12403
13678
  type: "object",
@@ -12453,7 +13728,7 @@ function createBridgeTools(storage, masterKey, identityManager, auditLog, handsh
12453
13728
  },
12454
13729
  // ─── bridge_attest ───────────────────────────────────────────────────
12455
13730
  {
12456
- name: "sanctuary/bridge_attest",
13731
+ name: "bridge_attest",
12457
13732
  description: "Record a Concordia negotiation as a Sanctuary L4 reputation attestation, linked to a bridge commitment. This completes the bridge: the commitment (L3) proves the terms were agreed, and the attestation (L4) feeds the sovereignty-weighted reputation score. The attestation is automatically tagged with the counterparty's sovereignty tier from any completed handshake.",
12458
13733
  inputSchema: {
12459
13734
  type: "object",
@@ -13017,7 +14292,7 @@ function generateGaps(env, l1, l2, l3, l4) {
13017
14292
  title: "No context gating for outbound inference calls",
13018
14293
  description: "Your agent sends its full context \u2014 conversation history, memory, preferences, internal reasoning \u2014 to remote LLM providers on every inference call. There is no mechanism to filter what leaves the sovereignty boundary. The provider sees everything the agent knows.",
13019
14294
  openclaw_relevance: env.openclaw_detected ? "OpenClaw sends full agent context (including MEMORY.md, tool results, and conversation history) to the configured LLM provider with every API call. There is no built-in context filtering." : null,
13020
- sanctuary_solution: "Sanctuary's context gating (sanctuary/context_gate_set_policy + sanctuary/context_gate_filter) lets you define per-provider policies that control exactly what context flows outbound. Redact secrets, hash identifiers, and send only minimum-necessary context for each call.",
14295
+ sanctuary_solution: "Sanctuary's context gating (sanctuary/context_gate_set_policy + context_gate_filter) lets you define per-provider policies that control exactly what context flows outbound. Redact secrets, hash identifiers, and send only minimum-necessary context for each call.",
13021
14296
  incident_class: INCIDENT_CONTEXT_LEAKAGE
13022
14297
  });
13023
14298
  }
@@ -13029,7 +14304,7 @@ function generateGaps(env, l1, l2, l3, l4) {
13029
14304
  title: "No audit trail",
13030
14305
  description: "No audit trail exists for tool call history. There is no record of what operations were executed, when, or by whom.",
13031
14306
  openclaw_relevance: null,
13032
- sanctuary_solution: "Sanctuary maintains an encrypted audit log of all operations, queryable via sanctuary/monitor_audit_log.",
14307
+ sanctuary_solution: "Sanctuary maintains an encrypted audit log of all operations, queryable via monitor_audit_log.",
13033
14308
  incident_class: INCIDENT_CLAUDE_CODE_LEAK
13034
14309
  });
13035
14310
  }
@@ -13064,7 +14339,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13064
14339
  recs.push({
13065
14340
  priority: 1,
13066
14341
  action: "Create a cryptographic identity \u2014 your agent's foundation for all sovereignty operations",
13067
- tool: "sanctuary/identity_create",
14342
+ tool: "identity_create",
13068
14343
  effort: "immediate",
13069
14344
  impact: "critical"
13070
14345
  });
@@ -13073,7 +14348,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13073
14348
  recs.push({
13074
14349
  priority: 2,
13075
14350
  action: "Migrate plaintext agent state to Sanctuary's encrypted store",
13076
- tool: "sanctuary/state_write",
14351
+ tool: "state_write",
13077
14352
  effort: "minutes",
13078
14353
  impact: "critical"
13079
14354
  });
@@ -13081,7 +14356,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13081
14356
  recs.push({
13082
14357
  priority: 3,
13083
14358
  action: "Generate a Sovereignty Health Report to present to counterparties",
13084
- tool: "sanctuary/shr_generate",
14359
+ tool: "shr_generate",
13085
14360
  effort: "immediate",
13086
14361
  impact: "high"
13087
14362
  });
@@ -13089,7 +14364,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13089
14364
  recs.push({
13090
14365
  priority: 4,
13091
14366
  action: "Enable the three-tier Principal Policy gate for graduated approval",
13092
- tool: "sanctuary/principal_policy_view",
14367
+ tool: "principal_policy_view",
13093
14368
  effort: "minutes",
13094
14369
  impact: "high"
13095
14370
  });
@@ -13098,7 +14373,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13098
14373
  recs.push({
13099
14374
  priority: 5,
13100
14375
  action: "Configure context gating to control what flows to LLM providers",
13101
- tool: "sanctuary/context_gate_set_policy",
14376
+ tool: "context_gate_set_policy",
13102
14377
  effort: "minutes",
13103
14378
  impact: "high"
13104
14379
  });
@@ -13107,7 +14382,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13107
14382
  recs.push({
13108
14383
  priority: 6,
13109
14384
  action: "Start recording reputation attestations from completed interactions",
13110
- tool: "sanctuary/reputation_record",
14385
+ tool: "reputation_record",
13111
14386
  effort: "minutes",
13112
14387
  impact: "medium"
13113
14388
  });
@@ -13116,7 +14391,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13116
14391
  recs.push({
13117
14392
  priority: 7,
13118
14393
  action: "Configure selective disclosure policies for data sharing",
13119
- tool: "sanctuary/disclosure_set_policy",
14394
+ tool: "disclosure_set_policy",
13120
14395
  effort: "hours",
13121
14396
  impact: "medium"
13122
14397
  });
@@ -13256,7 +14531,7 @@ function wordWrap(text, maxWidth) {
13256
14531
  function createAuditTools(config) {
13257
14532
  const tools = [
13258
14533
  {
13259
- name: "sanctuary/sovereignty_audit",
14534
+ name: "sovereignty_audit",
13260
14535
  description: "Audit your agent's sovereignty posture. Inspects the local environment for encryption, identity, approval gates, selective disclosure, and reputation \u2014 including OpenClaw-specific configurations. Returns a scored gap analysis with prioritized recommendations.",
13261
14536
  inputSchema: {
13262
14537
  type: "object",
@@ -13284,6 +14559,302 @@ function createAuditTools(config) {
13284
14559
  return { tools };
13285
14560
  }
13286
14561
 
14562
+ // src/audit/siem-formatter.ts
14563
+ function parseGateDecision(details) {
14564
+ if (!details || typeof details.gate_decision !== "string") {
14565
+ return "auto-allow";
14566
+ }
14567
+ const decision = details.gate_decision.toLowerCase();
14568
+ if (decision === "approve" || decision === "deny") {
14569
+ return decision;
14570
+ }
14571
+ return "auto-allow";
14572
+ }
14573
+ function parseTier(details) {
14574
+ if (!details || typeof details.tier !== "number") {
14575
+ return 3;
14576
+ }
14577
+ return Math.max(1, Math.min(3, details.tier));
14578
+ }
14579
+ function parseSessionId(details) {
14580
+ if (!details || typeof details.session_id !== "string") {
14581
+ return "unknown";
14582
+ }
14583
+ return details.session_id;
14584
+ }
14585
+ function parseAgentDid(details) {
14586
+ if (!details || typeof details.agent_did !== "string") {
14587
+ return "unknown";
14588
+ }
14589
+ return details.agent_did;
14590
+ }
14591
+ function gateToCEFSeverity(decision, tier) {
14592
+ if (decision === "deny") {
14593
+ return 8;
14594
+ }
14595
+ if (decision === "approve") {
14596
+ if (tier === 1) return 5;
14597
+ if (tier === 2) return 3;
14598
+ }
14599
+ return 1;
14600
+ }
14601
+ function formatAsCEF(entry, options) {
14602
+ const version = "0";
14603
+ const vendor = "Sanctuary";
14604
+ const product = "MCP-Server";
14605
+ const productVersion = "0.7.0";
14606
+ const decision = parseGateDecision(entry.details);
14607
+ const tier = parseTier(entry.details);
14608
+ const sessionId = parseSessionId(entry.details);
14609
+ const agentDid = parseAgentDid(entry.details);
14610
+ const severity = gateToCEFSeverity(decision, tier);
14611
+ const signatureId = entry.operation.replace(/[^a-zA-Z0-9_-]/g, "_");
14612
+ const description = `Sanctuary ${entry.operation}`;
14613
+ const extensions = [
14614
+ `src=${agentDid}`,
14615
+ `act=${entry.operation}`,
14616
+ `outcome=${decision}`,
14617
+ `tier=${tier}`,
14618
+ `cs1=${sessionId}`,
14619
+ `cs1Label=SessionId`,
14620
+ `rt=${new Date(entry.timestamp).getTime()}`,
14621
+ `layer=${entry.layer}`,
14622
+ `result=${entry.result}`
14623
+ ];
14624
+ return `CEF:${version}|${vendor}|${product}|${productVersion}|${signatureId}|${description}|${severity}|${extensions.join(" ")}`;
14625
+ }
14626
+ function gateToOCSFStatus(decision, result) {
14627
+ return decision === "deny" || result === "failure" ? 2 : 1;
14628
+ }
14629
+ function gateToCOCSFSeverity(decision, tier) {
14630
+ if (decision === "deny") {
14631
+ return 4;
14632
+ }
14633
+ if (decision === "approve") {
14634
+ if (tier === 1) return 3;
14635
+ if (tier === 2) return 2;
14636
+ }
14637
+ return 1;
14638
+ }
14639
+ function gateToOCSFDisposition(decision) {
14640
+ return decision === "deny" ? 2 : 1;
14641
+ }
14642
+ function formatAsOCSF(entry) {
14643
+ const decision = parseGateDecision(entry.details);
14644
+ const tier = parseTier(entry.details);
14645
+ const agentDid = parseAgentDid(entry.details);
14646
+ const timestamp = new Date(entry.timestamp).getTime();
14647
+ const statusId = gateToOCSFStatus(decision, entry.result);
14648
+ const severityId = gateToCOCSFSeverity(decision, tier);
14649
+ const dispositionId = gateToOCSFDisposition(decision);
14650
+ return {
14651
+ class_uid: 3001,
14652
+ class_name: "API Activity",
14653
+ category_uid: 3,
14654
+ category_name: "Application Activity",
14655
+ severity_id: severityId,
14656
+ time: timestamp,
14657
+ activity_id: 1,
14658
+ activity_name: "API Call",
14659
+ actor: {
14660
+ user: {
14661
+ uid: agentDid
14662
+ }
14663
+ },
14664
+ api: {
14665
+ operation: entry.operation,
14666
+ service: {
14667
+ name: "sanctuary-mcp"
14668
+ }
14669
+ },
14670
+ status_id: statusId,
14671
+ disposition_id: dispositionId,
14672
+ metadata: {
14673
+ version: "1.3.0",
14674
+ product: {
14675
+ name: "Sanctuary Framework",
14676
+ vendor_name: "Erik Newton",
14677
+ version: "0.7.0"
14678
+ }
14679
+ }
14680
+ };
14681
+ }
14682
+
14683
+ // src/audit/siem-tools.ts
14684
+ function createSIEMTools(auditLog) {
14685
+ const tools = [
14686
+ {
14687
+ name: "audit_export_siem",
14688
+ description: "Export audit log events in SIEM-standard formats (CEF or OCSF) for ingestion into Splunk, Datadog, QRadar, and other security information and event management (SIEM) platforms. Encrypted audit entries are decrypted and formatted according to your chosen standard. Tier 2 \u2014 may contain sensitive operation metadata.",
14689
+ inputSchema: {
14690
+ type: "object",
14691
+ properties: {
14692
+ format: {
14693
+ type: "string",
14694
+ enum: ["cef", "ocsf"],
14695
+ description: 'Output format: "cef" (Common Event Format, newline-delimited) or "ocsf" (Open Cybersecurity Schema Framework, JSON array)'
14696
+ },
14697
+ since: {
14698
+ type: "string",
14699
+ description: "Optional ISO 8601 timestamp. Export only events on or after this time. Defaults to 24 hours ago."
14700
+ },
14701
+ until: {
14702
+ type: "string",
14703
+ description: "Optional ISO 8601 timestamp. Export only events before this time. Defaults to now."
14704
+ },
14705
+ limit: {
14706
+ type: "number",
14707
+ description: "Maximum number of events to export (default 100, max 1000). Set to 1000 for bulk exports to SIEMs."
14708
+ },
14709
+ filter_tool: {
14710
+ type: "string",
14711
+ description: 'Optional. Export only events from this tool name (e.g., "sovereignty_audit", "state_set"). Case-insensitive substring matching.'
14712
+ },
14713
+ filter_decision: {
14714
+ type: "string",
14715
+ enum: ["approve", "deny", "auto-allow"],
14716
+ description: 'Optional. Export only events with this gate decision: "approve" (manual approval), "deny" (blocked), or "auto-allow" (Tier 3 auto-allowed).'
14717
+ },
14718
+ filter_layer: {
14719
+ type: "string",
14720
+ enum: ["l1", "l2", "l3", "l4"],
14721
+ description: "Optional. Export only events from this sovereignty layer (L1=Cognitive, L2=Operational, L3=Disclosure, L4=Reputation)."
14722
+ },
14723
+ filter_result: {
14724
+ type: "string",
14725
+ enum: ["success", "failure"],
14726
+ description: 'Optional. Export only events with this result: "success" or "failure".'
14727
+ }
14728
+ },
14729
+ required: ["format"]
14730
+ },
14731
+ handler: async (args) => {
14732
+ const format = String(args.format || "").toLowerCase();
14733
+ if (format !== "cef" && format !== "ocsf") {
14734
+ return {
14735
+ content: [
14736
+ {
14737
+ type: "text",
14738
+ text: JSON.stringify({
14739
+ error: "Invalid format. Must be 'cef' or 'ocsf'."
14740
+ })
14741
+ }
14742
+ ]
14743
+ };
14744
+ }
14745
+ let since;
14746
+ if (args.since) {
14747
+ since = String(args.since);
14748
+ const sinceDate = new Date(since);
14749
+ if (isNaN(sinceDate.getTime())) {
14750
+ return {
14751
+ content: [
14752
+ {
14753
+ type: "text",
14754
+ text: JSON.stringify({
14755
+ error: `Invalid 'since' timestamp: ${since}. Must be ISO 8601.`
14756
+ })
14757
+ }
14758
+ ]
14759
+ };
14760
+ }
14761
+ } else {
14762
+ const now = /* @__PURE__ */ new Date();
14763
+ const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
14764
+ since = oneDayAgo.toISOString();
14765
+ }
14766
+ let until;
14767
+ if (args.until) {
14768
+ until = String(args.until);
14769
+ const untilDate = new Date(until);
14770
+ if (isNaN(untilDate.getTime())) {
14771
+ return {
14772
+ content: [
14773
+ {
14774
+ type: "text",
14775
+ text: JSON.stringify({
14776
+ error: `Invalid 'until' timestamp: ${until}. Must be ISO 8601.`
14777
+ })
14778
+ }
14779
+ ]
14780
+ };
14781
+ }
14782
+ }
14783
+ let limit = 100;
14784
+ if (typeof args.limit === "number") {
14785
+ limit = Math.max(1, Math.min(1e3, args.limit));
14786
+ }
14787
+ const filterTool = args.filter_tool ? String(args.filter_tool).toLowerCase() : void 0;
14788
+ const filterDecision = args.filter_decision ? String(args.filter_decision).toLowerCase() : void 0;
14789
+ const filterLayer = args.filter_layer ? String(args.filter_layer).toLowerCase() : void 0;
14790
+ const filterResult = args.filter_result ? String(args.filter_result).toLowerCase() : void 0;
14791
+ const result = await auditLog.query({
14792
+ since,
14793
+ layer: filterLayer,
14794
+ operation_type: void 0,
14795
+ // Will filter after
14796
+ limit
14797
+ });
14798
+ let filtered = result.entries;
14799
+ if (filterTool) {
14800
+ filtered = filtered.filter(
14801
+ (e) => e.operation.toLowerCase().includes(filterTool)
14802
+ );
14803
+ }
14804
+ if (filterDecision) {
14805
+ filtered = filtered.filter((e) => {
14806
+ const decision = String(e.details?.gate_decision || "auto-allow").toLowerCase();
14807
+ return decision === filterDecision;
14808
+ });
14809
+ }
14810
+ if (filterResult) {
14811
+ filtered = filtered.filter((e) => e.result === filterResult);
14812
+ }
14813
+ if (until) {
14814
+ const untilDate = new Date(until);
14815
+ filtered = filtered.filter((e) => new Date(e.timestamp) < untilDate);
14816
+ }
14817
+ let output;
14818
+ if (format === "cef") {
14819
+ const cefLines = filtered.map((entry) => formatAsCEF(entry));
14820
+ output = cefLines.join("\n");
14821
+ } else {
14822
+ const ocsfObjects = filtered.map((entry) => formatAsOCSF(entry));
14823
+ output = JSON.stringify(ocsfObjects, null, 2);
14824
+ }
14825
+ return {
14826
+ content: [
14827
+ {
14828
+ type: "text",
14829
+ text: JSON.stringify({
14830
+ format,
14831
+ count: filtered.length,
14832
+ total_available: result.total,
14833
+ time_range: {
14834
+ since,
14835
+ until: until || (/* @__PURE__ */ new Date()).toISOString()
14836
+ },
14837
+ filters: {
14838
+ tool: filterTool,
14839
+ decision: filterDecision,
14840
+ layer: filterLayer,
14841
+ result: filterResult
14842
+ },
14843
+ note: format === "cef" ? `${filtered.length} CEF events (newline-delimited). Each line is a complete CEF event.` : `${filtered.length} OCSF objects in JSON array format.`
14844
+ })
14845
+ },
14846
+ {
14847
+ type: "text",
14848
+ text: output
14849
+ }
14850
+ ]
14851
+ };
14852
+ }
14853
+ }
14854
+ ];
14855
+ return { tools };
14856
+ }
14857
+
13287
14858
  // src/l2-operational/context-gate-tools.ts
13288
14859
  init_router();
13289
14860
 
@@ -14285,13 +15856,17 @@ var ContextGateEnforcer = class {
14285
15856
  * Check if a tool should be filtered based on bypass prefixes.
14286
15857
  *
14287
15858
  * SEC-033: Uses exact namespace component matching, not bare startsWith().
14288
- * A prefix of "sanctuary/" matches "sanctuary/state_read" but NOT
14289
- * "sanctuary_evil/steal_data" (no slash boundary confusion). The prefix
14290
- * must match exactly up to its length, and the prefix must end with "/"
14291
- * to enforce namespace boundaries (if it doesn't, we add one for safety).
15859
+ * A prefix of "proxy/" matches "proxy/server/tool" but NOT "proxyevil/steal".
15860
+ * The prefix must match exactly up to its length, and the prefix must end
15861
+ * with "/" to enforce namespace boundaries (if it doesn't, we add one).
15862
+ *
15863
+ * Special sentinel: "*" bypasses ALL tools (used when all Sanctuary-internal
15864
+ * tools should skip context gating — the default). Only proxy/external tools
15865
+ * should be filtered in production.
14292
15866
  */
14293
15867
  shouldFilter(toolName) {
14294
15868
  for (const prefix of this.config.bypass_prefixes) {
15869
+ if (prefix === "*") return false;
14295
15870
  const safePrefix = prefix.endsWith("/") ? prefix : prefix + "/";
14296
15871
  if (toolName === safePrefix.slice(0, -1) || toolName.startsWith(safePrefix)) {
14297
15872
  return false;
@@ -14388,8 +15963,8 @@ function createContextGateTools(storage, masterKey, auditLog) {
14388
15963
  const enforcerConfig = {
14389
15964
  enabled: false,
14390
15965
  // Off by default; agents must explicitly enable it
14391
- bypass_prefixes: ["sanctuary/"],
14392
- // Skip internal tools by default
15966
+ bypass_prefixes: ["*"],
15967
+ // Skip all Sanctuary-internal tools; only proxy/ tools get filtered
14393
15968
  log_only: false,
14394
15969
  // Filter immediately
14395
15970
  on_deny: "block"
@@ -14399,7 +15974,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14399
15974
  const tools = [
14400
15975
  // ── Set Policy ──────────────────────────────────────────────────
14401
15976
  {
14402
- name: "sanctuary/context_gate_set_policy",
15977
+ name: "context_gate_set_policy",
14403
15978
  description: "Create a context-gating policy that controls what information flows to remote providers (LLM APIs, tool APIs, logging services). Each rule specifies a provider category and which context fields to allow, redact, hash, or flag for summarization. Redact rules take absolute priority \u2014 if a field is in both 'allow' and 'redact', it is redacted. Default action applies to any field not mentioned in any rule. Use this to prevent your full agent context from being sent to remote LLM providers during inference calls.",
14404
15979
  inputSchema: {
14405
15980
  type: "object",
@@ -14508,13 +16083,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14508
16083
  rules: policy.rules,
14509
16084
  default_action: policy.default_action,
14510
16085
  created_at: policy.created_at,
14511
- message: "Context-gating policy created. Use sanctuary/context_gate_filter to apply this policy before making outbound calls."
16086
+ message: "Context-gating policy created. Use context_gate_filter to apply this policy before making outbound calls."
14512
16087
  });
14513
16088
  }
14514
16089
  },
14515
16090
  // ── Apply Template ───────────────────────────────────────────────
14516
16091
  {
14517
- name: "sanctuary/context_gate_apply_template",
16092
+ name: "context_gate_apply_template",
14518
16093
  description: "Apply a starter context-gating template. Available templates: inference-minimal (strictest \u2014 only task and query pass through), inference-standard (balanced \u2014 adds tool results, summarizes history), logging-strict (redacts all content for telemetry services), tool-api-scoped (allows tool parameters, redacts agent state). Templates are starting points \u2014 customize after applying.",
14519
16094
  inputSchema: {
14520
16095
  type: "object",
@@ -14563,13 +16138,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14563
16138
  rules: policy.rules,
14564
16139
  default_action: policy.default_action,
14565
16140
  created_at: policy.created_at,
14566
- message: "Template applied. Use sanctuary/context_gate_filter with this policy_id to filter context before outbound calls. Customize rules with sanctuary/context_gate_set_policy if needed."
16141
+ message: "Template applied. Use context_gate_filter with this policy_id to filter context before outbound calls. Customize rules with context_gate_set_policy if needed."
14567
16142
  });
14568
16143
  }
14569
16144
  },
14570
16145
  // ── Recommend Policy ────────────────────────────────────────────
14571
16146
  {
14572
- name: "sanctuary/context_gate_recommend",
16147
+ name: "context_gate_recommend",
14573
16148
  description: "Analyze a sample context object and recommend a context-gating policy based on field name heuristics. Classifies each field as allow, redact, hash, or summarize with confidence levels. Returns a ready-to-apply rule set. When in doubt, recommends redact (conservative). Review the recommendations before applying.",
14574
16149
  inputSchema: {
14575
16150
  type: "object",
@@ -14606,7 +16181,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14606
16181
  });
14607
16182
  return toolResult({
14608
16183
  ...recommendation,
14609
- next_steps: "Review the classifications above. If they look correct, you can apply them directly with sanctuary/context_gate_set_policy using the recommended_rules. Or start with a template via sanctuary/context_gate_apply_template and customize from there.",
16184
+ next_steps: "Review the classifications above. If they look correct, you can apply them directly with context_gate_set_policy using the recommended_rules. Or start with a template via context_gate_apply_template and customize from there.",
14610
16185
  available_templates: listTemplateIds().map((id) => {
14611
16186
  const t = TEMPLATES[id];
14612
16187
  return { id, name: t.name, description: t.description };
@@ -14616,7 +16191,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14616
16191
  },
14617
16192
  // ── Filter Context ──────────────────────────────────────────────
14618
16193
  {
14619
- name: "sanctuary/context_gate_filter",
16194
+ name: "context_gate_filter",
14620
16195
  description: "Filter agent context through a gating policy before sending to a remote provider. Returns per-field decisions (allow, redact, hash, summarize) and content hashes for the audit trail. Call this BEFORE making any outbound API call to ensure you are only sending the minimum necessary context. The filtered output tells you exactly what can be sent safely.",
14621
16196
  inputSchema: {
14622
16197
  type: "object",
@@ -14722,7 +16297,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14722
16297
  },
14723
16298
  // ── List Policies ───────────────────────────────────────────────
14724
16299
  {
14725
- name: "sanctuary/context_gate_list_policies",
16300
+ name: "context_gate_list_policies",
14726
16301
  description: "List all configured context-gating policies. Returns policy IDs, names, rule summaries, and default actions.",
14727
16302
  inputSchema: {
14728
16303
  type: "object",
@@ -14745,13 +16320,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14745
16320
  updated_at: p.updated_at
14746
16321
  })),
14747
16322
  count: policies.length,
14748
- message: policies.length === 0 ? "No context-gating policies configured. Use sanctuary/context_gate_set_policy to create one." : `${policies.length} context-gating ${policies.length === 1 ? "policy" : "policies"} configured.`
16323
+ message: policies.length === 0 ? "No context-gating policies configured. Use context_gate_set_policy to create one." : `${policies.length} context-gating ${policies.length === 1 ? "policy" : "policies"} configured.`
14749
16324
  });
14750
16325
  }
14751
16326
  },
14752
16327
  // ── Enforcer Status ─────────────────────────────────────────────────
14753
16328
  {
14754
- name: "sanctuary/context_gate_enforcer_status",
16329
+ name: "context_gate_enforcer_status",
14755
16330
  description: "Get the status of the automatic context gate enforcer, including enabled/disabled state, log_only mode, active policy, and statistics. The enforcer automatically filters tool arguments when enabled. Use this to monitor what the enforcer has been filtering.",
14756
16331
  inputSchema: {
14757
16332
  type: "object",
@@ -14772,13 +16347,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14772
16347
  return toolResult({
14773
16348
  enforcer_status: status,
14774
16349
  description: "The enforcer is " + (status.enabled ? "enabled" : "disabled") + ". " + (status.log_only ? "Currently in log_only mode \u2014 filtering is logged but not applied." : "Filtering is actively applied to tool arguments."),
14775
- guidance: status.stats.calls_inspected > 0 ? `Over ${status.stats.calls_inspected} tool calls, ${status.stats.fields_redacted} sensitive fields were redacted. Use sanctuary/context_gate_enforcer_configure to adjust settings.` : "No tool calls have been inspected yet."
16350
+ guidance: status.stats.calls_inspected > 0 ? `Over ${status.stats.calls_inspected} tool calls, ${status.stats.fields_redacted} sensitive fields were redacted. Use context_gate_enforcer_configure to adjust settings.` : "No tool calls have been inspected yet."
14776
16351
  });
14777
16352
  }
14778
16353
  },
14779
16354
  // ── Enforcer Configuration ──────────────────────────────────────────
14780
16355
  {
14781
- name: "sanctuary/context_gate_enforcer_configure",
16356
+ name: "context_gate_enforcer_configure",
14782
16357
  description: "Configure the automatic context gate enforcer. Control whether it filters tool arguments, toggle log_only mode for gradual rollout, set the active policy, and choose what to do when denied fields are encountered (block the request or redact the field). Use this to enable automatic context protection.",
14783
16358
  inputSchema: {
14784
16359
  type: "object",
@@ -15104,7 +16679,7 @@ function assessL2Hardening(storagePath) {
15104
16679
  function createL2HardeningTools(storagePath, auditLog) {
15105
16680
  return [
15106
16681
  {
15107
- name: "sanctuary/l2_hardening_status",
16682
+ name: "l2_hardening_status",
15108
16683
  description: "L2 Process Hardening Status \u2014 Verify software-based operational isolation. Reports memory protection, process isolation level, filesystem permissions, and overall hardening assessment. Read-only. Tier 3 \u2014 always allowed.",
15109
16684
  inputSchema: {
15110
16685
  type: "object",
@@ -15172,7 +16747,7 @@ function createL2HardeningTools(storagePath, auditLog) {
15172
16747
  }
15173
16748
  },
15174
16749
  {
15175
- name: "sanctuary/l2_verify_isolation",
16750
+ name: "l2_verify_isolation",
15176
16751
  description: "Verify L2 process isolation at runtime. Checks whether the Sanctuary server is running in an isolated environment (container, VM, sandbox) and validates filesystem and memory protections. Reports isolation level and any issues. Read-only. Tier 3 \u2014 always allowed.",
15177
16752
  inputSchema: {
15178
16753
  type: "object",
@@ -15274,7 +16849,7 @@ function createSovereigntyProfileTools(profileStore, auditLog) {
15274
16849
  const tools = [
15275
16850
  // ── Get Profile ──────────────────────────────────────────────────
15276
16851
  {
15277
- name: "sanctuary/sovereignty_profile_get",
16852
+ name: "sovereignty_profile_get",
15278
16853
  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.",
15279
16854
  inputSchema: {
15280
16855
  type: "object",
@@ -15293,7 +16868,7 @@ function createSovereigntyProfileTools(profileStore, auditLog) {
15293
16868
  },
15294
16869
  // ── Update Profile ───────────────────────────────────────────────
15295
16870
  {
15296
- name: "sanctuary/sovereignty_profile_update",
16871
+ name: "sovereignty_profile_update",
15297
16872
  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.",
15298
16873
  inputSchema: {
15299
16874
  type: "object",
@@ -15374,7 +16949,7 @@ function createSovereigntyProfileTools(profileStore, auditLog) {
15374
16949
  },
15375
16950
  // ── Generate System Prompt ───────────────────────────────────────
15376
16951
  {
15377
- name: "sanctuary/sovereignty_profile_generate_prompt",
16952
+ name: "sovereignty_profile_generate_prompt",
15378
16953
  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.",
15379
16954
  inputSchema: {
15380
16955
  type: "object",
@@ -15769,6 +17344,7 @@ var ProxyRouter = class {
15769
17344
  confidence: injectionResult.confidence,
15770
17345
  latency_ms: Date.now() - start
15771
17346
  }, "failure");
17347
+ this.notifyProxyCall(proxyName, serverName, "blocked", "injection_detected", tier);
15772
17348
  return toolResult({
15773
17349
  error: "Operation not permitted",
15774
17350
  proxy: true
@@ -15799,6 +17375,7 @@ var ProxyRouter = class {
15799
17375
  reason: govResult.reason,
15800
17376
  latency_ms: Date.now() - start
15801
17377
  }, "failure");
17378
+ this.notifyProxyCall(proxyName, serverName, "blocked", govResult.reason, tier);
15802
17379
  return toolResult({
15803
17380
  error: "Operation not permitted",
15804
17381
  proxy: true,
@@ -15833,6 +17410,7 @@ var ProxyRouter = class {
15833
17410
  decision: "allowed",
15834
17411
  latency_ms: latencyMs
15835
17412
  });
17413
+ this.notifyProxyCall(proxyName, serverName, "allowed", void 0, tier);
15836
17414
  return this.normalizeResponse(result);
15837
17415
  } catch (err) {
15838
17416
  const latencyMs = Date.now() - start;
@@ -15852,6 +17430,7 @@ var ProxyRouter = class {
15852
17430
  error: errorMessage,
15853
17431
  latency_ms: latencyMs
15854
17432
  }, "failure");
17433
+ this.notifyProxyCall(proxyName, serverName, "error", errorMessage, tier);
15855
17434
  return {
15856
17435
  content: [{
15857
17436
  type: "text",
@@ -15866,6 +17445,24 @@ var ProxyRouter = class {
15866
17445
  }
15867
17446
  };
15868
17447
  }
17448
+ /**
17449
+ * Notify the onProxyCall callback if configured.
17450
+ */
17451
+ notifyProxyCall(tool, server, decision, reason, tier) {
17452
+ if (this.options.onProxyCall) {
17453
+ try {
17454
+ this.options.onProxyCall({
17455
+ tool,
17456
+ server,
17457
+ decision,
17458
+ reason,
17459
+ tier,
17460
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17461
+ });
17462
+ } catch {
17463
+ }
17464
+ }
17465
+ }
15869
17466
  /**
15870
17467
  * Call an upstream tool with a timeout.
15871
17468
  */
@@ -16139,7 +17736,7 @@ function createGovernorTools(governor, auditLog) {
16139
17736
  const tools = [
16140
17737
  // ── Governor Status ─────────────────────────────────────────────
16141
17738
  {
16142
- name: "sanctuary/governor_status",
17739
+ name: "governor_status",
16143
17740
  description: "View the current Call Governor status including volume counters, per-tool rate counts, duplicate cache size, and lifetime counter. Use this to monitor tool call consumption, detect potential loops, and check how close you are to governance limits. The governor protects against runaway tool calls by enforcing volume limits, rate limits, duplicate detection, and a session lifetime cap.",
16144
17741
  inputSchema: {
16145
17742
  type: "object",
@@ -16179,7 +17776,7 @@ function createGovernorTools(governor, auditLog) {
16179
17776
  },
16180
17777
  // ── Governor Reset ──────────────────────────────────────────────
16181
17778
  {
16182
- name: "sanctuary/governor_reset",
17779
+ name: "governor_reset",
16183
17780
  description: "Reset all Call Governor counters: volume window, per-tool rate windows, duplicate cache, and lifetime counter. This clears the hard stop if the lifetime limit was reached. This is a Tier 1 operation \u2014 requires human approval because it removes all runtime governance state and could allow previously blocked behavior to resume.",
16184
17781
  inputSchema: {
16185
17782
  type: "object",
@@ -16272,7 +17869,7 @@ function createSanctuaryTools(opts) {
16272
17869
  const tools = [
16273
17870
  // ─── sanctuary_bootstrap ───────────────────────────────────────────
16274
17871
  {
16275
- name: "sanctuary/sanctuary_bootstrap",
17872
+ name: "sanctuary_bootstrap",
16276
17873
  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.",
16277
17874
  inputSchema: {
16278
17875
  type: "object",
@@ -16410,7 +18007,7 @@ function createSanctuaryTools(opts) {
16410
18007
  },
16411
18008
  // ─── sanctuary_policy_status ───────────────────────────────────────
16412
18009
  {
16413
- name: "sanctuary/sanctuary_policy_status",
18010
+ name: "sanctuary_policy_status",
16414
18011
  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).",
16415
18012
  inputSchema: {
16416
18013
  type: "object",
@@ -16440,7 +18037,7 @@ function createSanctuaryTools(opts) {
16440
18037
  },
16441
18038
  // ─── sanctuary_export_identity_bundle ──────────────────────────────
16442
18039
  {
16443
- name: "sanctuary/sanctuary_export_identity_bundle",
18040
+ name: "sanctuary_export_identity_bundle",
16444
18041
  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.",
16445
18042
  inputSchema: {
16446
18043
  type: "object",
@@ -16509,7 +18106,7 @@ function createSanctuaryTools(opts) {
16509
18106
  },
16510
18107
  // ─── sanctuary_link_to_human ───────────────────────────────────────
16511
18108
  {
16512
- name: "sanctuary/sanctuary_link_to_human",
18109
+ name: "sanctuary_link_to_human",
16513
18110
  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.",
16514
18111
  inputSchema: {
16515
18112
  type: "object",
@@ -16564,7 +18161,7 @@ function createSanctuaryTools(opts) {
16564
18161
  },
16565
18162
  // ─── sanctuary_sign_challenge ──────────────────────────────────────
16566
18163
  {
16567
- name: "sanctuary/sanctuary_sign_challenge",
18164
+ name: "sanctuary_sign_challenge",
16568
18165
  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.",
16569
18166
  inputSchema: {
16570
18167
  type: "object",
@@ -16798,7 +18395,7 @@ async function createSanctuaryServer(options) {
16798
18395
  }
16799
18396
  const l2Tools = [
16800
18397
  {
16801
- name: "sanctuary/exec_attest",
18398
+ name: "exec_attest",
16802
18399
  description: "Generate an attestation of the current execution environment, including sovereignty assessment and degradation report.",
16803
18400
  inputSchema: {
16804
18401
  type: "object",
@@ -16849,7 +18446,7 @@ async function createSanctuaryServer(options) {
16849
18446
  }
16850
18447
  },
16851
18448
  {
16852
- name: "sanctuary/monitor_health",
18449
+ name: "monitor_health",
16853
18450
  description: "Sanctuary Health Report (SHR) \u2014 standardized sovereignty status.",
16854
18451
  inputSchema: { type: "object", properties: {} },
16855
18452
  handler: async () => {
@@ -16898,7 +18495,7 @@ async function createSanctuaryServer(options) {
16898
18495
  }
16899
18496
  },
16900
18497
  {
16901
- name: "sanctuary/monitor_audit_log",
18498
+ name: "monitor_audit_log",
16902
18499
  description: "Query the sovereignty audit log.",
16903
18500
  inputSchema: {
16904
18501
  type: "object",
@@ -16924,7 +18521,7 @@ async function createSanctuaryServer(options) {
16924
18521
  }
16925
18522
  ];
16926
18523
  const manifestTool = {
16927
- name: "sanctuary/manifest",
18524
+ name: "manifest",
16928
18525
  description: "Generate the Sanctuary Interface Manifest (SIM) \u2014 a machine-readable declaration of this server's capabilities.",
16929
18526
  inputSchema: { type: "object", properties: {} },
16930
18527
  handler: async () => {
@@ -17036,6 +18633,7 @@ async function createSanctuaryServer(options) {
17036
18633
  handshakeResults
17037
18634
  );
17038
18635
  const { tools: auditTools } = createAuditTools(config);
18636
+ const { tools: siemTools } = createSIEMTools(auditLog);
17039
18637
  const { tools: contextGateTools, enforcer: contextGateEnforcer } = createContextGateTools(storage, masterKey, auditLog);
17040
18638
  const hardeningTools = createL2HardeningTools(config.storage_path, auditLog);
17041
18639
  const profileStore = new SovereigntyProfileStore(storage, masterKey);
@@ -17118,7 +18716,7 @@ async function createSanctuaryServer(options) {
17118
18716
  const dashboardTools = [];
17119
18717
  if (dashboard) {
17120
18718
  dashboardTools.push({
17121
- name: "sanctuary/dashboard_open",
18719
+ name: "dashboard_open",
17122
18720
  description: "Generate a one-click URL to open the Principal Dashboard in a browser. Returns a pre-authenticated link \u2014 no manual token entry needed.",
17123
18721
  inputSchema: {
17124
18722
  type: "object",
@@ -17152,6 +18750,7 @@ async function createSanctuaryServer(options) {
17152
18750
  ...federationTools,
17153
18751
  ...bridgeTools,
17154
18752
  ...auditTools,
18753
+ ...siemTools,
17155
18754
  ...contextGateTools,
17156
18755
  ...hardeningTools,
17157
18756
  ...profileTools,
@@ -17198,7 +18797,12 @@ async function createSanctuaryServer(options) {
17198
18797
  }
17199
18798
  return args;
17200
18799
  },
17201
- governor
18800
+ governor,
18801
+ onProxyCall: (data) => {
18802
+ if (dashboard) {
18803
+ dashboard.broadcastProxyCall(data);
18804
+ }
18805
+ }
17202
18806
  }
17203
18807
  );
17204
18808
  clientManager.configure(enabledServers).catch((err) => {
@@ -17216,6 +18820,7 @@ async function createSanctuaryServer(options) {
17216
18820
  auditLog,
17217
18821
  clientManager
17218
18822
  });
18823
+ dashboard.enableFortressView(enabledServers.length);
17219
18824
  }
17220
18825
  }
17221
18826
  }
@@ -17335,6 +18940,12 @@ async function main() {
17335
18940
  await runStandaloneDashboard(args.slice(1));
17336
18941
  return;
17337
18942
  }
18943
+ if (args[0] === "cocoon") {
18944
+ const { parseCocoonArgs: parseCocoonArgs2, runCocoon: runCocoon2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
18945
+ const cocoonOpts = parseCocoonArgs2(args.slice(1));
18946
+ await runCocoon2(cocoonOpts);
18947
+ return;
18948
+ }
17338
18949
  for (let i = 0; i < args.length; i++) {
17339
18950
  if (args[i] === "--dashboard") {
17340
18951
  process.env.SANCTUARY_DASHBOARD_ENABLED = "true";
@@ -17402,6 +19013,7 @@ Sovereignty infrastructure for agents in the agentic economy.
17402
19013
  Usage:
17403
19014
  sanctuary-mcp-server [options] # MCP server (stdio)
17404
19015
  sanctuary-mcp-server dashboard [opts] # Standalone dashboard
19016
+ sanctuary-mcp-server cocoon [opts] # Wrap agent in Cocoon protection
17405
19017
 
17406
19018
  Options:
17407
19019
  --dashboard Enable the Principal Dashboard (web UI)
@@ -17414,6 +19026,10 @@ Subcommands:
17414
19026
  Reads from the same storage as the MCP server.
17415
19027
  Use "sanctuary-mcp-server dashboard --help" for options.
17416
19028
 
19029
+ cocoon Wrap an existing agent in Sanctuary's enforcement chain.
19030
+ One command to protect any MCP-compatible agent.
19031
+ Use "sanctuary-mcp-server cocoon --help" for options.
19032
+
17417
19033
  Environment variables:
17418
19034
  SANCTUARY_STORAGE_PATH State directory (default: ~/.sanctuary)
17419
19035
  SANCTUARY_PASSPHRASE Key derivation passphrase