@clampd/mcp-proxy 0.2.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.
@@ -0,0 +1,516 @@
1
+ /**
2
+ * Live dashboard — serves an HTML page at GET / with a real-time event log
3
+ * of all tool calls flowing through the proxy.
4
+ *
5
+ * Features:
6
+ * - Enriched event data (classification, session flags, intent labels, encodings)
7
+ * - Expandable detail rows with full gateway response data
8
+ * - Real-time risk trend SVG sparkline chart
9
+ * - Session summary panel with aggregated stats
10
+ * - Export (Copy Report / Download JSON)
11
+ * - Attack Demo panel with pre-built payloads
12
+ * - Status filter tabs (All / Allowed / Blocked / Flagged / Errors)
13
+ */
14
+ // ── Dashboard HTML ────────────────────────────────────────────────────
15
+ function renderDashboard(events, opts) {
16
+ const modeLabel = opts.dryRun ? "DRY-RUN" : "LIVE";
17
+ const blocked = events.filter((e) => e.status === "blocked").length;
18
+ const flagged = events.filter((e) => e.status === "flagged").length;
19
+ const allowed = events.filter((e) => e.status === "allowed").length;
20
+ const errors = events.filter((e) => e.status === "error").length;
21
+ const total = events.length;
22
+ const threatRate = total > 0 ? (((blocked + flagged) / total) * 100).toFixed(1) : "—";
23
+ const avgLatency = total > 0 ? Math.round(events.reduce((s, e) => s + e.latency_ms, 0) / total) : 0;
24
+ const totalRulesFired = events.reduce((s, e) => s + (e.matched_rules?.length ?? 0), 0);
25
+ const last50 = events.slice(-50).reverse();
26
+ // Build risk sparkline SVG
27
+ const sparkline = renderSparkline(last50);
28
+ // Build session summary
29
+ const sessionHtml = opts.sessionStats ? renderSessionSummary(opts.sessionStats) : "";
30
+ // Build event rows with expandable detail
31
+ const rows = last50.map((e, i) => renderEventRow(e, i)).join("");
32
+ // Build demo panel
33
+ const demoHtml = opts.demoPanel ? renderDemoPanel(opts) : "";
34
+ return `<!DOCTYPE html>
35
+ <html lang="en">
36
+ <head>
37
+ <meta charset="utf-8">
38
+ <meta name="viewport" content="width=device-width, initial-scale=1">
39
+ <title>Clampd MCP Proxy</title>
40
+ <style>
41
+ * { margin: 0; padding: 0; box-sizing: border-box; }
42
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0a0a0a; color: #e0e0e0; }
43
+ .header { padding: 20px 32px; border-bottom: 1px solid #222; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
44
+ .header h1 { font-size: 20px; color: #fff; }
45
+ .badge { padding: 3px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; }
46
+ .badge-live { background: #1a472a; color: #4ade80; }
47
+ .badge-dryrun { background: #422006; color: #fbbf24; }
48
+ .stats { display: flex; gap: 24px; padding: 14px 32px; border-bottom: 1px solid #222; flex-wrap: wrap; align-items: flex-end; }
49
+ .stat { display: flex; flex-direction: column; }
50
+ .stat-label { font-size: 10px; text-transform: uppercase; color: #555; letter-spacing: 0.5px; }
51
+ .stat-value { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
52
+ .stat-allowed { color: #22c55e; }
53
+ .stat-blocked { color: #ef4444; }
54
+ .stat-flagged { color: #f59e0b; }
55
+ .stat-error { color: #6b7280; }
56
+ .stat-sep { width: 1px; height: 32px; background: #222; margin: 0 4px; }
57
+ .info { padding: 10px 32px; font-size: 12px; color: #555; border-bottom: 1px solid #222; display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
58
+ .info span { color: #888; }
59
+ .filters { display: flex; gap: 4px; padding: 12px 32px; border-bottom: 1px solid #222; }
60
+ .filter-btn { padding: 5px 14px; border: 1px solid #333; background: transparent; color: #888; border-radius: 4px; cursor: pointer; font-size: 12px; font-family: inherit; }
61
+ .filter-btn:hover { border-color: #555; color: #ccc; }
62
+ .filter-btn.active { background: #1a1a2e; border-color: #6366f1; color: #a5b4fc; }
63
+ .sparkline-container { padding: 8px 32px; border-bottom: 1px solid #222; }
64
+ .session-panel { padding: 14px 32px; border-bottom: 1px solid #222; display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-start; }
65
+ .session-block { display: flex; flex-direction: column; gap: 4px; }
66
+ .session-title { font-size: 10px; text-transform: uppercase; color: #555; letter-spacing: 0.5px; }
67
+ .session-val { font-size: 13px; color: #ccc; font-family: 'SF Mono', 'Fira Code', monospace; }
68
+ .badge-sm { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 500; margin: 1px 2px; }
69
+ .badge-rule { background: #2d1b69; color: #c4b5fd; }
70
+ .badge-label { background: #1e3a5f; color: #7dd3fc; }
71
+ .badge-flag { background: #422006; color: #fbbf24; }
72
+ .badge-encoding { background: #431407; color: #fb923c; }
73
+ .badge-scope { background: #042f2e; color: #5eead4; }
74
+ .badge-degraded { background: #450a0a; color: #fca5a5; }
75
+ .badge-pii { background: #4a1d96; color: #d8b4fe; }
76
+ .badge-secret { background: #7f1d1d; color: #fca5a5; }
77
+ .table-wrap { max-height: 500px; overflow-y: auto; }
78
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
79
+ th { text-align: left; padding: 7px 12px; background: #111; color: #666; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; position: sticky; top: 0; z-index: 1; }
80
+ td { padding: 7px 12px; border-bottom: 1px solid #151515; vertical-align: top; }
81
+ tr.event-row { cursor: pointer; transition: background 0.1s; }
82
+ tr.event-row:hover { background: #111; }
83
+ .mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px; }
84
+ .params-cell { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #666; }
85
+ .status-allowed { color: #22c55e; font-weight: 600; }
86
+ .status-blocked { color: #ef4444; font-weight: 600; }
87
+ .status-flagged { color: #f59e0b; font-weight: 600; }
88
+ .status-error { color: #6b7280; font-weight: 600; }
89
+ .detail-row { display: none; }
90
+ .detail-row.open { display: table-row; }
91
+ .detail-cell { padding: 12px 16px; background: #0d0d14; border-bottom: 1px solid #1a1a2e; }
92
+ .detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 24px; }
93
+ .detail-section { display: flex; flex-direction: column; gap: 3px; }
94
+ .detail-label { font-size: 10px; text-transform: uppercase; color: #444; letter-spacing: 0.3px; }
95
+ .detail-val { font-size: 12px; color: #bbb; }
96
+ .detail-params { max-height: 180px; overflow: auto; background: #080810; border: 1px solid #1a1a2e; border-radius: 4px; padding: 8px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px; color: #888; white-space: pre-wrap; word-break: break-all; margin-top: 6px; }
97
+ .empty { padding: 48px; text-align: center; color: #333; }
98
+ .actions { margin-left: auto; display: flex; gap: 8px; }
99
+ .btn { padding: 5px 12px; border: 1px solid #333; background: #111; color: #aaa; border-radius: 4px; cursor: pointer; font-size: 11px; font-family: inherit; transition: all 0.15s; }
100
+ .btn:hover { background: #1a1a2e; border-color: #6366f1; color: #c4b5fd; }
101
+ .btn-copy.copied { background: #1a472a; border-color: #22c55e; color: #4ade80; }
102
+ .demo-panel { padding: 16px 32px; border-bottom: 1px solid #222; }
103
+ .demo-title { font-size: 13px; font-weight: 600; color: #a5b4fc; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
104
+ .demo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; }
105
+ .demo-card { padding: 8px 12px; border: 1px solid #222; border-radius: 6px; cursor: pointer; transition: all 0.15s; }
106
+ .demo-card:hover { border-color: #6366f1; background: #0d0d14; }
107
+ .demo-card.running { opacity: 0.5; pointer-events: none; }
108
+ .demo-card-name { font-size: 12px; font-weight: 600; color: #ccc; }
109
+ .demo-card-desc { font-size: 10px; color: #555; margin-top: 2px; }
110
+ .demo-card-result { font-size: 10px; margin-top: 4px; font-family: 'SF Mono', monospace; }
111
+ .class-malicious { color: #ef4444; }
112
+ .class-suspicious { color: #f59e0b; }
113
+ .class-benign { color: #22c55e; }
114
+ @media (max-width: 768px) {
115
+ .stats { gap: 12px; padding: 10px 16px; }
116
+ .stat-value { font-size: 18px; }
117
+ .info, .filters, .session-panel, .demo-panel, .sparkline-container { padding-left: 16px; padding-right: 16px; }
118
+ .header { padding: 16px; }
119
+ .detail-grid { grid-template-columns: 1fr; }
120
+ .demo-grid { grid-template-columns: 1fr; }
121
+ }
122
+ </style>
123
+ </head>
124
+ <body>
125
+ <div class="header">
126
+ <h1>Clampd MCP Proxy</h1>
127
+ <span class="badge ${modeLabel === "LIVE" ? "badge-live" : "badge-dryrun"}">${modeLabel}</span>
128
+ <div class="actions">
129
+ <button class="btn" onclick="location.reload()" id="refreshBtn">Refresh</button>
130
+ <button class="btn btn-copy" onclick="copyReport()" id="copyBtn">Copy Report</button>
131
+ <button class="btn" onclick="downloadJSON()">Download JSON</button>
132
+ </div>
133
+ </div>
134
+ <div class="stats">
135
+ <div class="stat">
136
+ <span class="stat-label">Allowed</span>
137
+ <span class="stat-value stat-allowed">${allowed}</span>
138
+ </div>
139
+ <div class="stat">
140
+ <span class="stat-label">Blocked</span>
141
+ <span class="stat-value stat-blocked">${blocked}</span>
142
+ </div>
143
+ <div class="stat">
144
+ <span class="stat-label">Flagged</span>
145
+ <span class="stat-value stat-flagged">${flagged}</span>
146
+ </div>
147
+ <div class="stat">
148
+ <span class="stat-label">Errors</span>
149
+ <span class="stat-value stat-error">${errors}</span>
150
+ </div>
151
+ <div class="stat-sep"></div>
152
+ <div class="stat">
153
+ <span class="stat-label">Total</span>
154
+ <span class="stat-value">${total}</span>
155
+ </div>
156
+ <div class="stat">
157
+ <span class="stat-label">Threat Rate</span>
158
+ <span class="stat-value stat-blocked">${threatRate}${total > 0 ? "%" : ""}</span>
159
+ </div>
160
+ <div class="stat">
161
+ <span class="stat-label">Rules Fired</span>
162
+ <span class="stat-value" style="color:#c4b5fd">${totalRulesFired}</span>
163
+ </div>
164
+ <div class="stat">
165
+ <span class="stat-label">Avg Latency</span>
166
+ <span class="stat-value" style="color:#888">${avgLatency}ms</span>
167
+ </div>
168
+ </div>
169
+ <div class="info">
170
+ Gateway: <span>${escapeHtml(opts.gatewayUrl)}</span> &nbsp;|&nbsp;
171
+ Agent: <span>${escapeHtml(opts.agentId)}</span> &nbsp;|&nbsp;
172
+ Port: <span>${opts.port}</span>
173
+ </div>
174
+ ${sessionHtml}
175
+ ${sparkline}
176
+ <div class="filters">
177
+ <button class="filter-btn active" onclick="filterEvents('all')">All (${total})</button>
178
+ <button class="filter-btn" onclick="filterEvents('allowed')">Allowed (${allowed})</button>
179
+ <button class="filter-btn" onclick="filterEvents('blocked')">Blocked (${blocked})</button>
180
+ <button class="filter-btn" onclick="filterEvents('flagged')">Flagged (${flagged})</button>
181
+ <button class="filter-btn" onclick="filterEvents('error')">Errors (${errors})</button>
182
+ </div>
183
+ ${demoHtml}
184
+ <div class="table-wrap">
185
+ <table>
186
+ <thead>
187
+ <tr>
188
+ <th>Time</th>
189
+ <th>Tool</th>
190
+ <th>Status</th>
191
+ <th>Risk</th>
192
+ <th>Rules</th>
193
+ <th>Latency</th>
194
+ <th>Reason</th>
195
+ </tr>
196
+ </thead>
197
+ <tbody id="eventBody">
198
+ ${rows || '<tr><td colspan="7" class="empty">No tool calls yet. Connect Claude Desktop to http://localhost:' + opts.port + '/sse</td></tr>'}
199
+ </tbody>
200
+ </table>
201
+ </div>
202
+ <script>
203
+ // SSE — track new event count, show badge on refresh button
204
+ const evtSource = new EventSource('/events');
205
+ let newCount = 0;
206
+ evtSource.onmessage = function() {
207
+ newCount++;
208
+ const btn = document.getElementById('refreshBtn');
209
+ if (btn) btn.textContent = 'Refresh (' + newCount + ' new)';
210
+ };
211
+
212
+ // Filter events by status
213
+ function filterEvents(status) {
214
+ document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
215
+ event.target.classList.add('active');
216
+ document.querySelectorAll('.event-row').forEach(row => {
217
+ const detail = row.nextElementSibling;
218
+ if (status === 'all' || row.dataset.status === status) {
219
+ row.style.display = '';
220
+ // keep detail state
221
+ } else {
222
+ row.style.display = 'none';
223
+ if (detail && detail.classList.contains('detail-row')) detail.style.display = 'none';
224
+ }
225
+ });
226
+ }
227
+
228
+ // Toggle detail row
229
+ function toggleDetail(idx) {
230
+ const detail = document.getElementById('detail-' + idx);
231
+ if (detail) detail.classList.toggle('open');
232
+ }
233
+
234
+ // Copy markdown report
235
+ function copyReport() {
236
+ const report = document.getElementById('reportData').textContent;
237
+ navigator.clipboard.writeText(report).then(() => {
238
+ const btn = document.getElementById('copyBtn');
239
+ btn.textContent = 'Copied!';
240
+ btn.classList.add('copied');
241
+ setTimeout(() => { btn.textContent = 'Copy Report'; btn.classList.remove('copied'); }, 2000);
242
+ });
243
+ }
244
+
245
+ // Download JSON
246
+ function downloadJSON() {
247
+ const data = document.getElementById('jsonData').textContent;
248
+ const blob = new Blob([data], { type: 'application/json' });
249
+ const url = URL.createObjectURL(blob);
250
+ const a = document.createElement('a');
251
+ a.href = url;
252
+ a.download = 'clampd-proxy-events-' + new Date().toISOString().slice(0,19).replace(/:/g,'-') + '.json';
253
+ a.click();
254
+ URL.revokeObjectURL(url);
255
+ }
256
+
257
+ // Demo attack buttons
258
+ function runDemo(attackId) {
259
+ const card = document.getElementById('demo-' + attackId);
260
+ if (!card) return;
261
+ card.classList.add('running');
262
+ fetch('/demo/attack', {
263
+ method: 'POST',
264
+ headers: { 'Content-Type': 'application/json' },
265
+ body: JSON.stringify({ attack_id: attackId }),
266
+ })
267
+ .then(r => r.json())
268
+ .then(result => {
269
+ card.classList.remove('running');
270
+ const resultEl = card.querySelector('.demo-card-result');
271
+ if (resultEl) {
272
+ const color = result.status === 'blocked' ? '#ef4444' : result.status === 'allowed' ? '#22c55e' : '#6b7280';
273
+ resultEl.innerHTML = '<span style="color:' + color + '">' + result.status.toUpperCase() + '</span> risk=' + (result.risk_score || 0).toFixed(2) + (result.matched_rules?.length ? ' [' + result.matched_rules.join(', ') + ']' : '');
274
+ }
275
+ // Update refresh button badge
276
+ newCount++;
277
+ const rbtn = document.getElementById('refreshBtn');
278
+ if (rbtn) rbtn.textContent = 'Refresh (' + newCount + ' new)';
279
+ })
280
+ .catch(() => { card.classList.remove('running'); });
281
+ }
282
+ </script>
283
+ <div style="display:none">
284
+ <pre id="reportData">${escapeHtml(generateReport(events, opts))}</pre>
285
+ <pre id="jsonData">${escapeHtml(JSON.stringify(events, null, 2))}</pre>
286
+ </div>
287
+ </body>
288
+ </html>`;
289
+ }
290
+ // ── Event Row (expandable) ───────────────────────────────────────────
291
+ function renderEventRow(e, idx) {
292
+ const statusClass = e.status === "blocked" ? "status-blocked" : e.status === "flagged" ? "status-flagged" : e.status === "error" ? "status-error" : "status-allowed";
293
+ const rules = e.matched_rules?.map((r) => `<span class="badge-sm badge-rule">${escapeHtml(r)}</span>`).join("") ?? "-";
294
+ const time = e.timestamp.split("T")[1]?.slice(0, 12) ?? e.timestamp;
295
+ // Detail panel content
296
+ const classColor = e.classification === "Malicious" ? "class-malicious" : e.classification === "Suspicious" ? "class-suspicious" : "class-benign";
297
+ const detailSections = [];
298
+ if (e.classification) {
299
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Classification</span><span class="detail-val ${classColor}">${escapeHtml(e.classification)}</span></div>`);
300
+ }
301
+ if (e.action) {
302
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Action</span><span class="detail-val">${escapeHtml(e.action)}</span></div>`);
303
+ }
304
+ if (e.reasoning) {
305
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Reasoning</span><span class="detail-val">${escapeHtml(e.reasoning)}</span></div>`);
306
+ }
307
+ if (e.intent_labels?.length) {
308
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Intent Labels</span><span class="detail-val">${e.intent_labels.map((l) => `<span class="badge-sm badge-label">${escapeHtml(l)}</span>`).join("")}</span></div>`);
309
+ }
310
+ if (e.session_flags?.length) {
311
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Session Flags</span><span class="detail-val">${e.session_flags.map((f) => `<span class="badge-sm badge-flag">${escapeHtml(f)}</span>`).join("")}</span></div>`);
312
+ }
313
+ if (e.encodings_detected?.length) {
314
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Encodings Detected</span><span class="detail-val">${e.encodings_detected.map((enc) => `<span class="badge-sm badge-encoding">${escapeHtml(enc)}</span>`).join("")}</span></div>`);
315
+ }
316
+ if (e.scope_granted) {
317
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Scope Granted</span><span class="detail-val"><span class="badge-sm badge-scope">${escapeHtml(e.scope_granted)}</span></span></div>`);
318
+ }
319
+ if (e.degraded_stages?.length) {
320
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Degraded Stages</span><span class="detail-val">${e.degraded_stages.map((s) => `<span class="badge-sm badge-degraded">${escapeHtml(s)}</span>`).join("")}</span></div>`);
321
+ }
322
+ if (e.scan_details?.pii_found?.length) {
323
+ detailSections.push(`<div class="detail-section"><span class="detail-label">PII Found</span><span class="detail-val">${e.scan_details.pii_found.map((p) => `<span class="badge-sm badge-pii">${escapeHtml(p.pii_type)} (${p.count})</span>`).join("")}</span></div>`);
324
+ }
325
+ if (e.scan_details?.secrets_found?.length) {
326
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Secrets Found</span><span class="detail-val">${e.scan_details.secrets_found.map((s) => `<span class="badge-sm badge-secret">${escapeHtml(s.secret_type)} (${s.count})</span>`).join("")}</span></div>`);
327
+ }
328
+ if (e.descriptor_hash) {
329
+ detailSections.push(`<div class="detail-section"><span class="detail-label">Descriptor Hash</span><span class="detail-val mono">${escapeHtml(e.descriptor_hash.slice(0, 16))}...</span></div>`);
330
+ }
331
+ const reasonText = e.reason ? escapeHtml(e.reason.length > 60 ? e.reason.slice(0, 60) + "..." : e.reason) : "-";
332
+ const hasDetail = detailSections.length > 0 || e.params.length > 5;
333
+ const expandIcon = hasDetail ? `<span style="color:#444;font-size:10px">&#9654;</span> ` : "";
334
+ // Pretty-print params for detail view
335
+ let prettyParams = e.params;
336
+ try {
337
+ prettyParams = JSON.stringify(JSON.parse(e.params), null, 2);
338
+ }
339
+ catch { /* keep as-is */ }
340
+ return `
341
+ <tr class="event-row" data-status="${e.status}" onclick="toggleDetail(${idx})">
342
+ <td class="mono">${expandIcon}${escapeHtml(time)}</td>
343
+ <td><strong>${escapeHtml(e.tool)}</strong></td>
344
+ <td class="${statusClass}">${e.status.toUpperCase()}</td>
345
+ <td class="mono">${e.risk_score.toFixed(2)}</td>
346
+ <td>${rules}</td>
347
+ <td class="mono">${e.latency_ms}ms</td>
348
+ <td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#888">${reasonText}</td>
349
+ </tr>
350
+ <tr class="detail-row" id="detail-${idx}">
351
+ <td colspan="7" class="detail-cell">
352
+ <div class="detail-grid">${detailSections.join("")}</div>
353
+ ${e.params.length > 2 ? `<div class="detail-params">${escapeHtml(prettyParams)}</div>` : ""}
354
+ </td>
355
+ </tr>`;
356
+ }
357
+ // ── Risk Sparkline SVG ───────────────────────────────────────────────
358
+ function renderSparkline(events) {
359
+ if (events.length === 0)
360
+ return "";
361
+ const width = 800;
362
+ const height = 60;
363
+ const padding = 4;
364
+ const count = Math.min(events.length, 50);
365
+ const data = events.slice(0, count).reverse(); // oldest first for chart
366
+ const stepX = (width - padding * 2) / Math.max(count - 1, 1);
367
+ let points = "";
368
+ let dots = "";
369
+ for (let i = 0; i < data.length; i++) {
370
+ const x = padding + i * stepX;
371
+ const y = height - padding - data[i].risk_score * (height - padding * 2);
372
+ points += `${x},${y} `;
373
+ const color = data[i].status === "blocked" ? "#ef4444" : data[i].risk_score > 0.85 ? "#ef4444" : data[i].risk_score > 0.5 ? "#f59e0b" : "#22c55e";
374
+ const r = data[i].status === "blocked" ? 4 : 3;
375
+ dots += `<circle cx="${x}" cy="${y}" r="${r}" fill="${color}" opacity="0.8"/>`;
376
+ if (data[i].status === "blocked") {
377
+ // X marker for blocked
378
+ dots += `<line x1="${x - 3}" y1="${y - 3}" x2="${x + 3}" y2="${y + 3}" stroke="#ef4444" stroke-width="1.5"/>`;
379
+ dots += `<line x1="${x + 3}" y1="${y - 3}" x2="${x - 3}" y2="${y + 3}" stroke="#ef4444" stroke-width="1.5"/>`;
380
+ }
381
+ }
382
+ return `<div class="sparkline-container">
383
+ <svg viewBox="0 0 ${width} ${height}" width="100%" height="${height}" preserveAspectRatio="none">
384
+ <rect width="${width}" height="${height}" fill="#050508" rx="4"/>
385
+ <line x1="${padding}" y1="${height - padding - 0.5 * (height - padding * 2)}" x2="${width - padding}" y2="${height - padding - 0.5 * (height - padding * 2)}" stroke="#1a1a1a" stroke-width="0.5" stroke-dasharray="4"/>
386
+ <line x1="${padding}" y1="${height - padding - 0.85 * (height - padding * 2)}" x2="${width - padding}" y2="${height - padding - 0.85 * (height - padding * 2)}" stroke="#2a1515" stroke-width="0.5" stroke-dasharray="4"/>
387
+ <polyline points="${points}" fill="none" stroke="#6366f1" stroke-width="1.5" opacity="0.5"/>
388
+ ${dots}
389
+ <text x="${padding}" y="10" fill="#333" font-size="8" font-family="sans-serif">1.0</text>
390
+ <text x="${padding}" y="${height - 2}" fill="#333" font-size="8" font-family="sans-serif">0.0</text>
391
+ </svg>
392
+ </div>`;
393
+ }
394
+ // ── Session Summary ──────────────────────────────────────────────────
395
+ function renderSessionSummary(stats) {
396
+ const duration = stats.firstCallAt && stats.lastCallAt
397
+ ? formatDuration(new Date(stats.lastCallAt).getTime() - new Date(stats.firstCallAt).getTime())
398
+ : "—";
399
+ const avgRisk = stats.toolCallCount > 0 ? (stats.totalRisk / stats.toolCallCount).toFixed(2) : "0.00";
400
+ const topRules = Object.entries(stats.rulesTriggered)
401
+ .sort(([, a], [, b]) => b - a)
402
+ .slice(0, 5)
403
+ .map(([rule, count]) => `<span class="badge-sm badge-rule">${escapeHtml(rule)} (${count})</span>`)
404
+ .join("");
405
+ const indicators = [];
406
+ if (stats.piiDetected)
407
+ indicators.push(`<span class="badge-sm badge-pii">PII Detected</span>`);
408
+ if (stats.secretsDetected)
409
+ indicators.push(`<span class="badge-sm badge-secret">Secrets Detected</span>`);
410
+ return `<div class="session-panel">
411
+ <div class="session-block"><span class="session-title">Session Duration</span><span class="session-val">${duration}</span></div>
412
+ <div class="session-block"><span class="session-title">Avg Risk</span><span class="session-val">${avgRisk}</span></div>
413
+ <div class="session-block"><span class="session-title">Unique Tools (${stats.uniqueTools.length})</span><span class="session-val">${stats.uniqueTools.slice(0, 8).map((t) => escapeHtml(t)).join(", ") || "—"}</span></div>
414
+ <div class="session-block"><span class="session-title">Top Rules</span><span class="session-val">${topRules || "—"}</span></div>
415
+ ${indicators.length ? `<div class="session-block"><span class="session-title">Alerts</span><span class="session-val">${indicators.join(" ")}</span></div>` : ""}
416
+ </div>`;
417
+ }
418
+ // ── Demo Panel ───────────────────────────────────────────────────────
419
+ function renderDemoPanel(opts) {
420
+ const attacks = [
421
+ { id: "sql_injection", name: "SQL Injection", desc: 'DROP TABLE users via database.query' },
422
+ { id: "path_traversal", name: "Path Traversal", desc: '../../etc/passwd via read_file' },
423
+ { id: "prompt_injection", name: "Prompt Injection", desc: 'IGNORE ALL INSTRUCTIONS via write_file' },
424
+ { id: "ssrf", name: "SSRF", desc: '169.254.169.254 metadata via http_request' },
425
+ { id: "reverse_shell", name: "Reverse Shell", desc: '#!/bin/bash >& /dev/tcp/ via write_file' },
426
+ { id: "schema_injection", name: "Schema Injection", desc: '<functions> XML tag injection' },
427
+ { id: "encoded_attack", name: "Encoded Attack", desc: 'Base64-encoded rm -rf /' },
428
+ { id: "safe_call", name: "Safe Call", desc: 'Normal read_file /tmp/report.txt' },
429
+ ];
430
+ const cards = attacks.map((a) => `
431
+ <div class="demo-card" id="demo-${a.id}" onclick="runDemo('${a.id}')">
432
+ <div class="demo-card-name">${escapeHtml(a.name)}</div>
433
+ <div class="demo-card-desc">${escapeHtml(a.desc)}</div>
434
+ <div class="demo-card-result"></div>
435
+ </div>`).join("");
436
+ return `<div class="demo-panel">
437
+ <div class="demo-title">Demo Attacks <span class="badge badge-dryrun" style="font-size:9px">click to test</span></div>
438
+ <div class="demo-grid">${cards}</div>
439
+ </div>`;
440
+ }
441
+ // ── Report Generation ────────────────────────────────────────────────
442
+ function generateReport(events, opts) {
443
+ const blocked = events.filter((e) => e.status === "blocked");
444
+ const flagged = events.filter((e) => e.status === "flagged");
445
+ const total = events.length;
446
+ const avgLatency = total > 0 ? Math.round(events.reduce((s, e) => s + e.latency_ms, 0) / total) : 0;
447
+ const threatRate = total > 0 ? (((blocked.length + flagged.length) / total) * 100).toFixed(1) : "0";
448
+ // Count rules
449
+ const ruleCounts = {};
450
+ for (const e of events) {
451
+ for (const r of e.matched_rules ?? []) {
452
+ ruleCounts[r] = (ruleCounts[r] ?? 0) + 1;
453
+ }
454
+ }
455
+ const ruleRows = Object.entries(ruleCounts)
456
+ .sort(([, a], [, b]) => b - a)
457
+ .map(([rule, count]) => `| ${rule} | ${count} |`)
458
+ .join("\n");
459
+ const blockedRows = blocked
460
+ .slice(0, 20)
461
+ .map((e) => {
462
+ const time = e.timestamp.split("T")[1]?.slice(0, 8) ?? "";
463
+ const rules = e.matched_rules?.join(", ") ?? "";
464
+ return `| ${time} | ${e.tool} | ${e.risk_score.toFixed(2)} | ${rules} | ${(e.reason ?? "").slice(0, 50)} |`;
465
+ })
466
+ .join("\n");
467
+ return `# Clampd MCP Proxy Security Report
468
+ **Agent:** ${opts.agentId} | **Gateway:** ${opts.gatewayUrl}
469
+ **Generated:** ${new Date().toISOString()}
470
+
471
+ ## Summary
472
+ - Allowed: ${total - blocked.length - flagged.length} | Blocked: ${blocked.length} | Flagged: ${flagged.length}
473
+ - Threat Rate: ${threatRate}%
474
+ - Avg Latency: ${avgLatency}ms
475
+ - Total Calls: ${total}
476
+
477
+ ## Rules Triggered
478
+ | Rule | Count |
479
+ |------|-------|
480
+ ${ruleRows || "| — | — |"}
481
+
482
+ ## Blocked Calls
483
+ | Time | Tool | Risk | Rules | Reason |
484
+ |------|------|------|-------|--------|
485
+ ${blockedRows || "| — | — | — | — | — |"}
486
+ `;
487
+ }
488
+ // ── Utilities ─────────────────────────────────────────────────────────
489
+ function formatDuration(ms) {
490
+ if (ms < 1000)
491
+ return `${ms}ms`;
492
+ const secs = Math.floor(ms / 1000);
493
+ if (secs < 60)
494
+ return `${secs}s`;
495
+ const mins = Math.floor(secs / 60);
496
+ if (mins < 60)
497
+ return `${mins}m ${secs % 60}s`;
498
+ return `${Math.floor(mins / 60)}h ${mins % 60}m`;
499
+ }
500
+ function escapeHtml(str) {
501
+ return str
502
+ .replace(/&/g, "&amp;")
503
+ .replace(/</g, "&lt;")
504
+ .replace(/>/g, "&gt;")
505
+ .replace(/"/g, "&quot;");
506
+ }
507
+ // ── Serve ─────────────────────────────────────────────────────────────
508
+ export function serveDashboard(_req, res, events, opts) {
509
+ const html = renderDashboard(events, opts);
510
+ res.writeHead(200, {
511
+ "Content-Type": "text/html; charset=utf-8",
512
+ "Cache-Control": "no-cache",
513
+ });
514
+ res.end(html);
515
+ }
516
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Fleet orchestrator — runs multiple MCP proxy instances from a single config.
3
+ *
4
+ * Each proxy wraps a different upstream MCP server with a different agent identity,
5
+ * enabling multi-agent demos showing scope isolation, delegation chains, and
6
+ * behavioral correlation.
7
+ *
8
+ * Usage:
9
+ * clampd-mcp-proxy --fleet-config fleet.json
10
+ *
11
+ * Config:
12
+ * {
13
+ * "gateway": "http://ag-gateway:8080",
14
+ * "apiKey": "ag_test_acme_demo_2026",
15
+ * "secret": "ags_...",
16
+ * "dashboardPort": 3000,
17
+ * "agents": [
18
+ * { "name": "Data Analyst", "agentId": "...", "port": 3003, "upstream": "...", "color": "#3b82f6" },
19
+ * { "name": "DevOps Bot", "agentId": "...", "port": 3004, "upstream": "...", "color": "#22c55e" },
20
+ * { "name": "DB Admin", "agentId": "...", "port": 3005, "upstream": "...", "color": "#f59e0b" }
21
+ * ]
22
+ * }
23
+ */
24
+ import type { ProxyEvent } from "./dashboard.js";
25
+ export interface FleetAgentConfig {
26
+ name: string;
27
+ agentId: string;
28
+ port: number;
29
+ upstream: string;
30
+ color?: string;
31
+ scanInput?: boolean;
32
+ scanOutput?: boolean;
33
+ checkResponse?: boolean;
34
+ demoPanel?: boolean;
35
+ secret?: string;
36
+ }
37
+ export interface FleetConfig {
38
+ gateway: string;
39
+ apiKey: string;
40
+ secret?: string;
41
+ dashboardPort: number;
42
+ dryRun?: boolean;
43
+ verbose?: boolean;
44
+ agents: FleetAgentConfig[];
45
+ }
46
+ export interface FleetEvent extends ProxyEvent {
47
+ agentName?: string;
48
+ agentColor?: string;
49
+ agentId?: string;
50
+ }
51
+ export declare function startFleet(configPath: string): Promise<void>;
52
+ //# sourceMappingURL=fleet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../src/fleet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAKjD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAID,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAyBD,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2GlE"}