@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.cjs CHANGED
@@ -965,7 +965,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
965
965
  const tools = [
966
966
  // ── Identity Tools ──────────────────────────────────────────────────
967
967
  {
968
- name: "sanctuary/identity_create",
968
+ name: "identity_create",
969
969
  description: "Create a new sovereign identity (Ed25519 keypair). The private key is encrypted and never exposed.",
970
970
  inputSchema: {
971
971
  type: "object",
@@ -1000,7 +1000,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1000
1000
  }
1001
1001
  },
1002
1002
  {
1003
- name: "sanctuary/identity_list",
1003
+ name: "identity_list",
1004
1004
  description: "List all managed sovereign identities.",
1005
1005
  inputSchema: {
1006
1006
  type: "object",
@@ -1025,7 +1025,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1025
1025
  }
1026
1026
  },
1027
1027
  {
1028
- name: "sanctuary/identity_sign",
1028
+ name: "identity_sign",
1029
1029
  description: "Sign data with a managed identity. The private key is decrypted in memory only during signing.",
1030
1030
  inputSchema: {
1031
1031
  type: "object",
@@ -1063,7 +1063,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1063
1063
  }
1064
1064
  },
1065
1065
  {
1066
- name: "sanctuary/identity_verify",
1066
+ name: "identity_verify",
1067
1067
  description: "Verify an Ed25519 signature. Provide either identity_id or public_key.",
1068
1068
  inputSchema: {
1069
1069
  type: "object",
@@ -1112,7 +1112,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1112
1112
  }
1113
1113
  },
1114
1114
  {
1115
- name: "sanctuary/identity_rotate",
1115
+ name: "identity_rotate",
1116
1116
  description: "Rotate keys for an identity. Generates a new keypair and signs a rotation event with the old key for verifiable chain.",
1117
1117
  inputSchema: {
1118
1118
  type: "object",
@@ -1145,7 +1145,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1145
1145
  },
1146
1146
  // ── State Tools ─────────────────────────────────────────────────────
1147
1147
  {
1148
- name: "sanctuary/state_write",
1148
+ name: "state_write",
1149
1149
  description: "Write encrypted state to the sovereign store. Value is encrypted with a namespace-specific key. The write is signed by the active identity.",
1150
1150
  inputSchema: {
1151
1151
  type: "object",
@@ -1202,7 +1202,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1202
1202
  }
1203
1203
  },
1204
1204
  {
1205
- name: "sanctuary/state_read",
1205
+ name: "state_read",
1206
1206
  description: "Read and decrypt state from the sovereign store. Verifies integrity via Merkle proof and signature.",
1207
1207
  inputSchema: {
1208
1208
  type: "object",
@@ -1243,7 +1243,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1243
1243
  }
1244
1244
  },
1245
1245
  {
1246
- name: "sanctuary/state_list",
1246
+ name: "state_list",
1247
1247
  description: "List keys in a namespace (metadata only \u2014 no decryption).",
1248
1248
  inputSchema: {
1249
1249
  type: "object",
@@ -1275,7 +1275,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1275
1275
  }
1276
1276
  },
1277
1277
  {
1278
- name: "sanctuary/state_delete",
1278
+ name: "state_delete",
1279
1279
  description: "Securely delete state. Overwrites file with random bytes before removal (right to deletion, S1.6).",
1280
1280
  inputSchema: {
1281
1281
  type: "object",
@@ -1307,7 +1307,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1307
1307
  }
1308
1308
  },
1309
1309
  {
1310
- name: "sanctuary/state_export",
1310
+ name: "state_export",
1311
1311
  description: "Export state as an encrypted, portable bundle for migration.",
1312
1312
  inputSchema: {
1313
1313
  type: "object",
@@ -1327,7 +1327,7 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
1327
1327
  }
1328
1328
  },
1329
1329
  {
1330
- name: "sanctuary/state_import",
1330
+ name: "state_import",
1331
1331
  description: "Import a previously exported state bundle.",
1332
1332
  inputSchema: {
1333
1333
  type: "object",
@@ -1738,8 +1738,6 @@ tier3_always_allow:
1738
1738
  - governor_status
1739
1739
  - reputation_publish
1740
1740
  - sanctuary_policy_status
1741
- - sanctuary_link_to_human
1742
- - sanctuary_sign_challenge
1743
1741
 
1744
1742
  # \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
1745
1743
  # How Sanctuary reaches you when approval is needed.
@@ -5073,6 +5071,807 @@ var init_dashboard_html = __esm({
5073
5071
  }
5074
5072
  });
5075
5073
 
5074
+ // src/cocoon/fortress-view.ts
5075
+ function generateFortressViewHTML(options) {
5076
+ return `<!DOCTYPE html>
5077
+ <html lang="en">
5078
+ <head>
5079
+ <meta charset="UTF-8">
5080
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5081
+ <title>Sanctuary \u2014 Fortress View</title>
5082
+ <style>
5083
+ :root {
5084
+ --bg: #0d1117;
5085
+ --surface: #161b22;
5086
+ --surface-raised: #1c2128;
5087
+ --border: #30363d;
5088
+ --text-primary: #e6edf3;
5089
+ --text-secondary: #8b949e;
5090
+ --text-muted: #484f58;
5091
+ --green: #3fb950;
5092
+ --green-dim: #238636;
5093
+ --amber: #d29922;
5094
+ --amber-dim: #9e6a03;
5095
+ --red: #f85149;
5096
+ --red-dim: #da3633;
5097
+ --blue: #58a6ff;
5098
+ --blue-dim: #1f6feb;
5099
+ }
5100
+
5101
+ * { margin: 0; padding: 0; box-sizing: border-box; }
5102
+
5103
+ body {
5104
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
5105
+ background-color: var(--bg);
5106
+ color: var(--text-primary);
5107
+ min-height: 100vh;
5108
+ }
5109
+
5110
+ /* \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 */
5111
+ .fortress-header {
5112
+ display: flex;
5113
+ align-items: center;
5114
+ justify-content: space-between;
5115
+ padding: 16px 24px;
5116
+ border-bottom: 1px solid var(--border);
5117
+ background: var(--surface);
5118
+ }
5119
+
5120
+ .fortress-brand {
5121
+ display: flex;
5122
+ align-items: center;
5123
+ gap: 12px;
5124
+ }
5125
+
5126
+ .fortress-brand .shield {
5127
+ font-size: 28px;
5128
+ color: var(--blue);
5129
+ }
5130
+
5131
+ .fortress-brand h1 {
5132
+ font-size: 18px;
5133
+ font-weight: 600;
5134
+ letter-spacing: -0.5px;
5135
+ }
5136
+
5137
+ .fortress-brand .version {
5138
+ font-size: 12px;
5139
+ color: var(--text-secondary);
5140
+ }
5141
+
5142
+ .header-actions {
5143
+ display: flex;
5144
+ gap: 8px;
5145
+ }
5146
+
5147
+ .header-actions button {
5148
+ padding: 6px 16px;
5149
+ border-radius: 6px;
5150
+ border: 1px solid var(--border);
5151
+ background: var(--surface);
5152
+ color: var(--text-primary);
5153
+ font-size: 13px;
5154
+ cursor: pointer;
5155
+ transition: background 0.15s;
5156
+ }
5157
+
5158
+ .header-actions button:hover {
5159
+ background: var(--surface-raised);
5160
+ }
5161
+
5162
+ .header-actions .pause-btn {
5163
+ border-color: var(--red-dim);
5164
+ color: var(--red);
5165
+ }
5166
+
5167
+ .header-actions .pause-btn:hover {
5168
+ background: rgba(248, 81, 73, 0.1);
5169
+ }
5170
+
5171
+ .header-actions .pause-btn.paused {
5172
+ background: var(--red-dim);
5173
+ color: white;
5174
+ }
5175
+
5176
+ /* \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 */
5177
+ .tab-bar {
5178
+ display: flex;
5179
+ border-bottom: 1px solid var(--border);
5180
+ background: var(--surface);
5181
+ padding: 0 24px;
5182
+ }
5183
+
5184
+ .tab-bar button {
5185
+ padding: 10px 16px;
5186
+ border: none;
5187
+ background: none;
5188
+ color: var(--text-secondary);
5189
+ font-size: 14px;
5190
+ cursor: pointer;
5191
+ border-bottom: 2px solid transparent;
5192
+ transition: all 0.15s;
5193
+ }
5194
+
5195
+ .tab-bar button:hover {
5196
+ color: var(--text-primary);
5197
+ }
5198
+
5199
+ .tab-bar button.active {
5200
+ color: var(--text-primary);
5201
+ border-bottom-color: var(--blue);
5202
+ }
5203
+
5204
+ /* \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 */
5205
+ .fortress-content { padding: 24px; }
5206
+
5207
+ /* \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 */
5208
+ .status-banner {
5209
+ display: flex;
5210
+ align-items: center;
5211
+ gap: 16px;
5212
+ padding: 20px 24px;
5213
+ border-radius: 8px;
5214
+ border: 1px solid var(--border);
5215
+ background: var(--surface);
5216
+ margin-bottom: 24px;
5217
+ }
5218
+
5219
+ .status-indicator {
5220
+ width: 48px;
5221
+ height: 48px;
5222
+ border-radius: 50%;
5223
+ display: flex;
5224
+ align-items: center;
5225
+ justify-content: center;
5226
+ font-size: 24px;
5227
+ flex-shrink: 0;
5228
+ }
5229
+
5230
+ .status-indicator.green { background: rgba(63, 185, 80, 0.15); color: var(--green); }
5231
+ .status-indicator.amber { background: rgba(210, 153, 34, 0.15); color: var(--amber); }
5232
+ .status-indicator.red { background: rgba(248, 81, 73, 0.15); color: var(--red); }
5233
+
5234
+ .status-info h2 {
5235
+ font-size: 18px;
5236
+ font-weight: 600;
5237
+ margin-bottom: 4px;
5238
+ }
5239
+
5240
+ .status-info p {
5241
+ font-size: 14px;
5242
+ color: var(--text-secondary);
5243
+ }
5244
+
5245
+ .status-stats {
5246
+ display: flex;
5247
+ gap: 24px;
5248
+ margin-left: auto;
5249
+ }
5250
+
5251
+ .stat {
5252
+ text-align: center;
5253
+ }
5254
+
5255
+ .stat .value {
5256
+ font-size: 24px;
5257
+ font-weight: 600;
5258
+ font-variant-numeric: tabular-nums;
5259
+ }
5260
+
5261
+ .stat .label {
5262
+ font-size: 11px;
5263
+ color: var(--text-secondary);
5264
+ text-transform: uppercase;
5265
+ letter-spacing: 0.5px;
5266
+ }
5267
+
5268
+ /* \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 */
5269
+ .fortress-grid {
5270
+ display: grid;
5271
+ grid-template-columns: 1fr 360px;
5272
+ gap: 24px;
5273
+ }
5274
+
5275
+ @media (max-width: 900px) {
5276
+ .fortress-grid { grid-template-columns: 1fr; }
5277
+ }
5278
+
5279
+ /* \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 */
5280
+ .feed-panel {
5281
+ background: var(--surface);
5282
+ border: 1px solid var(--border);
5283
+ border-radius: 8px;
5284
+ overflow: hidden;
5285
+ }
5286
+
5287
+ .panel-header {
5288
+ display: flex;
5289
+ align-items: center;
5290
+ justify-content: space-between;
5291
+ padding: 12px 16px;
5292
+ border-bottom: 1px solid var(--border);
5293
+ }
5294
+
5295
+ .panel-header h3 {
5296
+ font-size: 14px;
5297
+ font-weight: 600;
5298
+ }
5299
+
5300
+ .feed-list {
5301
+ max-height: 600px;
5302
+ overflow-y: auto;
5303
+ scroll-behavior: smooth;
5304
+ }
5305
+
5306
+ .feed-item {
5307
+ display: flex;
5308
+ align-items: flex-start;
5309
+ gap: 10px;
5310
+ padding: 10px 16px;
5311
+ border-bottom: 1px solid var(--border);
5312
+ font-size: 13px;
5313
+ transition: background 0.1s;
5314
+ }
5315
+
5316
+ .feed-item:hover {
5317
+ background: var(--surface-raised);
5318
+ }
5319
+
5320
+ .feed-dot {
5321
+ width: 8px;
5322
+ height: 8px;
5323
+ border-radius: 50%;
5324
+ margin-top: 5px;
5325
+ flex-shrink: 0;
5326
+ }
5327
+
5328
+ .feed-dot.green { background: var(--green); }
5329
+ .feed-dot.amber { background: var(--amber); }
5330
+ .feed-dot.red { background: var(--red); }
5331
+
5332
+ .feed-detail {
5333
+ flex: 1;
5334
+ min-width: 0;
5335
+ }
5336
+
5337
+ .feed-tool {
5338
+ font-family: 'SF Mono', 'Fira Code', monospace;
5339
+ font-size: 12px;
5340
+ color: var(--blue);
5341
+ word-break: break-all;
5342
+ }
5343
+
5344
+ .feed-decision {
5345
+ font-size: 12px;
5346
+ color: var(--text-secondary);
5347
+ margin-top: 2px;
5348
+ }
5349
+
5350
+ .feed-time {
5351
+ font-size: 11px;
5352
+ color: var(--text-muted);
5353
+ flex-shrink: 0;
5354
+ white-space: nowrap;
5355
+ }
5356
+
5357
+ .feed-empty {
5358
+ padding: 40px 16px;
5359
+ text-align: center;
5360
+ color: var(--text-muted);
5361
+ font-size: 14px;
5362
+ }
5363
+
5364
+ /* \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 */
5365
+ .alerts-panel {
5366
+ background: var(--surface);
5367
+ border: 1px solid var(--border);
5368
+ border-radius: 8px;
5369
+ overflow: hidden;
5370
+ }
5371
+
5372
+ .alert-item {
5373
+ padding: 12px 16px;
5374
+ border-bottom: 1px solid var(--border);
5375
+ }
5376
+
5377
+ .alert-item .alert-title {
5378
+ font-size: 13px;
5379
+ font-weight: 500;
5380
+ margin-bottom: 4px;
5381
+ }
5382
+
5383
+ .alert-item .alert-desc {
5384
+ font-size: 12px;
5385
+ color: var(--text-secondary);
5386
+ margin-bottom: 8px;
5387
+ }
5388
+
5389
+ .alert-actions {
5390
+ display: flex;
5391
+ gap: 8px;
5392
+ }
5393
+
5394
+ .alert-actions button {
5395
+ padding: 4px 12px;
5396
+ border-radius: 4px;
5397
+ border: 1px solid var(--border);
5398
+ font-size: 12px;
5399
+ cursor: pointer;
5400
+ transition: all 0.15s;
5401
+ }
5402
+
5403
+ .approve-btn {
5404
+ background: var(--green-dim);
5405
+ color: white;
5406
+ border-color: var(--green-dim) !important;
5407
+ }
5408
+
5409
+ .approve-btn:hover { opacity: 0.9; }
5410
+
5411
+ .deny-btn {
5412
+ background: none;
5413
+ color: var(--red);
5414
+ border-color: var(--red-dim) !important;
5415
+ }
5416
+
5417
+ .deny-btn:hover {
5418
+ background: rgba(248, 81, 73, 0.1);
5419
+ }
5420
+
5421
+ .alerts-empty {
5422
+ padding: 40px 16px;
5423
+ text-align: center;
5424
+ color: var(--text-muted);
5425
+ font-size: 14px;
5426
+ }
5427
+
5428
+ /* \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 */
5429
+ .servers-panel {
5430
+ margin-top: 16px;
5431
+ }
5432
+
5433
+ .server-row {
5434
+ display: flex;
5435
+ align-items: center;
5436
+ gap: 8px;
5437
+ padding: 8px 16px;
5438
+ border-bottom: 1px solid var(--border);
5439
+ font-size: 13px;
5440
+ }
5441
+
5442
+ .server-status-dot {
5443
+ width: 8px;
5444
+ height: 8px;
5445
+ border-radius: 50%;
5446
+ }
5447
+
5448
+ .server-status-dot.connected { background: var(--green); }
5449
+ .server-status-dot.connecting { background: var(--amber); }
5450
+ .server-status-dot.disconnected, .server-status-dot.error { background: var(--red); }
5451
+
5452
+ .server-name {
5453
+ font-family: 'SF Mono', 'Fira Code', monospace;
5454
+ font-size: 12px;
5455
+ }
5456
+
5457
+ .server-tier {
5458
+ margin-left: auto;
5459
+ font-size: 11px;
5460
+ color: var(--text-secondary);
5461
+ }
5462
+ </style>
5463
+ </head>
5464
+ <body>
5465
+ <!-- Header -->
5466
+ <div class="fortress-header">
5467
+ <div class="fortress-brand">
5468
+ <div class="shield">&#x1F6E1;</div>
5469
+ <div>
5470
+ <h1>Sanctuary Cocoon</h1>
5471
+ <div class="version">v${esc(options.serverVersion)}</div>
5472
+ </div>
5473
+ </div>
5474
+ <div class="header-actions">
5475
+ <button class="pause-btn" id="pause-btn" title="Pause agent \u2014 requires approval for all operations">Pause Agent</button>
5476
+ <button id="advanced-btn">Advanced</button>
5477
+ </div>
5478
+ </div>
5479
+
5480
+ <!-- Tab bar -->
5481
+ <div class="tab-bar">
5482
+ <button class="active" data-tab="fortress">Fortress</button>
5483
+ <button data-tab="advanced">Advanced</button>
5484
+ </div>
5485
+
5486
+ <!-- Fortress View -->
5487
+ <div class="fortress-content" id="fortress-tab">
5488
+ <!-- Status Banner -->
5489
+ <div class="status-banner" id="status-banner">
5490
+ <div class="status-indicator green" id="status-indicator">&#x2713;</div>
5491
+ <div class="status-info">
5492
+ <h2 id="status-title">Agent Protected</h2>
5493
+ <p id="status-subtitle">${options.upstreamServerCount} server${options.upstreamServerCount !== 1 ? "s" : ""} monitored. All systems nominal.</p>
5494
+ </div>
5495
+ <div class="status-stats">
5496
+ <div class="stat">
5497
+ <div class="value" id="stat-total">0</div>
5498
+ <div class="label">Calls</div>
5499
+ </div>
5500
+ <div class="stat">
5501
+ <div class="value" id="stat-blocked">0</div>
5502
+ <div class="label">Blocked</div>
5503
+ </div>
5504
+ <div class="stat">
5505
+ <div class="value" id="stat-pending">0</div>
5506
+ <div class="label">Pending</div>
5507
+ </div>
5508
+ </div>
5509
+ </div>
5510
+
5511
+ <!-- Two-column layout -->
5512
+ <div class="fortress-grid">
5513
+ <!-- Live Feed -->
5514
+ <div class="feed-panel">
5515
+ <div class="panel-header">
5516
+ <h3>Live Activity</h3>
5517
+ <span style="font-size: 12px; color: var(--text-muted);" id="feed-count">0 events</span>
5518
+ </div>
5519
+ <div class="feed-list" id="feed-list">
5520
+ <div class="feed-empty">Waiting for tool calls...</div>
5521
+ </div>
5522
+ </div>
5523
+
5524
+ <!-- Right column: Alerts + Servers -->
5525
+ <div>
5526
+ <!-- Alerts -->
5527
+ <div class="alerts-panel">
5528
+ <div class="panel-header">
5529
+ <h3>Needs Attention</h3>
5530
+ <span style="font-size: 12px; color: var(--text-muted);" id="alert-count">0</span>
5531
+ </div>
5532
+ <div id="alerts-list">
5533
+ <div class="alerts-empty">No pending actions</div>
5534
+ </div>
5535
+ </div>
5536
+
5537
+ <!-- Servers -->
5538
+ <div class="alerts-panel servers-panel">
5539
+ <div class="panel-header">
5540
+ <h3>Upstream Servers</h3>
5541
+ </div>
5542
+ <div id="servers-list">
5543
+ <div class="alerts-empty">No servers configured</div>
5544
+ </div>
5545
+ </div>
5546
+ </div>
5547
+ </div>
5548
+ </div>
5549
+
5550
+ <script>
5551
+ // \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
5552
+ const API_BASE = window.location.origin;
5553
+ const SESSION_TOKEN = sessionStorage.getItem('sanctuary_session') || '';
5554
+ const MAX_FEED_ITEMS = 50;
5555
+
5556
+ let feedItems = [];
5557
+ let totalCalls = 0;
5558
+ let blockedCalls = 0;
5559
+ let pendingApprovals = [];
5560
+ let upstreamServers = [];
5561
+ let paused = false;
5562
+
5563
+ // \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
5564
+ function connectSSE() {
5565
+ const url = API_BASE + '/events' + (SESSION_TOKEN ? '?session=' + SESSION_TOKEN : '');
5566
+ const eventSource = new EventSource(url);
5567
+
5568
+ eventSource.addEventListener('proxy-call', (e) => {
5569
+ try {
5570
+ const data = JSON.parse(e.data);
5571
+ addFeedItem(data);
5572
+ } catch {}
5573
+ });
5574
+
5575
+ eventSource.addEventListener('proxy-server-status', (e) => {
5576
+ try {
5577
+ const data = JSON.parse(e.data);
5578
+ updateServerStatus(data.server, data.state, data.tool_count, data.error);
5579
+ } catch {}
5580
+ });
5581
+
5582
+ eventSource.addEventListener('injection-alert', (e) => {
5583
+ try {
5584
+ const data = JSON.parse(e.data);
5585
+ addFeedItem({
5586
+ tool: data.tool_name || 'unknown',
5587
+ server: 'detection',
5588
+ decision: 'blocked',
5589
+ reason: 'Injection detected: ' + (data.signals || []).join(', '),
5590
+ timestamp: new Date().toISOString(),
5591
+ });
5592
+ } catch {}
5593
+ });
5594
+
5595
+ eventSource.addEventListener('approval-request', (e) => {
5596
+ try {
5597
+ const data = JSON.parse(e.data);
5598
+ addPendingApproval(data);
5599
+ } catch {}
5600
+ });
5601
+
5602
+ eventSource.addEventListener('approval-resolved', (e) => {
5603
+ try {
5604
+ const data = JSON.parse(e.data);
5605
+ removePendingApproval(data.id);
5606
+ } catch {}
5607
+ });
5608
+
5609
+ eventSource.onerror = () => {
5610
+ eventSource.close();
5611
+ setTimeout(connectSSE, 3000);
5612
+ };
5613
+ }
5614
+
5615
+ // \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
5616
+ function addFeedItem(data) {
5617
+ totalCalls++;
5618
+ if (data.decision === 'blocked' || data.decision === 'denied') {
5619
+ blockedCalls++;
5620
+ }
5621
+
5622
+ feedItems.unshift({
5623
+ tool: data.tool || 'unknown',
5624
+ server: data.server || '',
5625
+ decision: data.decision || 'allowed',
5626
+ reason: data.reason || '',
5627
+ time: data.timestamp || new Date().toISOString(),
5628
+ });
5629
+
5630
+ if (feedItems.length > MAX_FEED_ITEMS) {
5631
+ feedItems = feedItems.slice(0, MAX_FEED_ITEMS);
5632
+ }
5633
+
5634
+ renderFeed();
5635
+ updateStats();
5636
+ updateStatus();
5637
+ }
5638
+
5639
+ function renderFeed() {
5640
+ const container = document.getElementById('feed-list');
5641
+ if (feedItems.length === 0) {
5642
+ container.innerHTML = '<div class="feed-empty">Waiting for tool calls...</div>';
5643
+ return;
5644
+ }
5645
+
5646
+ container.innerHTML = feedItems.map(item => {
5647
+ const dotColor = item.decision === 'allowed' ? 'green'
5648
+ : item.decision === 'pending' ? 'amber' : 'red';
5649
+ const decisionText = item.decision === 'allowed' ? 'Auto-allowed'
5650
+ : item.decision === 'pending' ? 'Awaiting approval'
5651
+ : item.decision === 'blocked' ? 'Blocked' : item.decision;
5652
+ const timeStr = new Date(item.time).toLocaleTimeString();
5653
+
5654
+ return '<div class="feed-item">' +
5655
+ '<div class="feed-dot ' + dotColor + '"></div>' +
5656
+ '<div class="feed-detail">' +
5657
+ '<div class="feed-tool">' + esc(item.tool) + '</div>' +
5658
+ '<div class="feed-decision">' + esc(decisionText) +
5659
+ (item.reason ? ' \u2014 ' + esc(item.reason) : '') + '</div>' +
5660
+ '</div>' +
5661
+ '<div class="feed-time">' + esc(timeStr) + '</div>' +
5662
+ '</div>';
5663
+ }).join('');
5664
+
5665
+ document.getElementById('feed-count').textContent = feedItems.length + ' events';
5666
+ }
5667
+
5668
+ // \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
5669
+ function addPendingApproval(data) {
5670
+ pendingApprovals.push(data);
5671
+ renderAlerts();
5672
+ updateStats();
5673
+ updateStatus();
5674
+ }
5675
+
5676
+ function removePendingApproval(id) {
5677
+ pendingApprovals = pendingApprovals.filter(a => a.id !== id);
5678
+ renderAlerts();
5679
+ updateStats();
5680
+ updateStatus();
5681
+ }
5682
+
5683
+ function renderAlerts() {
5684
+ const container = document.getElementById('alerts-list');
5685
+ if (pendingApprovals.length === 0) {
5686
+ container.innerHTML = '<div class="alerts-empty">No pending actions</div>';
5687
+ document.getElementById('alert-count').textContent = '0';
5688
+ return;
5689
+ }
5690
+
5691
+ document.getElementById('alert-count').textContent = pendingApprovals.length.toString();
5692
+
5693
+ container.innerHTML = pendingApprovals.map(approval => {
5694
+ return '<div class="alert-item">' +
5695
+ '<div class="alert-title">Approval required: ' + esc(approval.operation || approval.tool_name || 'unknown') + '</div>' +
5696
+ '<div class="alert-desc">' + esc(approval.reason || 'This operation requires your approval before it can proceed.') + '</div>' +
5697
+ '<div class="alert-actions">' +
5698
+ '<button class="approve-btn" onclick="handleApproval(\\'' + esc(approval.id) + '\\', true)">Approve</button>' +
5699
+ '<button class="deny-btn" onclick="handleApproval(\\'' + esc(approval.id) + '\\', false)">Deny</button>' +
5700
+ '</div>' +
5701
+ '</div>';
5702
+ }).join('');
5703
+ }
5704
+
5705
+ async function handleApproval(id, approved) {
5706
+ const endpoint = approved ? '/api/approve/' : '/api/deny/';
5707
+ try {
5708
+ await fetch(API_BASE + endpoint + id, {
5709
+ method: 'POST',
5710
+ headers: SESSION_TOKEN ? { 'Authorization': 'Bearer ' + SESSION_TOKEN } : {},
5711
+ });
5712
+ removePendingApproval(id);
5713
+ } catch (err) {
5714
+ console.error('Approval action failed:', err);
5715
+ }
5716
+ }
5717
+
5718
+ // \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
5719
+ function updateServerStatus(serverName, state, toolCount, error) {
5720
+ const existing = upstreamServers.find(s => s.name === serverName);
5721
+ if (existing) {
5722
+ existing.state = state;
5723
+ existing.tool_count = toolCount;
5724
+ existing.error = error;
5725
+ } else {
5726
+ upstreamServers.push({ name: serverName, state, tool_count: toolCount, error });
5727
+ }
5728
+ renderServers();
5729
+ updateStatus();
5730
+ }
5731
+
5732
+ function renderServers() {
5733
+ const container = document.getElementById('servers-list');
5734
+ if (upstreamServers.length === 0) {
5735
+ container.innerHTML = '<div class="alerts-empty">No servers configured</div>';
5736
+ return;
5737
+ }
5738
+
5739
+ container.innerHTML = upstreamServers.map(server => {
5740
+ const stateClass = server.state || 'disconnected';
5741
+ const stateLabel = server.state === 'connected' ? 'Connected'
5742
+ : server.state === 'connecting' ? 'Connecting...'
5743
+ : server.state === 'error' ? 'Error' : 'Disconnected';
5744
+
5745
+ return '<div class="server-row">' +
5746
+ '<div class="server-status-dot ' + stateClass + '"></div>' +
5747
+ '<span class="server-name">' + esc(server.name) + '</span>' +
5748
+ '<span class="server-tier">' + esc(stateLabel) +
5749
+ (server.tool_count ? ' (' + server.tool_count + ' tools)' : '') + '</span>' +
5750
+ '</div>';
5751
+ }).join('');
5752
+ }
5753
+
5754
+ // \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
5755
+ function updateStats() {
5756
+ document.getElementById('stat-total').textContent = totalCalls.toString();
5757
+ document.getElementById('stat-blocked').textContent = blockedCalls.toString();
5758
+ document.getElementById('stat-pending').textContent = pendingApprovals.length.toString();
5759
+ }
5760
+
5761
+ function updateStatus() {
5762
+ const indicator = document.getElementById('status-indicator');
5763
+ const title = document.getElementById('status-title');
5764
+ const subtitle = document.getElementById('status-subtitle');
5765
+
5766
+ const hasErrors = upstreamServers.some(s => s.state === 'error');
5767
+ const hasPending = pendingApprovals.length > 0;
5768
+ const hasBlocked = blockedCalls > 0;
5769
+
5770
+ if (paused) {
5771
+ indicator.className = 'status-indicator red';
5772
+ indicator.innerHTML = '&#x23F8;';
5773
+ title.textContent = 'Agent Paused';
5774
+ subtitle.textContent = 'All operations require approval. Click Resume to restore normal mode.';
5775
+ } else if (hasErrors) {
5776
+ indicator.className = 'status-indicator red';
5777
+ indicator.innerHTML = '&#x26A0;';
5778
+ title.textContent = 'Connection Issues';
5779
+ subtitle.textContent = 'One or more upstream servers have errors.';
5780
+ } else if (hasPending) {
5781
+ indicator.className = 'status-indicator amber';
5782
+ indicator.innerHTML = '&#x23F3;';
5783
+ title.textContent = 'Action Required';
5784
+ subtitle.textContent = pendingApprovals.length + ' operation' + (pendingApprovals.length > 1 ? 's' : '') + ' awaiting your approval.';
5785
+ } else {
5786
+ indicator.className = 'status-indicator green';
5787
+ indicator.innerHTML = '&#x2713;';
5788
+ title.textContent = 'Agent Protected';
5789
+ const serverCount = upstreamServers.filter(s => s.state === 'connected').length || ${options.upstreamServerCount};
5790
+ subtitle.textContent = serverCount + ' server' + (serverCount !== 1 ? 's' : '') + ' monitored. All systems nominal.';
5791
+ }
5792
+ }
5793
+
5794
+ // \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
5795
+ document.getElementById('pause-btn').addEventListener('click', () => {
5796
+ paused = !paused;
5797
+ const btn = document.getElementById('pause-btn');
5798
+ if (paused) {
5799
+ btn.textContent = 'Resume Agent';
5800
+ btn.classList.add('paused');
5801
+ } else {
5802
+ btn.textContent = 'Pause Agent';
5803
+ btn.classList.remove('paused');
5804
+ }
5805
+ updateStatus();
5806
+ // TODO: POST to /api/cocoon/pause to set all tiers to 1
5807
+ });
5808
+
5809
+ // \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
5810
+ document.getElementById('advanced-btn').addEventListener('click', () => {
5811
+ window.location.href = '/dashboard?session=' + SESSION_TOKEN;
5812
+ });
5813
+
5814
+ document.querySelectorAll('.tab-bar button').forEach(btn => {
5815
+ btn.addEventListener('click', () => {
5816
+ const tab = btn.dataset.tab;
5817
+ if (tab === 'advanced') {
5818
+ window.location.href = '/dashboard?session=' + SESSION_TOKEN;
5819
+ }
5820
+ });
5821
+ });
5822
+
5823
+ // \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
5824
+ function esc(str) {
5825
+ if (!str) return '';
5826
+ const d = document.createElement('div');
5827
+ d.textContent = String(str);
5828
+ return d.innerHTML;
5829
+ }
5830
+
5831
+ // \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
5832
+ async function init() {
5833
+ // Load initial server state
5834
+ try {
5835
+ const resp = await fetch(API_BASE + '/api/proxy/servers', {
5836
+ headers: SESSION_TOKEN ? { 'Authorization': 'Bearer ' + SESSION_TOKEN } : {},
5837
+ });
5838
+ if (resp.ok) {
5839
+ const data = await resp.json();
5840
+ upstreamServers = data.servers || [];
5841
+ renderServers();
5842
+ }
5843
+ } catch {}
5844
+
5845
+ // Load pending approvals
5846
+ try {
5847
+ const resp = await fetch(API_BASE + '/api/pending', {
5848
+ headers: SESSION_TOKEN ? { 'Authorization': 'Bearer ' + SESSION_TOKEN } : {},
5849
+ });
5850
+ if (resp.ok) {
5851
+ const data = await resp.json();
5852
+ pendingApprovals = data.pending || [];
5853
+ renderAlerts();
5854
+ updateStats();
5855
+ }
5856
+ } catch {}
5857
+
5858
+ updateStatus();
5859
+ connectSSE();
5860
+ }
5861
+
5862
+ init();
5863
+ </script>
5864
+ </body>
5865
+ </html>`;
5866
+ }
5867
+ function esc(str) {
5868
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5869
+ }
5870
+ var init_fortress_view = __esm({
5871
+ "src/cocoon/fortress-view.ts"() {
5872
+ }
5873
+ });
5874
+
5076
5875
  // src/system-prompt-generator.ts
5077
5876
  function generateSystemPrompt(profile) {
5078
5877
  const activeFeatures = [];
@@ -5134,12 +5933,12 @@ function buildQuickStart(activeKeys) {
5134
5933
  const items = [];
5135
5934
  if (activeKeys.includes("context_gating")) {
5136
5935
  items.push(
5137
- "1. ALWAYS call sanctuary/context_gate_filter before sending context to external APIs."
5936
+ "1. ALWAYS call context_gate_filter before sending context to external APIs."
5138
5937
  );
5139
5938
  }
5140
5939
  if (activeKeys.includes("zk_proofs")) {
5141
5940
  items.push(
5142
- `${items.length + 1}. Use sanctuary/zk_commit to prove claims without revealing underlying data.`
5941
+ `${items.length + 1}. Use zk_commit to prove claims without revealing underlying data.`
5143
5942
  );
5144
5943
  }
5145
5944
  if (activeKeys.includes("approval_gate")) {
@@ -5165,9 +5964,9 @@ var init_system_prompt_generator = __esm({
5165
5964
  FEATURE_INFO = {
5166
5965
  audit_logging: {
5167
5966
  name: "Audit Logging",
5168
- 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.",
5169
- toolNames: ["sanctuary/monitor_audit_log"],
5170
- disabledDescription: "audit logging (sanctuary/monitor_audit_log)",
5967
+ 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.",
5968
+ toolNames: ["monitor_audit_log"],
5969
+ disabledDescription: "audit logging (monitor_audit_log)",
5171
5970
  usageExample: "Automatic \u2014 every tool call you make is recorded. No explicit action required."
5172
5971
  },
5173
5972
  injection_detection: {
@@ -5178,16 +5977,16 @@ var init_system_prompt_generator = __esm({
5178
5977
  },
5179
5978
  context_gating: {
5180
5979
  name: "Context Gating",
5181
- 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.",
5980
+ 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.",
5182
5981
  toolNames: [
5183
- "sanctuary/context_gate_filter",
5184
- "sanctuary/context_gate_set_policy",
5185
- "sanctuary/context_gate_apply_template",
5186
- "sanctuary/context_gate_recommend",
5187
- "sanctuary/context_gate_list_policies"
5982
+ "context_gate_filter",
5983
+ "context_gate_set_policy",
5984
+ "context_gate_apply_template",
5985
+ "context_gate_recommend",
5986
+ "context_gate_list_policies"
5188
5987
  ],
5189
- disabledDescription: "context gating (sanctuary/context_gate_filter)",
5190
- usageExample: "Before calling an external API, run: sanctuary/context_gate_filter with your context object and policy_id to get a filtered version."
5988
+ disabledDescription: "context gating (context_gate_filter)",
5989
+ usageExample: "Before calling an external API, run: context_gate_filter with your context object and policy_id to get a filtered version."
5191
5990
  },
5192
5991
  approval_gate: {
5193
5992
  name: "Approval Gates",
@@ -5197,15 +5996,15 @@ var init_system_prompt_generator = __esm({
5197
5996
  },
5198
5997
  zk_proofs: {
5199
5998
  name: "Zero-Knowledge Proofs",
5200
- 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.",
5999
+ 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.",
5201
6000
  toolNames: [
5202
- "sanctuary/zk_commit",
5203
- "sanctuary/zk_prove",
5204
- "sanctuary/zk_range_prove",
5205
- "sanctuary/proof_commitment"
6001
+ "zk_commit",
6002
+ "zk_prove",
6003
+ "zk_range_prove",
6004
+ "proof_commitment"
5206
6005
  ],
5207
- disabledDescription: "zero-knowledge proofs (sanctuary/zk_commit, sanctuary/zk_prove)",
5208
- 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."
6006
+ disabledDescription: "zero-knowledge proofs (zk_commit, zk_prove)",
6007
+ 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."
5209
6008
  }
5210
6009
  };
5211
6010
  }
@@ -5216,6 +6015,7 @@ var init_dashboard = __esm({
5216
6015
  init_config();
5217
6016
  init_generator();
5218
6017
  init_dashboard_html();
6018
+ init_fortress_view();
5219
6019
  init_system_prompt_generator();
5220
6020
  SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
5221
6021
  SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
@@ -5239,6 +6039,7 @@ var init_dashboard = __esm({
5239
6039
  profileStore = null;
5240
6040
  clientManager = null;
5241
6041
  dashboardHTML;
6042
+ fortressHTML = null;
5242
6043
  loginHTML;
5243
6044
  authToken;
5244
6045
  useTLS;
@@ -5624,8 +6425,14 @@ var init_dashboard = __esm({
5624
6425
  if (!this.checkAuth(req, url, res)) return;
5625
6426
  if (!this.checkRateLimit(req, res, "general")) return;
5626
6427
  try {
5627
- if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
5628
- this.serveDashboard(res);
6428
+ if (method === "GET" && url.pathname === "/fortress") {
6429
+ this.serveFortressView(res);
6430
+ } else if (method === "GET" && (url.pathname === "/" || url.pathname === "/dashboard")) {
6431
+ if (this.fortressHTML) {
6432
+ this.serveFortressView(res);
6433
+ } else {
6434
+ this.serveDashboard(res);
6435
+ }
5629
6436
  } else if (method === "GET" && url.pathname === "/events") {
5630
6437
  this.handleSSE(req, res);
5631
6438
  } else if (method === "GET" && url.pathname === "/api/status") {
@@ -5720,6 +6527,35 @@ var init_dashboard = __esm({
5720
6527
  });
5721
6528
  res.end(this.dashboardHTML);
5722
6529
  }
6530
+ serveFortressView(res) {
6531
+ if (!this.fortressHTML) {
6532
+ this.serveDashboard(res);
6533
+ return;
6534
+ }
6535
+ res.writeHead(200, {
6536
+ "Content-Type": "text/html; charset=utf-8",
6537
+ "Cache-Control": "no-cache"
6538
+ });
6539
+ res.end(this.fortressHTML);
6540
+ }
6541
+ /**
6542
+ * Enable Fortress View (Cocoon mode) with the given upstream server count.
6543
+ * Once enabled, the root path `/` serves the Fortress View instead of the
6544
+ * standard dashboard. The standard dashboard remains available at `/dashboard`.
6545
+ */
6546
+ enableFortressView(upstreamServerCount) {
6547
+ this.fortressHTML = generateFortressViewHTML({
6548
+ serverVersion: SANCTUARY_VERSION,
6549
+ authToken: this.authToken,
6550
+ upstreamServerCount
6551
+ });
6552
+ }
6553
+ /**
6554
+ * Broadcast a proxy call event to connected dashboards (Fortress View feed).
6555
+ */
6556
+ broadcastProxyCall(data) {
6557
+ this.broadcastSSE("proxy-call", data);
6558
+ }
5723
6559
  handleSSE(req, res) {
5724
6560
  res.writeHead(200, {
5725
6561
  "Content-Type": "text/event-stream",
@@ -6364,6 +7200,445 @@ var init_sovereignty_profile = __esm({
6364
7200
  };
6365
7201
  }
6366
7202
  });
7203
+ async function backupConfig(configPath) {
7204
+ await promises.mkdir(BACKUP_DIR, { recursive: true, mode: 448 });
7205
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7206
+ const backupPath = path.join(BACKUP_DIR, `config-backup-${timestamp}.json`);
7207
+ await promises.copyFile(configPath, backupPath);
7208
+ return backupPath;
7209
+ }
7210
+ async function restoreConfig(backupPath, targetPath) {
7211
+ await promises.copyFile(backupPath, targetPath);
7212
+ }
7213
+ async function findLatestBackup() {
7214
+ const metaPath = path.join(BACKUP_DIR, "cocoon-meta.json");
7215
+ try {
7216
+ const raw = await promises.readFile(metaPath, "utf-8");
7217
+ const meta = JSON.parse(raw);
7218
+ return {
7219
+ backupPath: meta.backupPath,
7220
+ originalPath: meta.originalPath
7221
+ };
7222
+ } catch {
7223
+ return null;
7224
+ }
7225
+ }
7226
+ async function saveCocoonMeta(meta) {
7227
+ await promises.mkdir(BACKUP_DIR, { recursive: true, mode: 448 });
7228
+ const metaPath = path.join(BACKUP_DIR, "cocoon-meta.json");
7229
+ await promises.writeFile(metaPath, JSON.stringify(meta, null, 2), { mode: 384 });
7230
+ }
7231
+ async function detectAgentConfig(platform2, configPath) {
7232
+ if (configPath) {
7233
+ return readConfigFile(configPath, platform2 ?? "generic");
7234
+ }
7235
+ if (platform2) {
7236
+ const paths = PLATFORM_PATHS[platform2];
7237
+ for (const path of paths) {
7238
+ const config = await readConfigFile(path, platform2);
7239
+ if (config) return config;
7240
+ }
7241
+ return null;
7242
+ }
7243
+ for (const [plat, paths] of Object.entries(PLATFORM_PATHS)) {
7244
+ for (const path of paths) {
7245
+ const config = await readConfigFile(path, plat);
7246
+ if (config) return config;
7247
+ }
7248
+ }
7249
+ return null;
7250
+ }
7251
+ async function readConfigFile(path, platform2) {
7252
+ try {
7253
+ await promises.access(path);
7254
+ } catch {
7255
+ return null;
7256
+ }
7257
+ try {
7258
+ const raw = await promises.readFile(path, "utf-8");
7259
+ const config = JSON.parse(raw);
7260
+ const servers = extractServers(config, platform2);
7261
+ return { platform: platform2, configPath: path, servers, rawConfig: config };
7262
+ } catch {
7263
+ return null;
7264
+ }
7265
+ }
7266
+ function extractServers(config, platform2) {
7267
+ if (!config || typeof config !== "object") return [];
7268
+ const servers = [];
7269
+ const obj = config;
7270
+ if (platform2 === "openclaw" || platform2 === "generic") {
7271
+ const mcp = obj.mcp;
7272
+ const nestedServers = mcp?.servers;
7273
+ if (nestedServers && typeof nestedServers === "object") {
7274
+ for (const [name, serverConfig] of Object.entries(nestedServers)) {
7275
+ const entry = parseServerEntry(name, serverConfig);
7276
+ if (entry) servers.push(entry);
7277
+ }
7278
+ }
7279
+ if (servers.length === 0) {
7280
+ const mcpServers = obj.mcpServers;
7281
+ if (mcpServers && typeof mcpServers === "object") {
7282
+ for (const [name, serverConfig] of Object.entries(mcpServers)) {
7283
+ const entry = parseServerEntry(name, serverConfig);
7284
+ if (entry) servers.push(entry);
7285
+ }
7286
+ }
7287
+ }
7288
+ }
7289
+ if (platform2 === "claude-code") {
7290
+ const mcpServers = obj.mcpServers;
7291
+ if (mcpServers && typeof mcpServers === "object") {
7292
+ for (const [name, serverConfig] of Object.entries(mcpServers)) {
7293
+ if (name.toLowerCase().includes("sanctuary")) continue;
7294
+ const entry = parseServerEntry(name, serverConfig);
7295
+ if (entry) servers.push(entry);
7296
+ }
7297
+ }
7298
+ }
7299
+ if (platform2 === "cursor") {
7300
+ const mcpServers = obj.mcpServers;
7301
+ if (mcpServers && typeof mcpServers === "object") {
7302
+ for (const [name, serverConfig] of Object.entries(mcpServers)) {
7303
+ if (name.toLowerCase().includes("sanctuary")) continue;
7304
+ const entry = parseServerEntry(name, serverConfig);
7305
+ if (entry) servers.push(entry);
7306
+ }
7307
+ }
7308
+ }
7309
+ return servers;
7310
+ }
7311
+ function parseServerEntry(name, config) {
7312
+ if (!config || typeof config !== "object") return null;
7313
+ const c = config;
7314
+ const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "-").substring(0, 128);
7315
+ if (!safeName) return null;
7316
+ if (c.url && typeof c.url === "string") {
7317
+ return {
7318
+ name: safeName,
7319
+ transport: "sse",
7320
+ url: c.url,
7321
+ env: extractEnv(c.env)
7322
+ };
7323
+ }
7324
+ if (c.command && typeof c.command === "string") {
7325
+ return {
7326
+ name: safeName,
7327
+ transport: "stdio",
7328
+ command: c.command,
7329
+ args: Array.isArray(c.args) ? c.args.filter((a) => typeof a === "string") : void 0,
7330
+ env: extractEnv(c.env)
7331
+ };
7332
+ }
7333
+ return null;
7334
+ }
7335
+ function extractEnv(env) {
7336
+ if (!env || typeof env !== "object") return void 0;
7337
+ const result = {};
7338
+ for (const [k, v] of Object.entries(env)) {
7339
+ if (typeof v === "string") result[k] = v;
7340
+ }
7341
+ return Object.keys(result).length > 0 ? result : void 0;
7342
+ }
7343
+ async function rewriteConfigForCocoon(agentConfig, sanctuaryCommand, sanctuaryArgs, sanctuaryEnv) {
7344
+ const raw = agentConfig.rawConfig;
7345
+ let existingServers = {};
7346
+ if (agentConfig.platform === "openclaw") {
7347
+ const existingMcp = raw.mcp ?? {};
7348
+ existingServers = existingMcp.servers ?? {};
7349
+ } else {
7350
+ existingServers = raw.mcpServers ?? {};
7351
+ }
7352
+ let resolvedEnv = sanctuaryEnv;
7353
+ if (!resolvedEnv) {
7354
+ const existingSanctuary = existingServers.sanctuary;
7355
+ if (existingSanctuary?.env && typeof existingSanctuary.env === "object") {
7356
+ const extracted = extractEnv(existingSanctuary.env);
7357
+ if (extracted) resolvedEnv = extracted;
7358
+ }
7359
+ }
7360
+ const sanctuaryEntry = {
7361
+ command: sanctuaryCommand,
7362
+ args: sanctuaryArgs
7363
+ };
7364
+ if (resolvedEnv && Object.keys(resolvedEnv).length > 0) {
7365
+ sanctuaryEntry.env = resolvedEnv;
7366
+ }
7367
+ let rewritten;
7368
+ if (agentConfig.platform === "openclaw") {
7369
+ const existingMcp = raw.mcp ?? {};
7370
+ rewritten = {
7371
+ ...raw,
7372
+ mcp: {
7373
+ ...existingMcp,
7374
+ servers: {
7375
+ ...existingServers,
7376
+ sanctuary: sanctuaryEntry
7377
+ }
7378
+ }
7379
+ };
7380
+ delete rewritten.mcpServers;
7381
+ } else {
7382
+ rewritten = {
7383
+ ...raw,
7384
+ mcpServers: {
7385
+ ...existingServers,
7386
+ sanctuary: sanctuaryEntry
7387
+ }
7388
+ };
7389
+ }
7390
+ await promises.writeFile(agentConfig.configPath, JSON.stringify(rewritten, null, 2), { mode: 384 });
7391
+ return agentConfig.configPath;
7392
+ }
7393
+ var PLATFORM_PATHS, BACKUP_DIR;
7394
+ var init_config_reader = __esm({
7395
+ "src/cocoon/config-reader.ts"() {
7396
+ PLATFORM_PATHS = {
7397
+ "openclaw": [
7398
+ path.join(os.homedir(), ".openclaw", "openclaw.json"),
7399
+ path.join(os.homedir(), ".openclaw", "config.json"),
7400
+ path.join(os.homedir(), "Library", "Application Support", "OpenClaw", "openclaw.json"),
7401
+ path.join(os.homedir(), "Library", "Application Support", "OpenClaw", "config.json")
7402
+ ],
7403
+ "claude-code": [
7404
+ path.join(os.homedir(), ".claude", "settings.json"),
7405
+ path.join(os.homedir(), ".config", "claude-code", "settings.json")
7406
+ ],
7407
+ "cursor": [
7408
+ path.join(os.homedir(), ".cursor", "mcp.json")
7409
+ ],
7410
+ "generic": []
7411
+ };
7412
+ BACKUP_DIR = path.join(os.homedir(), ".sanctuary", "backup");
7413
+ }
7414
+ });
7415
+
7416
+ // src/cocoon/cli.ts
7417
+ var cli_exports = {};
7418
+ __export(cli_exports, {
7419
+ COCOON_GOVERNOR_DEFAULTS: () => COCOON_GOVERNOR_DEFAULTS,
7420
+ parseCocoonArgs: () => parseCocoonArgs,
7421
+ runCocoon: () => runCocoon
7422
+ });
7423
+ async function runCocoon(options) {
7424
+ if (options.unwrap) {
7425
+ await unwrap();
7426
+ return;
7427
+ }
7428
+ let platform2;
7429
+ if (options.openclaw) platform2 = "openclaw";
7430
+ else if (options.claudeCode) platform2 = "claude-code";
7431
+ else if (options.cursor) platform2 = "cursor";
7432
+ const agentConfig = await detectAgentConfig(platform2, options.wrap);
7433
+ if (!agentConfig) {
7434
+ if (platform2) {
7435
+ console.error(`Could not find ${platform2} configuration. Check that the agent is installed.`);
7436
+ } else if (options.wrap) {
7437
+ console.error(`Could not read config file: ${options.wrap}`);
7438
+ } else {
7439
+ console.error("Could not auto-detect any agent configuration.");
7440
+ console.error("Use --openclaw, --claude-code, --cursor, or --wrap /path/to/config.json");
7441
+ }
7442
+ process.exit(1);
7443
+ }
7444
+ if (agentConfig.servers.length === 0) {
7445
+ console.error(`Found ${agentConfig.platform} config at ${agentConfig.configPath}, but no MCP servers configured.`);
7446
+ process.exit(1);
7447
+ }
7448
+ console.error(`
7449
+ Sanctuary Cocoon
7450
+ `);
7451
+ console.error(` Platform: ${agentConfig.platform}`);
7452
+ console.error(` Config: ${agentConfig.configPath}`);
7453
+ console.error(` MCP servers found: ${agentConfig.servers.length}
7454
+ `);
7455
+ const upstreamServers = convertToUpstreamServers(agentConfig.servers);
7456
+ for (const server of upstreamServers) {
7457
+ const overrideCount = Object.keys(server.tool_overrides ?? {}).length;
7458
+ console.error(` \u2192 ${server.name} (${server.transport.type}) \u2014 default: Tier ${server.default_tier}`);
7459
+ if (overrideCount > 0) {
7460
+ console.error(` ${overrideCount} tool-specific tier overrides`);
7461
+ }
7462
+ }
7463
+ if (options.dryRun) {
7464
+ console.error(`
7465
+ Dry run \u2014 no changes made.
7466
+ `);
7467
+ return;
7468
+ }
7469
+ const storagePath = path.join(os.homedir(), ".sanctuary");
7470
+ await promises.mkdir(storagePath, { recursive: true, mode: 448 });
7471
+ const profile = createCocoonProfile(upstreamServers);
7472
+ const cocoonConfigPath = path.join(storagePath, "cocoon-profile.json");
7473
+ await promises.writeFile(cocoonConfigPath, JSON.stringify(profile, null, 2), { mode: 384 });
7474
+ const backupPath = await backupConfig(agentConfig.configPath);
7475
+ await saveCocoonMeta({
7476
+ backupPath,
7477
+ originalPath: agentConfig.configPath,
7478
+ platform: agentConfig.platform,
7479
+ wrappedAt: (/* @__PURE__ */ new Date()).toISOString()
7480
+ });
7481
+ console.error(`
7482
+ Original config backed up to: ${backupPath}`);
7483
+ await rewriteConfigForCocoon(
7484
+ agentConfig,
7485
+ "npx",
7486
+ [
7487
+ "@sanctuary-framework/mcp-server",
7488
+ ...options.passphrase ? ["--passphrase", options.passphrase] : []
7489
+ ]
7490
+ );
7491
+ console.error(` Agent config rewritten to route through Sanctuary`);
7492
+ const dashboardPort = options.port ?? 3501;
7493
+ console.error(`
7494
+ Your agent is now protected.`);
7495
+ console.error(` All tool calls are being logged and scanned.`);
7496
+ console.error(`
7497
+ To view the dashboard, run in a separate terminal:`);
7498
+ console.error(` npx @sanctuary-framework/mcp-server dashboard --port ${dashboardPort}`);
7499
+ console.error(`
7500
+ To restore: npx @sanctuary-framework/cocoon --unwrap
7501
+ `);
7502
+ }
7503
+ async function unwrap() {
7504
+ const meta = await findLatestBackup();
7505
+ if (!meta) {
7506
+ console.error("No Cocoon wrapping found to restore.");
7507
+ console.error("Run --wrap or --openclaw first.");
7508
+ process.exit(1);
7509
+ }
7510
+ try {
7511
+ await promises.access(meta.backupPath);
7512
+ } catch {
7513
+ console.error(`Backup file not found: ${meta.backupPath}`);
7514
+ process.exit(1);
7515
+ }
7516
+ await restoreConfig(meta.backupPath, meta.originalPath);
7517
+ console.error(`
7518
+ Sanctuary Cocoon \u2014 Unwrapped`);
7519
+ console.error(` Original config restored to: ${meta.originalPath}`);
7520
+ console.error(` Backup preserved at: ${meta.backupPath}
7521
+ `);
7522
+ }
7523
+ function convertToUpstreamServers(servers) {
7524
+ return servers.map((server) => {
7525
+ const upstream = {
7526
+ name: server.name,
7527
+ transport: server.transport === "sse" ? { type: "sse", url: server.url } : {
7528
+ type: "stdio",
7529
+ command: server.command,
7530
+ ...server.args ? { args: server.args } : {},
7531
+ ...server.env ? { env: server.env } : {}
7532
+ },
7533
+ enabled: true,
7534
+ default_tier: 2
7535
+ };
7536
+ return upstream;
7537
+ });
7538
+ }
7539
+ function createCocoonProfile(upstreamServers) {
7540
+ return {
7541
+ version: 1,
7542
+ features: {
7543
+ audit_logging: { enabled: true },
7544
+ // Non-negotiable
7545
+ injection_detection: { enabled: true },
7546
+ // Non-negotiable
7547
+ context_gating: { enabled: false },
7548
+ // Can enable later
7549
+ approval_gate: { enabled: true },
7550
+ // Core enforcement — always ON
7551
+ zk_proofs: { enabled: false }
7552
+ // Not needed for Cocoon
7553
+ },
7554
+ upstream_servers: upstreamServers,
7555
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
7556
+ };
7557
+ }
7558
+ function parseCocoonArgs(argv) {
7559
+ const options = {};
7560
+ for (let i = 0; i < argv.length; i++) {
7561
+ switch (argv[i]) {
7562
+ case "--wrap":
7563
+ options.wrap = argv[++i];
7564
+ break;
7565
+ case "--openclaw":
7566
+ options.openclaw = true;
7567
+ break;
7568
+ case "--claude-code":
7569
+ options.claudeCode = true;
7570
+ break;
7571
+ case "--cursor":
7572
+ options.cursor = true;
7573
+ break;
7574
+ case "--unwrap":
7575
+ options.unwrap = true;
7576
+ break;
7577
+ case "--passphrase":
7578
+ options.passphrase = argv[++i];
7579
+ break;
7580
+ case "--port":
7581
+ options.port = parseInt(argv[++i], 10);
7582
+ break;
7583
+ case "--dry-run":
7584
+ options.dryRun = true;
7585
+ break;
7586
+ case "--help":
7587
+ case "-h":
7588
+ printCocoonHelp();
7589
+ process.exit(0);
7590
+ }
7591
+ }
7592
+ return options;
7593
+ }
7594
+ function printCocoonHelp() {
7595
+ console.log(`
7596
+ Sanctuary Cocoon \u2014 Wrap any agent in sovereignty protection
7597
+
7598
+ Usage:
7599
+ npx @sanctuary-framework/cocoon --openclaw # Wrap OpenClaw agent
7600
+ npx @sanctuary-framework/cocoon --claude-code # Wrap Claude Code
7601
+ npx @sanctuary-framework/cocoon --cursor # Wrap Cursor
7602
+ npx @sanctuary-framework/cocoon --wrap config.json # Wrap generic MCP config
7603
+ npx @sanctuary-framework/cocoon --unwrap # Restore original config
7604
+
7605
+ Options:
7606
+ --openclaw Auto-detect and wrap OpenClaw agent
7607
+ --claude-code Auto-detect and wrap Claude Code
7608
+ --cursor Auto-detect and wrap Cursor
7609
+ --wrap <path> Wrap a specific MCP config file
7610
+ --unwrap Restore original config from backup
7611
+ --passphrase <p> Encryption passphrase
7612
+ --port <port> Dashboard port (default: 3501)
7613
+ --dry-run Show what would happen without making changes
7614
+ --help, -h Show this help
7615
+
7616
+ What happens:
7617
+ 1. Reads your agent's MCP server configuration
7618
+ 2. Backs up the original config to ~/.sanctuary/backup/
7619
+ 3. Rewrites the config so your agent routes through Sanctuary
7620
+ 4. All tool calls are logged, scanned for injection, and rate-limited
7621
+ 5. Dangerous operations require your approval via the dashboard
7622
+
7623
+ Rollback:
7624
+ --unwrap restores the original config from backup.
7625
+ Backups are preserved and never deleted.
7626
+ `);
7627
+ }
7628
+ var COCOON_GOVERNOR_DEFAULTS;
7629
+ var init_cli = __esm({
7630
+ "src/cocoon/cli.ts"() {
7631
+ init_config_reader();
7632
+ COCOON_GOVERNOR_DEFAULTS = {
7633
+ volume_limit: 200,
7634
+ // 200 calls per 10-minute window
7635
+ rate_limit_per_tool: 20,
7636
+ // 20 calls/min per individual tool
7637
+ lifetime_limit: 1e3
7638
+ // 1000 total calls per session
7639
+ };
7640
+ }
7641
+ });
6367
7642
 
6368
7643
  // src/dashboard-standalone.ts
6369
7644
  var dashboard_standalone_exports = {};
@@ -7415,7 +8690,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7415
8690
  const tools = [
7416
8691
  // ─── Commitment Schemes ───────────────────────────────────────────────
7417
8692
  {
7418
- name: "sanctuary/proof_commitment",
8693
+ name: "proof_commitment",
7419
8694
  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).",
7420
8695
  inputSchema: {
7421
8696
  type: "object",
@@ -7450,7 +8725,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7450
8725
  }
7451
8726
  },
7452
8727
  {
7453
- name: "sanctuary/proof_reveal",
8728
+ name: "proof_reveal",
7454
8729
  description: "Verify a previously committed value by revealing it with the blinding factor. Returns whether the revealed value matches the commitment.",
7455
8730
  inputSchema: {
7456
8731
  type: "object",
@@ -7488,7 +8763,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7488
8763
  },
7489
8764
  // ─── Disclosure Policies ──────────────────────────────────────────────
7490
8765
  {
7491
- name: "sanctuary/disclosure_set_policy",
8766
+ name: "disclosure_set_policy",
7492
8767
  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.",
7493
8768
  inputSchema: {
7494
8769
  type: "object",
@@ -7563,7 +8838,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7563
8838
  }
7564
8839
  },
7565
8840
  {
7566
- name: "sanctuary/disclosure_evaluate",
8841
+ name: "disclosure_evaluate",
7567
8842
  description: "Evaluate a disclosure request against an active policy. Returns per-field decisions: disclose, withhold, proof, or ask-principal.",
7568
8843
  inputSchema: {
7569
8844
  type: "object",
@@ -7639,7 +8914,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7639
8914
  },
7640
8915
  // ─── ZK Proof Tools ───────────────────────────────────────────────────
7641
8916
  {
7642
- name: "sanctuary/zk_commit",
8917
+ name: "zk_commit",
7643
8918
  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.",
7644
8919
  inputSchema: {
7645
8920
  type: "object",
@@ -7670,7 +8945,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7670
8945
  }
7671
8946
  },
7672
8947
  {
7673
- name: "sanctuary/zk_prove",
8948
+ name: "zk_prove",
7674
8949
  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.",
7675
8950
  inputSchema: {
7676
8951
  type: "object",
@@ -7711,7 +8986,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7711
8986
  }
7712
8987
  },
7713
8988
  {
7714
- name: "sanctuary/zk_verify",
8989
+ name: "zk_verify",
7715
8990
  description: "Verify a zero-knowledge proof of knowledge for a Pedersen commitment. Checks that the prover knows the commitment's opening without learning anything.",
7716
8991
  inputSchema: {
7717
8992
  type: "object",
@@ -7739,7 +9014,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7739
9014
  }
7740
9015
  },
7741
9016
  {
7742
- name: "sanctuary/zk_range_prove",
9017
+ name: "zk_range_prove",
7743
9018
  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.",
7744
9019
  inputSchema: {
7745
9020
  type: "object",
@@ -7789,7 +9064,7 @@ function createL3Tools(storage, masterKey, auditLog) {
7789
9064
  }
7790
9065
  },
7791
9066
  {
7792
- name: "sanctuary/zk_range_verify",
9067
+ name: "zk_range_verify",
7793
9068
  description: "Verify a zero-knowledge range proof \u2014 confirms a committed value is within the claimed range without learning the value.",
7794
9069
  inputSchema: {
7795
9070
  type: "object",
@@ -8242,7 +9517,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8242
9517
  const tools = [
8243
9518
  // ─── Reputation Recording ─────────────────────────────────────────
8244
9519
  {
8245
- name: "sanctuary/reputation_record",
9520
+ name: "reputation_record",
8246
9521
  description: "Record an interaction outcome as a signed attestation. Creates an EAS-compatible attestation signed by the specified identity.",
8247
9522
  inputSchema: {
8248
9523
  type: "object",
@@ -8335,7 +9610,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8335
9610
  },
8336
9611
  // ─── Reputation Query ─────────────────────────────────────────────
8337
9612
  {
8338
- name: "sanctuary/reputation_query",
9613
+ name: "reputation_query",
8339
9614
  description: "Query aggregated reputation data with filtering. Returns summary statistics, never raw interaction details.",
8340
9615
  inputSchema: {
8341
9616
  type: "object",
@@ -8383,7 +9658,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8383
9658
  },
8384
9659
  // ─── Reputation Export ─────────────────────────────────────────────
8385
9660
  {
8386
- name: "sanctuary/reputation_export",
9661
+ name: "reputation_export",
8387
9662
  description: "Export a portable reputation bundle (SANCTUARY_REP_V1). Includes all signed attestations for independent verification.",
8388
9663
  inputSchema: {
8389
9664
  type: "object",
@@ -8442,7 +9717,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8442
9717
  },
8443
9718
  // ─── Reputation Import ────────────────────────────────────────────
8444
9719
  {
8445
- name: "sanctuary/reputation_import",
9720
+ name: "reputation_import",
8446
9721
  description: "Import a reputation bundle from another Sanctuary instance. Verifies all attestation signatures by default.",
8447
9722
  inputSchema: {
8448
9723
  type: "object",
@@ -8494,7 +9769,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8494
9769
  },
8495
9770
  // ─── Sovereignty-Weighted Query ──────────────────────────────────
8496
9771
  {
8497
- name: "sanctuary/reputation_query_weighted",
9772
+ name: "reputation_query_weighted",
8498
9773
  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.",
8499
9774
  inputSchema: {
8500
9775
  type: "object",
@@ -8550,7 +9825,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8550
9825
  },
8551
9826
  // ─── Trust Bootstrap: Escrow ──────────────────────────────────────
8552
9827
  {
8553
- name: "sanctuary/bootstrap_create_escrow",
9828
+ name: "bootstrap_create_escrow",
8554
9829
  description: "Create an escrow record for trust bootstrapping. Allows new participants with no reputation to transact safely.",
8555
9830
  inputSchema: {
8556
9831
  type: "object",
@@ -8609,7 +9884,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8609
9884
  },
8610
9885
  // ─── Trust Bootstrap: Guarantee ───────────────────────────────────
8611
9886
  {
8612
- name: "sanctuary/bootstrap_provide_guarantee",
9887
+ name: "bootstrap_provide_guarantee",
8613
9888
  description: "A principal provides a signed reputation guarantee for a new agent. The guarantee certificate can be presented to counterparties.",
8614
9889
  inputSchema: {
8615
9890
  type: "object",
@@ -8687,7 +9962,7 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
8687
9962
  },
8688
9963
  // ─── Verascore Reputation Publish ────────────────────────────────
8689
9964
  {
8690
- name: "sanctuary/reputation_publish",
9965
+ name: "reputation_publish",
8691
9966
  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.",
8692
9967
  inputSchema: {
8693
9968
  type: "object",
@@ -9309,7 +10584,7 @@ var InjectionDetector = class {
9309
10584
  }
9310
10585
  /**
9311
10586
  * Scan tool arguments for injection signals.
9312
- * @param toolName Full tool name (e.g., "sanctuary/state_read")
10587
+ * @param toolName Full tool name (e.g., "state_read")
9313
10588
  * @param args Tool arguments
9314
10589
  * @returns DetectionResult with all detected signals
9315
10590
  */
@@ -10310,7 +11585,7 @@ var ApprovalGate = class {
10310
11585
  /**
10311
11586
  * Evaluate a tool call against the Principal Policy.
10312
11587
  *
10313
- * @param toolName - Full MCP tool name (e.g., "sanctuary/state_export")
11588
+ * @param toolName - Full MCP tool name (e.g., "state_export")
10314
11589
  * @param args - Tool call arguments (for context extraction)
10315
11590
  * @returns GateResult indicating whether the call is allowed
10316
11591
  */
@@ -10580,7 +11855,7 @@ init_router();
10580
11855
  function createPrincipalPolicyTools(policy, baseline, auditLog) {
10581
11856
  return [
10582
11857
  {
10583
- name: "sanctuary/principal_policy_view",
11858
+ name: "principal_policy_view",
10584
11859
  description: "View the current Principal Policy \u2014 the human-controlled rules governing what operations require approval. Read-only.",
10585
11860
  inputSchema: {
10586
11861
  type: "object",
@@ -10618,7 +11893,7 @@ function createPrincipalPolicyTools(policy, baseline, auditLog) {
10618
11893
  }
10619
11894
  },
10620
11895
  {
10621
- name: "sanctuary/principal_baseline_view",
11896
+ name: "principal_baseline_view",
10622
11897
  description: "View the current behavioral baseline \u2014 the session profile used for anomaly detection. Shows known namespaces, counterparties, and tool call counts. Read-only.",
10623
11898
  inputSchema: {
10624
11899
  type: "object",
@@ -10967,7 +12242,7 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
10967
12242
  };
10968
12243
  const tools = [
10969
12244
  {
10970
- name: "sanctuary/shr_generate",
12245
+ name: "shr_generate",
10971
12246
  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.",
10972
12247
  inputSchema: {
10973
12248
  type: "object",
@@ -10996,7 +12271,7 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
10996
12271
  }
10997
12272
  },
10998
12273
  {
10999
- name: "sanctuary/shr_verify",
12274
+ name: "shr_verify",
11000
12275
  description: "Verify a counterparty's Sovereignty Health Report (SHR). Checks signature validity, temporal validity, and assesses sovereignty level.",
11001
12276
  inputSchema: {
11002
12277
  type: "object",
@@ -11022,7 +12297,7 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
11022
12297
  }
11023
12298
  },
11024
12299
  {
11025
- name: "sanctuary/shr_gateway_export",
12300
+ name: "shr_gateway_export",
11026
12301
  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.",
11027
12302
  inputSchema: {
11028
12303
  type: "object",
@@ -11415,7 +12690,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11415
12690
  };
11416
12691
  const tools = [
11417
12692
  {
11418
- name: "sanctuary/handshake_initiate",
12693
+ name: "handshake_initiate",
11419
12694
  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.",
11420
12695
  inputSchema: {
11421
12696
  type: "object",
@@ -11442,7 +12717,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11442
12717
  }
11443
12718
  },
11444
12719
  {
11445
- name: "sanctuary/handshake_respond",
12720
+ name: "handshake_respond",
11446
12721
  description: "Respond to an incoming sovereignty handshake challenge. Verifies the initiator's SHR, signs their nonce, and returns our SHR with a counter-nonce.",
11447
12722
  inputSchema: {
11448
12723
  type: "object",
@@ -11563,7 +12838,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11563
12838
  }
11564
12839
  },
11565
12840
  {
11566
- name: "sanctuary/handshake_complete",
12841
+ name: "handshake_complete",
11567
12842
  description: "Complete a sovereignty handshake (initiator side). Verifies the responder's SHR and nonce signature, signs their nonce, and produces the final result.",
11568
12843
  inputSchema: {
11569
12844
  type: "object",
@@ -11618,7 +12893,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11618
12893
  }
11619
12894
  },
11620
12895
  {
11621
- name: "sanctuary/handshake_status",
12896
+ name: "handshake_status",
11622
12897
  description: "Check the status of a handshake session, or verify a completion message (responder side).",
11623
12898
  inputSchema: {
11624
12899
  type: "object",
@@ -11668,7 +12943,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11668
12943
  },
11669
12944
  // ─── Streamlined Exchange ─────────────────────────────────────────
11670
12945
  {
11671
- name: "sanctuary/handshake_exchange",
12946
+ name: "handshake_exchange",
11672
12947
  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).",
11673
12948
  inputSchema: {
11674
12949
  type: "object",
@@ -11735,7 +13010,7 @@ function createHandshakeTools(config, identityManager, masterKey, auditLog, opti
11735
13010
  }
11736
13011
  },
11737
13012
  {
11738
- name: "sanctuary/handshake_verify_attestation",
13013
+ name: "handshake_verify_attestation",
11739
13014
  description: "Verify a signed attestation artifact from another agent. Checks the Ed25519 signature, temporal validity, and structural integrity.",
11740
13015
  inputSchema: {
11741
13016
  type: "object",
@@ -11947,7 +13222,7 @@ function createFederationTools(auditLog, handshakeResults) {
11947
13222
  const tools = [
11948
13223
  // ─── Peer Management ──────────────────────────────────────────────
11949
13224
  {
11950
- name: "sanctuary/federation_peers",
13225
+ name: "federation_peers",
11951
13226
  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.",
11952
13227
  inputSchema: {
11953
13228
  type: "object",
@@ -12050,7 +13325,7 @@ function createFederationTools(auditLog, handshakeResults) {
12050
13325
  },
12051
13326
  // ─── Trust Evaluation ─────────────────────────────────────────────
12052
13327
  {
12053
- name: "sanctuary/federation_trust_evaluate",
13328
+ name: "federation_trust_evaluate",
12054
13329
  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.",
12055
13330
  inputSchema: {
12056
13331
  type: "object",
@@ -12085,7 +13360,7 @@ function createFederationTools(auditLog, handshakeResults) {
12085
13360
  },
12086
13361
  // ─── Federation Status ────────────────────────────────────────────
12087
13362
  {
12088
- name: "sanctuary/federation_status",
13363
+ name: "federation_status",
12089
13364
  description: "Overview of federation state: total peers, active connections, trust distribution, and readiness for cross-instance operations.",
12090
13365
  inputSchema: {
12091
13366
  type: "object",
@@ -12298,7 +13573,7 @@ function createBridgeTools(storage, masterKey, identityManager, auditLog, handsh
12298
13573
  const tools = [
12299
13574
  // ─── bridge_commit ─────────────────────────────────────────────────
12300
13575
  {
12301
- name: "sanctuary/bridge_commit",
13576
+ name: "bridge_commit",
12302
13577
  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.",
12303
13578
  inputSchema: {
12304
13579
  type: "object",
@@ -12400,7 +13675,7 @@ function createBridgeTools(storage, masterKey, identityManager, auditLog, handsh
12400
13675
  },
12401
13676
  // ─── bridge_verify ───────────────────────────────────────────────────
12402
13677
  {
12403
- name: "sanctuary/bridge_verify",
13678
+ name: "bridge_verify",
12404
13679
  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.",
12405
13680
  inputSchema: {
12406
13681
  type: "object",
@@ -12456,7 +13731,7 @@ function createBridgeTools(storage, masterKey, identityManager, auditLog, handsh
12456
13731
  },
12457
13732
  // ─── bridge_attest ───────────────────────────────────────────────────
12458
13733
  {
12459
- name: "sanctuary/bridge_attest",
13734
+ name: "bridge_attest",
12460
13735
  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.",
12461
13736
  inputSchema: {
12462
13737
  type: "object",
@@ -13020,7 +14295,7 @@ function generateGaps(env, l1, l2, l3, l4) {
13020
14295
  title: "No context gating for outbound inference calls",
13021
14296
  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.",
13022
14297
  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,
13023
- 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.",
14298
+ 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.",
13024
14299
  incident_class: INCIDENT_CONTEXT_LEAKAGE
13025
14300
  });
13026
14301
  }
@@ -13032,7 +14307,7 @@ function generateGaps(env, l1, l2, l3, l4) {
13032
14307
  title: "No audit trail",
13033
14308
  description: "No audit trail exists for tool call history. There is no record of what operations were executed, when, or by whom.",
13034
14309
  openclaw_relevance: null,
13035
- sanctuary_solution: "Sanctuary maintains an encrypted audit log of all operations, queryable via sanctuary/monitor_audit_log.",
14310
+ sanctuary_solution: "Sanctuary maintains an encrypted audit log of all operations, queryable via monitor_audit_log.",
13036
14311
  incident_class: INCIDENT_CLAUDE_CODE_LEAK
13037
14312
  });
13038
14313
  }
@@ -13067,7 +14342,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13067
14342
  recs.push({
13068
14343
  priority: 1,
13069
14344
  action: "Create a cryptographic identity \u2014 your agent's foundation for all sovereignty operations",
13070
- tool: "sanctuary/identity_create",
14345
+ tool: "identity_create",
13071
14346
  effort: "immediate",
13072
14347
  impact: "critical"
13073
14348
  });
@@ -13076,7 +14351,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13076
14351
  recs.push({
13077
14352
  priority: 2,
13078
14353
  action: "Migrate plaintext agent state to Sanctuary's encrypted store",
13079
- tool: "sanctuary/state_write",
14354
+ tool: "state_write",
13080
14355
  effort: "minutes",
13081
14356
  impact: "critical"
13082
14357
  });
@@ -13084,7 +14359,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13084
14359
  recs.push({
13085
14360
  priority: 3,
13086
14361
  action: "Generate a Sovereignty Health Report to present to counterparties",
13087
- tool: "sanctuary/shr_generate",
14362
+ tool: "shr_generate",
13088
14363
  effort: "immediate",
13089
14364
  impact: "high"
13090
14365
  });
@@ -13092,7 +14367,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13092
14367
  recs.push({
13093
14368
  priority: 4,
13094
14369
  action: "Enable the three-tier Principal Policy gate for graduated approval",
13095
- tool: "sanctuary/principal_policy_view",
14370
+ tool: "principal_policy_view",
13096
14371
  effort: "minutes",
13097
14372
  impact: "high"
13098
14373
  });
@@ -13101,7 +14376,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13101
14376
  recs.push({
13102
14377
  priority: 5,
13103
14378
  action: "Configure context gating to control what flows to LLM providers",
13104
- tool: "sanctuary/context_gate_set_policy",
14379
+ tool: "context_gate_set_policy",
13105
14380
  effort: "minutes",
13106
14381
  impact: "high"
13107
14382
  });
@@ -13110,7 +14385,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13110
14385
  recs.push({
13111
14386
  priority: 6,
13112
14387
  action: "Start recording reputation attestations from completed interactions",
13113
- tool: "sanctuary/reputation_record",
14388
+ tool: "reputation_record",
13114
14389
  effort: "minutes",
13115
14390
  impact: "medium"
13116
14391
  });
@@ -13119,7 +14394,7 @@ function generateRecommendations(env, l1, l2, l3, l4) {
13119
14394
  recs.push({
13120
14395
  priority: 7,
13121
14396
  action: "Configure selective disclosure policies for data sharing",
13122
- tool: "sanctuary/disclosure_set_policy",
14397
+ tool: "disclosure_set_policy",
13123
14398
  effort: "hours",
13124
14399
  impact: "medium"
13125
14400
  });
@@ -13259,7 +14534,7 @@ function wordWrap(text, maxWidth) {
13259
14534
  function createAuditTools(config) {
13260
14535
  const tools = [
13261
14536
  {
13262
- name: "sanctuary/sovereignty_audit",
14537
+ name: "sovereignty_audit",
13263
14538
  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.",
13264
14539
  inputSchema: {
13265
14540
  type: "object",
@@ -13287,6 +14562,302 @@ function createAuditTools(config) {
13287
14562
  return { tools };
13288
14563
  }
13289
14564
 
14565
+ // src/audit/siem-formatter.ts
14566
+ function parseGateDecision(details) {
14567
+ if (!details || typeof details.gate_decision !== "string") {
14568
+ return "auto-allow";
14569
+ }
14570
+ const decision = details.gate_decision.toLowerCase();
14571
+ if (decision === "approve" || decision === "deny") {
14572
+ return decision;
14573
+ }
14574
+ return "auto-allow";
14575
+ }
14576
+ function parseTier(details) {
14577
+ if (!details || typeof details.tier !== "number") {
14578
+ return 3;
14579
+ }
14580
+ return Math.max(1, Math.min(3, details.tier));
14581
+ }
14582
+ function parseSessionId(details) {
14583
+ if (!details || typeof details.session_id !== "string") {
14584
+ return "unknown";
14585
+ }
14586
+ return details.session_id;
14587
+ }
14588
+ function parseAgentDid(details) {
14589
+ if (!details || typeof details.agent_did !== "string") {
14590
+ return "unknown";
14591
+ }
14592
+ return details.agent_did;
14593
+ }
14594
+ function gateToCEFSeverity(decision, tier) {
14595
+ if (decision === "deny") {
14596
+ return 8;
14597
+ }
14598
+ if (decision === "approve") {
14599
+ if (tier === 1) return 5;
14600
+ if (tier === 2) return 3;
14601
+ }
14602
+ return 1;
14603
+ }
14604
+ function formatAsCEF(entry, options) {
14605
+ const version = "0";
14606
+ const vendor = "Sanctuary";
14607
+ const product = "MCP-Server";
14608
+ const productVersion = "0.7.0";
14609
+ const decision = parseGateDecision(entry.details);
14610
+ const tier = parseTier(entry.details);
14611
+ const sessionId = parseSessionId(entry.details);
14612
+ const agentDid = parseAgentDid(entry.details);
14613
+ const severity = gateToCEFSeverity(decision, tier);
14614
+ const signatureId = entry.operation.replace(/[^a-zA-Z0-9_-]/g, "_");
14615
+ const description = `Sanctuary ${entry.operation}`;
14616
+ const extensions = [
14617
+ `src=${agentDid}`,
14618
+ `act=${entry.operation}`,
14619
+ `outcome=${decision}`,
14620
+ `tier=${tier}`,
14621
+ `cs1=${sessionId}`,
14622
+ `cs1Label=SessionId`,
14623
+ `rt=${new Date(entry.timestamp).getTime()}`,
14624
+ `layer=${entry.layer}`,
14625
+ `result=${entry.result}`
14626
+ ];
14627
+ return `CEF:${version}|${vendor}|${product}|${productVersion}|${signatureId}|${description}|${severity}|${extensions.join(" ")}`;
14628
+ }
14629
+ function gateToOCSFStatus(decision, result) {
14630
+ return decision === "deny" || result === "failure" ? 2 : 1;
14631
+ }
14632
+ function gateToCOCSFSeverity(decision, tier) {
14633
+ if (decision === "deny") {
14634
+ return 4;
14635
+ }
14636
+ if (decision === "approve") {
14637
+ if (tier === 1) return 3;
14638
+ if (tier === 2) return 2;
14639
+ }
14640
+ return 1;
14641
+ }
14642
+ function gateToOCSFDisposition(decision) {
14643
+ return decision === "deny" ? 2 : 1;
14644
+ }
14645
+ function formatAsOCSF(entry) {
14646
+ const decision = parseGateDecision(entry.details);
14647
+ const tier = parseTier(entry.details);
14648
+ const agentDid = parseAgentDid(entry.details);
14649
+ const timestamp = new Date(entry.timestamp).getTime();
14650
+ const statusId = gateToOCSFStatus(decision, entry.result);
14651
+ const severityId = gateToCOCSFSeverity(decision, tier);
14652
+ const dispositionId = gateToOCSFDisposition(decision);
14653
+ return {
14654
+ class_uid: 3001,
14655
+ class_name: "API Activity",
14656
+ category_uid: 3,
14657
+ category_name: "Application Activity",
14658
+ severity_id: severityId,
14659
+ time: timestamp,
14660
+ activity_id: 1,
14661
+ activity_name: "API Call",
14662
+ actor: {
14663
+ user: {
14664
+ uid: agentDid
14665
+ }
14666
+ },
14667
+ api: {
14668
+ operation: entry.operation,
14669
+ service: {
14670
+ name: "sanctuary-mcp"
14671
+ }
14672
+ },
14673
+ status_id: statusId,
14674
+ disposition_id: dispositionId,
14675
+ metadata: {
14676
+ version: "1.3.0",
14677
+ product: {
14678
+ name: "Sanctuary Framework",
14679
+ vendor_name: "Erik Newton",
14680
+ version: "0.7.0"
14681
+ }
14682
+ }
14683
+ };
14684
+ }
14685
+
14686
+ // src/audit/siem-tools.ts
14687
+ function createSIEMTools(auditLog) {
14688
+ const tools = [
14689
+ {
14690
+ name: "audit_export_siem",
14691
+ 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.",
14692
+ inputSchema: {
14693
+ type: "object",
14694
+ properties: {
14695
+ format: {
14696
+ type: "string",
14697
+ enum: ["cef", "ocsf"],
14698
+ description: 'Output format: "cef" (Common Event Format, newline-delimited) or "ocsf" (Open Cybersecurity Schema Framework, JSON array)'
14699
+ },
14700
+ since: {
14701
+ type: "string",
14702
+ description: "Optional ISO 8601 timestamp. Export only events on or after this time. Defaults to 24 hours ago."
14703
+ },
14704
+ until: {
14705
+ type: "string",
14706
+ description: "Optional ISO 8601 timestamp. Export only events before this time. Defaults to now."
14707
+ },
14708
+ limit: {
14709
+ type: "number",
14710
+ description: "Maximum number of events to export (default 100, max 1000). Set to 1000 for bulk exports to SIEMs."
14711
+ },
14712
+ filter_tool: {
14713
+ type: "string",
14714
+ description: 'Optional. Export only events from this tool name (e.g., "sovereignty_audit", "state_set"). Case-insensitive substring matching.'
14715
+ },
14716
+ filter_decision: {
14717
+ type: "string",
14718
+ enum: ["approve", "deny", "auto-allow"],
14719
+ description: 'Optional. Export only events with this gate decision: "approve" (manual approval), "deny" (blocked), or "auto-allow" (Tier 3 auto-allowed).'
14720
+ },
14721
+ filter_layer: {
14722
+ type: "string",
14723
+ enum: ["l1", "l2", "l3", "l4"],
14724
+ description: "Optional. Export only events from this sovereignty layer (L1=Cognitive, L2=Operational, L3=Disclosure, L4=Reputation)."
14725
+ },
14726
+ filter_result: {
14727
+ type: "string",
14728
+ enum: ["success", "failure"],
14729
+ description: 'Optional. Export only events with this result: "success" or "failure".'
14730
+ }
14731
+ },
14732
+ required: ["format"]
14733
+ },
14734
+ handler: async (args) => {
14735
+ const format = String(args.format || "").toLowerCase();
14736
+ if (format !== "cef" && format !== "ocsf") {
14737
+ return {
14738
+ content: [
14739
+ {
14740
+ type: "text",
14741
+ text: JSON.stringify({
14742
+ error: "Invalid format. Must be 'cef' or 'ocsf'."
14743
+ })
14744
+ }
14745
+ ]
14746
+ };
14747
+ }
14748
+ let since;
14749
+ if (args.since) {
14750
+ since = String(args.since);
14751
+ const sinceDate = new Date(since);
14752
+ if (isNaN(sinceDate.getTime())) {
14753
+ return {
14754
+ content: [
14755
+ {
14756
+ type: "text",
14757
+ text: JSON.stringify({
14758
+ error: `Invalid 'since' timestamp: ${since}. Must be ISO 8601.`
14759
+ })
14760
+ }
14761
+ ]
14762
+ };
14763
+ }
14764
+ } else {
14765
+ const now = /* @__PURE__ */ new Date();
14766
+ const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
14767
+ since = oneDayAgo.toISOString();
14768
+ }
14769
+ let until;
14770
+ if (args.until) {
14771
+ until = String(args.until);
14772
+ const untilDate = new Date(until);
14773
+ if (isNaN(untilDate.getTime())) {
14774
+ return {
14775
+ content: [
14776
+ {
14777
+ type: "text",
14778
+ text: JSON.stringify({
14779
+ error: `Invalid 'until' timestamp: ${until}. Must be ISO 8601.`
14780
+ })
14781
+ }
14782
+ ]
14783
+ };
14784
+ }
14785
+ }
14786
+ let limit = 100;
14787
+ if (typeof args.limit === "number") {
14788
+ limit = Math.max(1, Math.min(1e3, args.limit));
14789
+ }
14790
+ const filterTool = args.filter_tool ? String(args.filter_tool).toLowerCase() : void 0;
14791
+ const filterDecision = args.filter_decision ? String(args.filter_decision).toLowerCase() : void 0;
14792
+ const filterLayer = args.filter_layer ? String(args.filter_layer).toLowerCase() : void 0;
14793
+ const filterResult = args.filter_result ? String(args.filter_result).toLowerCase() : void 0;
14794
+ const result = await auditLog.query({
14795
+ since,
14796
+ layer: filterLayer,
14797
+ operation_type: void 0,
14798
+ // Will filter after
14799
+ limit
14800
+ });
14801
+ let filtered = result.entries;
14802
+ if (filterTool) {
14803
+ filtered = filtered.filter(
14804
+ (e) => e.operation.toLowerCase().includes(filterTool)
14805
+ );
14806
+ }
14807
+ if (filterDecision) {
14808
+ filtered = filtered.filter((e) => {
14809
+ const decision = String(e.details?.gate_decision || "auto-allow").toLowerCase();
14810
+ return decision === filterDecision;
14811
+ });
14812
+ }
14813
+ if (filterResult) {
14814
+ filtered = filtered.filter((e) => e.result === filterResult);
14815
+ }
14816
+ if (until) {
14817
+ const untilDate = new Date(until);
14818
+ filtered = filtered.filter((e) => new Date(e.timestamp) < untilDate);
14819
+ }
14820
+ let output;
14821
+ if (format === "cef") {
14822
+ const cefLines = filtered.map((entry) => formatAsCEF(entry));
14823
+ output = cefLines.join("\n");
14824
+ } else {
14825
+ const ocsfObjects = filtered.map((entry) => formatAsOCSF(entry));
14826
+ output = JSON.stringify(ocsfObjects, null, 2);
14827
+ }
14828
+ return {
14829
+ content: [
14830
+ {
14831
+ type: "text",
14832
+ text: JSON.stringify({
14833
+ format,
14834
+ count: filtered.length,
14835
+ total_available: result.total,
14836
+ time_range: {
14837
+ since,
14838
+ until: until || (/* @__PURE__ */ new Date()).toISOString()
14839
+ },
14840
+ filters: {
14841
+ tool: filterTool,
14842
+ decision: filterDecision,
14843
+ layer: filterLayer,
14844
+ result: filterResult
14845
+ },
14846
+ note: format === "cef" ? `${filtered.length} CEF events (newline-delimited). Each line is a complete CEF event.` : `${filtered.length} OCSF objects in JSON array format.`
14847
+ })
14848
+ },
14849
+ {
14850
+ type: "text",
14851
+ text: output
14852
+ }
14853
+ ]
14854
+ };
14855
+ }
14856
+ }
14857
+ ];
14858
+ return { tools };
14859
+ }
14860
+
13290
14861
  // src/l2-operational/context-gate-tools.ts
13291
14862
  init_router();
13292
14863
 
@@ -14288,13 +15859,17 @@ var ContextGateEnforcer = class {
14288
15859
  * Check if a tool should be filtered based on bypass prefixes.
14289
15860
  *
14290
15861
  * SEC-033: Uses exact namespace component matching, not bare startsWith().
14291
- * A prefix of "sanctuary/" matches "sanctuary/state_read" but NOT
14292
- * "sanctuary_evil/steal_data" (no slash boundary confusion). The prefix
14293
- * must match exactly up to its length, and the prefix must end with "/"
14294
- * to enforce namespace boundaries (if it doesn't, we add one for safety).
15862
+ * A prefix of "proxy/" matches "proxy/server/tool" but NOT "proxyevil/steal".
15863
+ * The prefix must match exactly up to its length, and the prefix must end
15864
+ * with "/" to enforce namespace boundaries (if it doesn't, we add one).
15865
+ *
15866
+ * Special sentinel: "*" bypasses ALL tools (used when all Sanctuary-internal
15867
+ * tools should skip context gating — the default). Only proxy/external tools
15868
+ * should be filtered in production.
14295
15869
  */
14296
15870
  shouldFilter(toolName) {
14297
15871
  for (const prefix of this.config.bypass_prefixes) {
15872
+ if (prefix === "*") return false;
14298
15873
  const safePrefix = prefix.endsWith("/") ? prefix : prefix + "/";
14299
15874
  if (toolName === safePrefix.slice(0, -1) || toolName.startsWith(safePrefix)) {
14300
15875
  return false;
@@ -14391,8 +15966,8 @@ function createContextGateTools(storage, masterKey, auditLog) {
14391
15966
  const enforcerConfig = {
14392
15967
  enabled: false,
14393
15968
  // Off by default; agents must explicitly enable it
14394
- bypass_prefixes: ["sanctuary/"],
14395
- // Skip internal tools by default
15969
+ bypass_prefixes: ["*"],
15970
+ // Skip all Sanctuary-internal tools; only proxy/ tools get filtered
14396
15971
  log_only: false,
14397
15972
  // Filter immediately
14398
15973
  on_deny: "block"
@@ -14402,7 +15977,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14402
15977
  const tools = [
14403
15978
  // ── Set Policy ──────────────────────────────────────────────────
14404
15979
  {
14405
- name: "sanctuary/context_gate_set_policy",
15980
+ name: "context_gate_set_policy",
14406
15981
  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.",
14407
15982
  inputSchema: {
14408
15983
  type: "object",
@@ -14511,13 +16086,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14511
16086
  rules: policy.rules,
14512
16087
  default_action: policy.default_action,
14513
16088
  created_at: policy.created_at,
14514
- message: "Context-gating policy created. Use sanctuary/context_gate_filter to apply this policy before making outbound calls."
16089
+ message: "Context-gating policy created. Use context_gate_filter to apply this policy before making outbound calls."
14515
16090
  });
14516
16091
  }
14517
16092
  },
14518
16093
  // ── Apply Template ───────────────────────────────────────────────
14519
16094
  {
14520
- name: "sanctuary/context_gate_apply_template",
16095
+ name: "context_gate_apply_template",
14521
16096
  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.",
14522
16097
  inputSchema: {
14523
16098
  type: "object",
@@ -14566,13 +16141,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14566
16141
  rules: policy.rules,
14567
16142
  default_action: policy.default_action,
14568
16143
  created_at: policy.created_at,
14569
- 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."
16144
+ 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."
14570
16145
  });
14571
16146
  }
14572
16147
  },
14573
16148
  // ── Recommend Policy ────────────────────────────────────────────
14574
16149
  {
14575
- name: "sanctuary/context_gate_recommend",
16150
+ name: "context_gate_recommend",
14576
16151
  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.",
14577
16152
  inputSchema: {
14578
16153
  type: "object",
@@ -14609,7 +16184,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14609
16184
  });
14610
16185
  return toolResult({
14611
16186
  ...recommendation,
14612
- 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.",
16187
+ 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.",
14613
16188
  available_templates: listTemplateIds().map((id) => {
14614
16189
  const t = TEMPLATES[id];
14615
16190
  return { id, name: t.name, description: t.description };
@@ -14619,7 +16194,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14619
16194
  },
14620
16195
  // ── Filter Context ──────────────────────────────────────────────
14621
16196
  {
14622
- name: "sanctuary/context_gate_filter",
16197
+ name: "context_gate_filter",
14623
16198
  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.",
14624
16199
  inputSchema: {
14625
16200
  type: "object",
@@ -14725,7 +16300,7 @@ function createContextGateTools(storage, masterKey, auditLog) {
14725
16300
  },
14726
16301
  // ── List Policies ───────────────────────────────────────────────
14727
16302
  {
14728
- name: "sanctuary/context_gate_list_policies",
16303
+ name: "context_gate_list_policies",
14729
16304
  description: "List all configured context-gating policies. Returns policy IDs, names, rule summaries, and default actions.",
14730
16305
  inputSchema: {
14731
16306
  type: "object",
@@ -14748,13 +16323,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14748
16323
  updated_at: p.updated_at
14749
16324
  })),
14750
16325
  count: policies.length,
14751
- 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.`
16326
+ 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.`
14752
16327
  });
14753
16328
  }
14754
16329
  },
14755
16330
  // ── Enforcer Status ─────────────────────────────────────────────────
14756
16331
  {
14757
- name: "sanctuary/context_gate_enforcer_status",
16332
+ name: "context_gate_enforcer_status",
14758
16333
  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.",
14759
16334
  inputSchema: {
14760
16335
  type: "object",
@@ -14775,13 +16350,13 @@ function createContextGateTools(storage, masterKey, auditLog) {
14775
16350
  return toolResult({
14776
16351
  enforcer_status: status,
14777
16352
  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."),
14778
- 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."
16353
+ 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."
14779
16354
  });
14780
16355
  }
14781
16356
  },
14782
16357
  // ── Enforcer Configuration ──────────────────────────────────────────
14783
16358
  {
14784
- name: "sanctuary/context_gate_enforcer_configure",
16359
+ name: "context_gate_enforcer_configure",
14785
16360
  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.",
14786
16361
  inputSchema: {
14787
16362
  type: "object",
@@ -15107,7 +16682,7 @@ function assessL2Hardening(storagePath) {
15107
16682
  function createL2HardeningTools(storagePath, auditLog) {
15108
16683
  return [
15109
16684
  {
15110
- name: "sanctuary/l2_hardening_status",
16685
+ name: "l2_hardening_status",
15111
16686
  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.",
15112
16687
  inputSchema: {
15113
16688
  type: "object",
@@ -15175,7 +16750,7 @@ function createL2HardeningTools(storagePath, auditLog) {
15175
16750
  }
15176
16751
  },
15177
16752
  {
15178
- name: "sanctuary/l2_verify_isolation",
16753
+ name: "l2_verify_isolation",
15179
16754
  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.",
15180
16755
  inputSchema: {
15181
16756
  type: "object",
@@ -15277,7 +16852,7 @@ function createSovereigntyProfileTools(profileStore, auditLog) {
15277
16852
  const tools = [
15278
16853
  // ── Get Profile ──────────────────────────────────────────────────
15279
16854
  {
15280
- name: "sanctuary/sovereignty_profile_get",
16855
+ name: "sovereignty_profile_get",
15281
16856
  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.",
15282
16857
  inputSchema: {
15283
16858
  type: "object",
@@ -15296,7 +16871,7 @@ function createSovereigntyProfileTools(profileStore, auditLog) {
15296
16871
  },
15297
16872
  // ── Update Profile ───────────────────────────────────────────────
15298
16873
  {
15299
- name: "sanctuary/sovereignty_profile_update",
16874
+ name: "sovereignty_profile_update",
15300
16875
  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.",
15301
16876
  inputSchema: {
15302
16877
  type: "object",
@@ -15377,7 +16952,7 @@ function createSovereigntyProfileTools(profileStore, auditLog) {
15377
16952
  },
15378
16953
  // ── Generate System Prompt ───────────────────────────────────────
15379
16954
  {
15380
- name: "sanctuary/sovereignty_profile_generate_prompt",
16955
+ name: "sovereignty_profile_generate_prompt",
15381
16956
  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.",
15382
16957
  inputSchema: {
15383
16958
  type: "object",
@@ -15772,6 +17347,7 @@ var ProxyRouter = class {
15772
17347
  confidence: injectionResult.confidence,
15773
17348
  latency_ms: Date.now() - start
15774
17349
  }, "failure");
17350
+ this.notifyProxyCall(proxyName, serverName, "blocked", "injection_detected", tier);
15775
17351
  return toolResult({
15776
17352
  error: "Operation not permitted",
15777
17353
  proxy: true
@@ -15802,6 +17378,7 @@ var ProxyRouter = class {
15802
17378
  reason: govResult.reason,
15803
17379
  latency_ms: Date.now() - start
15804
17380
  }, "failure");
17381
+ this.notifyProxyCall(proxyName, serverName, "blocked", govResult.reason, tier);
15805
17382
  return toolResult({
15806
17383
  error: "Operation not permitted",
15807
17384
  proxy: true,
@@ -15836,6 +17413,7 @@ var ProxyRouter = class {
15836
17413
  decision: "allowed",
15837
17414
  latency_ms: latencyMs
15838
17415
  });
17416
+ this.notifyProxyCall(proxyName, serverName, "allowed", void 0, tier);
15839
17417
  return this.normalizeResponse(result);
15840
17418
  } catch (err) {
15841
17419
  const latencyMs = Date.now() - start;
@@ -15855,6 +17433,7 @@ var ProxyRouter = class {
15855
17433
  error: errorMessage,
15856
17434
  latency_ms: latencyMs
15857
17435
  }, "failure");
17436
+ this.notifyProxyCall(proxyName, serverName, "error", errorMessage, tier);
15858
17437
  return {
15859
17438
  content: [{
15860
17439
  type: "text",
@@ -15869,6 +17448,24 @@ var ProxyRouter = class {
15869
17448
  }
15870
17449
  };
15871
17450
  }
17451
+ /**
17452
+ * Notify the onProxyCall callback if configured.
17453
+ */
17454
+ notifyProxyCall(tool, server, decision, reason, tier) {
17455
+ if (this.options.onProxyCall) {
17456
+ try {
17457
+ this.options.onProxyCall({
17458
+ tool,
17459
+ server,
17460
+ decision,
17461
+ reason,
17462
+ tier,
17463
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17464
+ });
17465
+ } catch {
17466
+ }
17467
+ }
17468
+ }
15872
17469
  /**
15873
17470
  * Call an upstream tool with a timeout.
15874
17471
  */
@@ -16142,7 +17739,7 @@ function createGovernorTools(governor, auditLog) {
16142
17739
  const tools = [
16143
17740
  // ── Governor Status ─────────────────────────────────────────────
16144
17741
  {
16145
- name: "sanctuary/governor_status",
17742
+ name: "governor_status",
16146
17743
  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.",
16147
17744
  inputSchema: {
16148
17745
  type: "object",
@@ -16182,7 +17779,7 @@ function createGovernorTools(governor, auditLog) {
16182
17779
  },
16183
17780
  // ── Governor Reset ──────────────────────────────────────────────
16184
17781
  {
16185
- name: "sanctuary/governor_reset",
17782
+ name: "governor_reset",
16186
17783
  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.",
16187
17784
  inputSchema: {
16188
17785
  type: "object",
@@ -16275,7 +17872,7 @@ function createSanctuaryTools(opts) {
16275
17872
  const tools = [
16276
17873
  // ─── sanctuary_bootstrap ───────────────────────────────────────────
16277
17874
  {
16278
- name: "sanctuary/sanctuary_bootstrap",
17875
+ name: "sanctuary_bootstrap",
16279
17876
  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.",
16280
17877
  inputSchema: {
16281
17878
  type: "object",
@@ -16413,7 +18010,7 @@ function createSanctuaryTools(opts) {
16413
18010
  },
16414
18011
  // ─── sanctuary_policy_status ───────────────────────────────────────
16415
18012
  {
16416
- name: "sanctuary/sanctuary_policy_status",
18013
+ name: "sanctuary_policy_status",
16417
18014
  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).",
16418
18015
  inputSchema: {
16419
18016
  type: "object",
@@ -16443,7 +18040,7 @@ function createSanctuaryTools(opts) {
16443
18040
  },
16444
18041
  // ─── sanctuary_export_identity_bundle ──────────────────────────────
16445
18042
  {
16446
- name: "sanctuary/sanctuary_export_identity_bundle",
18043
+ name: "sanctuary_export_identity_bundle",
16447
18044
  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.",
16448
18045
  inputSchema: {
16449
18046
  type: "object",
@@ -16512,7 +18109,7 @@ function createSanctuaryTools(opts) {
16512
18109
  },
16513
18110
  // ─── sanctuary_link_to_human ───────────────────────────────────────
16514
18111
  {
16515
- name: "sanctuary/sanctuary_link_to_human",
18112
+ name: "sanctuary_link_to_human",
16516
18113
  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.",
16517
18114
  inputSchema: {
16518
18115
  type: "object",
@@ -16567,7 +18164,7 @@ function createSanctuaryTools(opts) {
16567
18164
  },
16568
18165
  // ─── sanctuary_sign_challenge ──────────────────────────────────────
16569
18166
  {
16570
- name: "sanctuary/sanctuary_sign_challenge",
18167
+ name: "sanctuary_sign_challenge",
16571
18168
  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.",
16572
18169
  inputSchema: {
16573
18170
  type: "object",
@@ -16801,7 +18398,7 @@ async function createSanctuaryServer(options) {
16801
18398
  }
16802
18399
  const l2Tools = [
16803
18400
  {
16804
- name: "sanctuary/exec_attest",
18401
+ name: "exec_attest",
16805
18402
  description: "Generate an attestation of the current execution environment, including sovereignty assessment and degradation report.",
16806
18403
  inputSchema: {
16807
18404
  type: "object",
@@ -16852,7 +18449,7 @@ async function createSanctuaryServer(options) {
16852
18449
  }
16853
18450
  },
16854
18451
  {
16855
- name: "sanctuary/monitor_health",
18452
+ name: "monitor_health",
16856
18453
  description: "Sanctuary Health Report (SHR) \u2014 standardized sovereignty status.",
16857
18454
  inputSchema: { type: "object", properties: {} },
16858
18455
  handler: async () => {
@@ -16901,7 +18498,7 @@ async function createSanctuaryServer(options) {
16901
18498
  }
16902
18499
  },
16903
18500
  {
16904
- name: "sanctuary/monitor_audit_log",
18501
+ name: "monitor_audit_log",
16905
18502
  description: "Query the sovereignty audit log.",
16906
18503
  inputSchema: {
16907
18504
  type: "object",
@@ -16927,7 +18524,7 @@ async function createSanctuaryServer(options) {
16927
18524
  }
16928
18525
  ];
16929
18526
  const manifestTool = {
16930
- name: "sanctuary/manifest",
18527
+ name: "manifest",
16931
18528
  description: "Generate the Sanctuary Interface Manifest (SIM) \u2014 a machine-readable declaration of this server's capabilities.",
16932
18529
  inputSchema: { type: "object", properties: {} },
16933
18530
  handler: async () => {
@@ -17039,6 +18636,7 @@ async function createSanctuaryServer(options) {
17039
18636
  handshakeResults
17040
18637
  );
17041
18638
  const { tools: auditTools } = createAuditTools(config);
18639
+ const { tools: siemTools } = createSIEMTools(auditLog);
17042
18640
  const { tools: contextGateTools, enforcer: contextGateEnforcer } = createContextGateTools(storage, masterKey, auditLog);
17043
18641
  const hardeningTools = createL2HardeningTools(config.storage_path, auditLog);
17044
18642
  const profileStore = new SovereigntyProfileStore(storage, masterKey);
@@ -17121,7 +18719,7 @@ async function createSanctuaryServer(options) {
17121
18719
  const dashboardTools = [];
17122
18720
  if (dashboard) {
17123
18721
  dashboardTools.push({
17124
- name: "sanctuary/dashboard_open",
18722
+ name: "dashboard_open",
17125
18723
  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.",
17126
18724
  inputSchema: {
17127
18725
  type: "object",
@@ -17155,6 +18753,7 @@ async function createSanctuaryServer(options) {
17155
18753
  ...federationTools,
17156
18754
  ...bridgeTools,
17157
18755
  ...auditTools,
18756
+ ...siemTools,
17158
18757
  ...contextGateTools,
17159
18758
  ...hardeningTools,
17160
18759
  ...profileTools,
@@ -17201,7 +18800,12 @@ async function createSanctuaryServer(options) {
17201
18800
  }
17202
18801
  return args;
17203
18802
  },
17204
- governor
18803
+ governor,
18804
+ onProxyCall: (data) => {
18805
+ if (dashboard) {
18806
+ dashboard.broadcastProxyCall(data);
18807
+ }
18808
+ }
17205
18809
  }
17206
18810
  );
17207
18811
  clientManager.configure(enabledServers).catch((err) => {
@@ -17219,6 +18823,7 @@ async function createSanctuaryServer(options) {
17219
18823
  auditLog,
17220
18824
  clientManager
17221
18825
  });
18826
+ dashboard.enableFortressView(enabledServers.length);
17222
18827
  }
17223
18828
  }
17224
18829
  }
@@ -17338,6 +18943,12 @@ async function main() {
17338
18943
  await runStandaloneDashboard(args.slice(1));
17339
18944
  return;
17340
18945
  }
18946
+ if (args[0] === "cocoon") {
18947
+ const { parseCocoonArgs: parseCocoonArgs2, runCocoon: runCocoon2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
18948
+ const cocoonOpts = parseCocoonArgs2(args.slice(1));
18949
+ await runCocoon2(cocoonOpts);
18950
+ return;
18951
+ }
17341
18952
  for (let i = 0; i < args.length; i++) {
17342
18953
  if (args[i] === "--dashboard") {
17343
18954
  process.env.SANCTUARY_DASHBOARD_ENABLED = "true";
@@ -17405,6 +19016,7 @@ Sovereignty infrastructure for agents in the agentic economy.
17405
19016
  Usage:
17406
19017
  sanctuary-mcp-server [options] # MCP server (stdio)
17407
19018
  sanctuary-mcp-server dashboard [opts] # Standalone dashboard
19019
+ sanctuary-mcp-server cocoon [opts] # Wrap agent in Cocoon protection
17408
19020
 
17409
19021
  Options:
17410
19022
  --dashboard Enable the Principal Dashboard (web UI)
@@ -17417,6 +19029,10 @@ Subcommands:
17417
19029
  Reads from the same storage as the MCP server.
17418
19030
  Use "sanctuary-mcp-server dashboard --help" for options.
17419
19031
 
19032
+ cocoon Wrap an existing agent in Sanctuary's enforcement chain.
19033
+ One command to protect any MCP-compatible agent.
19034
+ Use "sanctuary-mcp-server cocoon --help" for options.
19035
+
17420
19036
  Environment variables:
17421
19037
  SANCTUARY_STORAGE_PATH State directory (default: ~/.sanctuary)
17422
19038
  SANCTUARY_PASSPHRASE Key derivation passphrase