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