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