brakit 0.8.5 → 0.8.6
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/README.md +3 -3
- package/dist/api.d.ts +120 -115
- package/dist/api.js +371 -340
- package/dist/bin/brakit.js +457 -332
- package/dist/dashboard.html +60 -59
- package/dist/mcp/server.js +75 -90
- package/dist/runtime/index.js +637 -529
- package/package.json +1 -1
package/dist/dashboard.html
CHANGED
|
@@ -2141,8 +2141,8 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2141
2141
|
'<div class="ov-stat"><span class="ov-stat-value">' + state.fetches.length + '</span><span class="ov-stat-label">Fetches</span></div>';
|
|
2142
2142
|
container.appendChild(summary);
|
|
2143
2143
|
|
|
2144
|
-
var all = state.
|
|
2145
|
-
var open = all.filter(function(si) { return si.state === 'open' || si.state === 'fixing'; });
|
|
2144
|
+
var all = state.issues || [];
|
|
2145
|
+
var open = all.filter(function(si) { return si.state === 'open' || si.state === 'fixing' || si.state === 'regressed'; });
|
|
2146
2146
|
var resolved = all.filter(function(si) { return si.state === 'resolved'; });
|
|
2147
2147
|
|
|
2148
2148
|
if (open.length === 0 && resolved.length === 0) {
|
|
@@ -2174,31 +2174,35 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2174
2174
|
|
|
2175
2175
|
for (var i = 0; i < open.length; i++) {
|
|
2176
2176
|
(function(si) {
|
|
2177
|
-
var
|
|
2177
|
+
var issue = si.issue;
|
|
2178
2178
|
var card = document.createElement('div');
|
|
2179
2179
|
card.className = 'ov-card';
|
|
2180
2180
|
|
|
2181
|
-
var sevCfg = SEV[
|
|
2181
|
+
var sevCfg = SEV[issue.severity];
|
|
2182
2182
|
var iconCls = sevCfg.cls;
|
|
2183
2183
|
var iconChar = sevCfg.icon;
|
|
2184
2184
|
|
|
2185
2185
|
var expandHtml = '';
|
|
2186
|
-
if (
|
|
2187
|
-
if (
|
|
2188
|
-
expandHtml += '<span class="ov-card-link" data-nav="' +
|
|
2186
|
+
if (issue.detail) expandHtml += issue.detail;
|
|
2187
|
+
if (issue.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(issue.hint) + '</div>';
|
|
2188
|
+
if (issue.nav) expandHtml += '<span class="ov-card-link" data-nav="' + issue.nav + '">View in ' + (NAV_LABELS[issue.nav] || issue.nav) + ' \u2192</span>';
|
|
2189
2189
|
|
|
2190
2190
|
var aiBadge = '';
|
|
2191
2191
|
if (si.state === 'fixing' && si.aiStatus === 'fixed') {
|
|
2192
2192
|
aiBadge = '<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>';
|
|
2193
2193
|
} else if (si.aiStatus === 'wont_fix') {
|
|
2194
2194
|
aiBadge = '<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>';
|
|
2195
|
+
} else if (si.state === 'regressed') {
|
|
2196
|
+
aiBadge = '<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>';
|
|
2195
2197
|
}
|
|
2196
2198
|
|
|
2199
|
+
var occBadge = si.occurrences > 1 ? ' <span class="sec-item-count">' + si.occurrences + 'x</span>' : '';
|
|
2200
|
+
|
|
2197
2201
|
card.innerHTML =
|
|
2198
2202
|
'<span class="ov-card-icon ' + iconCls + '">' + iconChar + '</span>' +
|
|
2199
2203
|
'<div class="ov-card-body">' +
|
|
2200
|
-
'<div class="ov-card-title">' + escHtml(
|
|
2201
|
-
'<div class="ov-card-desc">' +
|
|
2204
|
+
'<div class="ov-card-title">' + escHtml(issue.title) + occBadge + aiBadge + '</div>' +
|
|
2205
|
+
'<div class="ov-card-desc">' + issue.desc + '</div>' +
|
|
2202
2206
|
'<div class="ov-card-expand">' + expandHtml + '</div>' +
|
|
2203
2207
|
'</div>' +
|
|
2204
2208
|
'<span class="ov-card-arrow">\u2192</span>';
|
|
@@ -2244,14 +2248,14 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2244
2248
|
resolvedCards.className = 'ov-cards';
|
|
2245
2249
|
|
|
2246
2250
|
for (var ri = 0; ri < resolved.length; ri++) {
|
|
2247
|
-
var
|
|
2251
|
+
var rIssue = resolved[ri].issue;
|
|
2248
2252
|
var rCard = document.createElement('div');
|
|
2249
2253
|
rCard.className = 'ov-card ov-card-resolved';
|
|
2250
2254
|
rCard.innerHTML =
|
|
2251
2255
|
'<span class="ov-card-icon resolved">\u2713</span>' +
|
|
2252
2256
|
'<div class="ov-card-body">' +
|
|
2253
|
-
'<div class="ov-card-title" style="text-decoration:line-through;color:var(--text-muted)">' + escHtml(
|
|
2254
|
-
'<div class="ov-card-desc">' +
|
|
2257
|
+
'<div class="ov-card-title" style="text-decoration:line-through;color:var(--text-muted)">' + escHtml(rIssue.title) + '</div>' +
|
|
2258
|
+
'<div class="ov-card-desc">' + rIssue.desc + '</div>' +
|
|
2255
2259
|
'</div>';
|
|
2256
2260
|
resolvedCards.appendChild(rCard);
|
|
2257
2261
|
}
|
|
@@ -2267,30 +2271,12 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2267
2271
|
container.innerHTML = '';
|
|
2268
2272
|
var SEV = { critical: { icon: '\u2717', cls: 'critical', sort: 0 }, warning: { icon: '\u26A0', cls: 'warning', sort: 1 }, info: { icon: '\u2139', cls: 'info', sort: 2 } };
|
|
2269
2273
|
|
|
2270
|
-
var all = (state.
|
|
2271
|
-
var
|
|
2272
|
-
for (var ix = 0; ix < insightsList.length; ix++) {
|
|
2273
|
-
var si = insightsList[ix];
|
|
2274
|
-
all.push({
|
|
2275
|
-
findingId: si.key,
|
|
2276
|
-
state: si.state,
|
|
2277
|
-
aiStatus: si.aiStatus,
|
|
2278
|
-
aiNotes: si.aiNotes,
|
|
2279
|
-
finding: {
|
|
2280
|
-
severity: si.insight.severity,
|
|
2281
|
-
rule: 'insight-' + si.insight.type,
|
|
2282
|
-
title: si.insight.title,
|
|
2283
|
-
desc: si.insight.desc,
|
|
2284
|
-
hint: si.insight.hint,
|
|
2285
|
-
endpoint: si.insight.nav || 'global',
|
|
2286
|
-
count: 1
|
|
2287
|
-
}
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
2290
|
-
var open = all.filter(function(f) { return f.state === 'open' || f.state === 'fixing'; });
|
|
2274
|
+
var all = (state.issues || []).slice();
|
|
2275
|
+
var open = all.filter(function(f) { return f.state === 'open' || f.state === 'fixing' || f.state === 'regressed'; });
|
|
2291
2276
|
var resolved = all.filter(function(f) { return f.state === 'resolved'; });
|
|
2277
|
+
var stale = all.filter(function(f) { return f.state === 'stale'; });
|
|
2292
2278
|
|
|
2293
|
-
if (open.length === 0 && resolved.length === 0) {
|
|
2279
|
+
if (open.length === 0 && resolved.length === 0 && stale.length === 0) {
|
|
2294
2280
|
var hasData = state.requests.length > 0 || state.logs.length > 0 || state.queries.length > 0;
|
|
2295
2281
|
if (!hasData) {
|
|
2296
2282
|
container.innerHTML = '<div class="empty"><span class="empty-title">Waiting for requests...</span><span class="empty-sub">Start using your app to see security findings here</span></div>';
|
|
@@ -2302,7 +2288,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2302
2288
|
|
|
2303
2289
|
var critCount = 0, warnCount = 0, infoCount = 0;
|
|
2304
2290
|
for (var ci = 0; ci < open.length; ci++) {
|
|
2305
|
-
var sev = open[ci].
|
|
2291
|
+
var sev = open[ci].issue.severity;
|
|
2306
2292
|
if (sev === 'critical') critCount++;
|
|
2307
2293
|
else if (sev === 'info') infoCount++;
|
|
2308
2294
|
else warnCount++;
|
|
@@ -2335,7 +2321,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2335
2321
|
var groupOrder = [];
|
|
2336
2322
|
for (var gi = 0; gi < open.length; gi++) {
|
|
2337
2323
|
var sf = open[gi];
|
|
2338
|
-
var f = sf.
|
|
2324
|
+
var f = sf.issue;
|
|
2339
2325
|
if (!groups[f.rule]) {
|
|
2340
2326
|
groups[f.rule] = { rule: f.rule, title: f.title, severity: f.severity, hint: f.hint, items: [] };
|
|
2341
2327
|
groupOrder.push(f.rule);
|
|
@@ -2378,7 +2364,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2378
2364
|
list.className = 'sec-items';
|
|
2379
2365
|
for (var ii = 0; ii < group.items.length; ii++) {
|
|
2380
2366
|
var sf2 = group.items[ii];
|
|
2381
|
-
var item = sf2.
|
|
2367
|
+
var item = sf2.issue;
|
|
2382
2368
|
var row = document.createElement('div');
|
|
2383
2369
|
row.className = 'sec-item';
|
|
2384
2370
|
var aiBadge = '';
|
|
@@ -2386,11 +2372,14 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2386
2372
|
aiBadge = '<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>';
|
|
2387
2373
|
} else if (sf2.aiStatus === 'wont_fix') {
|
|
2388
2374
|
aiBadge = '<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>';
|
|
2375
|
+
} else if (sf2.state === 'regressed') {
|
|
2376
|
+
aiBadge = '<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>';
|
|
2389
2377
|
}
|
|
2390
2378
|
var aiNotes = sf2.aiNotes ? '<div class="sec-ai-notes">' + escHtml(sf2.aiNotes) + '</div>' : '';
|
|
2379
|
+
var occBadge = sf2.occurrences > 1 ? '<span class="sec-item-count">' + sf2.occurrences + 'x</span>' : '';
|
|
2391
2380
|
row.innerHTML =
|
|
2392
2381
|
'<div class="sec-item-desc">' + escHtml(item.desc) + '</div>' +
|
|
2393
|
-
|
|
2382
|
+
occBadge +
|
|
2394
2383
|
aiBadge + aiNotes;
|
|
2395
2384
|
list.appendChild(row);
|
|
2396
2385
|
}
|
|
@@ -2411,20 +2400,44 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2411
2400
|
resolvedItems.className = 'sec-items';
|
|
2412
2401
|
for (var ri = 0; ri < resolved.length; ri++) {
|
|
2413
2402
|
var rsf = resolved[ri];
|
|
2414
|
-
var rf = rsf.
|
|
2403
|
+
var rf = rsf.issue;
|
|
2415
2404
|
var rRow = document.createElement('div');
|
|
2416
2405
|
rRow.className = 'sec-item sec-item-resolved';
|
|
2417
2406
|
var verifiedBadge = rsf.aiStatus === 'fixed' ? '<span class="sec-ai-badge sec-ai-verified">Verified fix</span>' : '';
|
|
2418
2407
|
var rNotes = rsf.aiNotes ? '<div class="sec-ai-notes">' + escHtml(rsf.aiNotes) + '</div>' : '';
|
|
2419
2408
|
rRow.innerHTML =
|
|
2420
2409
|
'<span class="sec-resolved-item-icon">\u2713</span>' +
|
|
2421
|
-
'<div class="sec-item-desc">' + escHtml(rf.title) + ' \u2014 ' + escHtml(rf.endpoint) + '</div>' +
|
|
2410
|
+
'<div class="sec-item-desc">' + escHtml(rf.title) + ' \u2014 ' + escHtml(rf.endpoint || 'global') + '</div>' +
|
|
2422
2411
|
verifiedBadge + rNotes;
|
|
2423
2412
|
resolvedItems.appendChild(rRow);
|
|
2424
2413
|
}
|
|
2425
2414
|
resolvedGroup.appendChild(resolvedItems);
|
|
2426
2415
|
container.appendChild(resolvedGroup);
|
|
2427
2416
|
}
|
|
2417
|
+
|
|
2418
|
+
if (stale.length > 0) {
|
|
2419
|
+
var staleTitle = document.createElement('div');
|
|
2420
|
+
staleTitle.className = 'sec-resolved-title';
|
|
2421
|
+
staleTitle.innerHTML = '<span style="color:var(--text-muted)">\u23F8</span> Stale <span class="sec-resolved-count">' + stale.length + '</span>';
|
|
2422
|
+
container.appendChild(staleTitle);
|
|
2423
|
+
|
|
2424
|
+
var staleGroup = document.createElement('div');
|
|
2425
|
+
staleGroup.className = 'sec-group sec-group-resolved';
|
|
2426
|
+
var staleItems = document.createElement('div');
|
|
2427
|
+
staleItems.className = 'sec-items';
|
|
2428
|
+
for (var sti = 0; sti < stale.length; sti++) {
|
|
2429
|
+
var ssf = stale[sti];
|
|
2430
|
+
var sf3 = ssf.issue;
|
|
2431
|
+
var sRow = document.createElement('div');
|
|
2432
|
+
sRow.className = 'sec-item sec-item-resolved';
|
|
2433
|
+
sRow.innerHTML =
|
|
2434
|
+
'<span style="color:var(--text-muted)">\u23F8</span>' +
|
|
2435
|
+
'<div class="sec-item-desc" style="color:var(--text-muted)">' + escHtml(sf3.title) + ' \u2014 endpoint inactive</div>';
|
|
2436
|
+
staleItems.appendChild(sRow);
|
|
2437
|
+
}
|
|
2438
|
+
staleGroup.appendChild(staleItems);
|
|
2439
|
+
container.appendChild(staleGroup);
|
|
2440
|
+
}
|
|
2428
2441
|
}
|
|
2429
2442
|
|
|
2430
2443
|
|
|
@@ -2482,13 +2495,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2482
2495
|
try {
|
|
2483
2496
|
var res3 = await fetch('/__brakit/api/insights');
|
|
2484
2497
|
var data3 = await res3.json();
|
|
2485
|
-
state.
|
|
2486
|
-
} catch(e) { console.warn('[brakit]', e); }
|
|
2487
|
-
|
|
2488
|
-
try {
|
|
2489
|
-
var res4 = await fetch('/__brakit/api/security');
|
|
2490
|
-
var data4 = await res4.json();
|
|
2491
|
-
state.findings = data4.findings || [];
|
|
2498
|
+
state.issues = data3.issues || [];
|
|
2492
2499
|
} catch(e) { console.warn('[brakit]', e); }
|
|
2493
2500
|
|
|
2494
2501
|
updateStats();
|
|
@@ -2527,19 +2534,13 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2527
2534
|
registerTelemetryListener('error_event', 'errors', prependErrorRow);
|
|
2528
2535
|
registerTelemetryListener('query', 'queries', prependQueryRow);
|
|
2529
2536
|
|
|
2530
|
-
events.addEventListener('
|
|
2531
|
-
state.
|
|
2537
|
+
events.addEventListener('issues', function(e) {
|
|
2538
|
+
state.issues = JSON.parse(e.data);
|
|
2532
2539
|
if (state.activeView === 'overview') renderOverview();
|
|
2533
2540
|
if (state.activeView === 'security') renderSecurity();
|
|
2534
2541
|
updateStats();
|
|
2535
2542
|
});
|
|
2536
2543
|
|
|
2537
|
-
events.addEventListener('security', function(e) {
|
|
2538
|
-
state.findings = JSON.parse(e.data);
|
|
2539
|
-
if (state.activeView === 'security') renderSecurity();
|
|
2540
|
-
updateStats();
|
|
2541
|
-
});
|
|
2542
|
-
|
|
2543
2544
|
window.addEventListener('beforeunload', function() {
|
|
2544
2545
|
events.close();
|
|
2545
2546
|
clearTimeout(reloadTimer);
|
|
@@ -2618,9 +2619,9 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2618
2619
|
if (queryCount) queryCount.textContent = state.queries.length;
|
|
2619
2620
|
var secCount = document.getElementById('sidebar-count-security');
|
|
2620
2621
|
if (secCount) {
|
|
2621
|
-
var
|
|
2622
|
-
secCount.textContent =
|
|
2623
|
-
secCount.style.display =
|
|
2622
|
+
var numIssues = (state.issues || []).filter(function(f) { return f.state !== 'resolved' && f.state !== 'stale'; }).length;
|
|
2623
|
+
secCount.textContent = numIssues;
|
|
2624
|
+
secCount.style.display = numIssues > 0 ? '' : 'none';
|
|
2624
2625
|
}
|
|
2625
2626
|
}
|
|
2626
2627
|
|
|
@@ -2638,7 +2639,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
|
|
|
2638
2639
|
if (!confirm('This will clear all data including performance metrics history. Continue?')) return;
|
|
2639
2640
|
await fetch('/__brakit/api/clear', {method: 'POST'});
|
|
2640
2641
|
state.flows = []; state.requests = []; state.fetches = []; state.errors = []; state.logs = []; state.queries = [];
|
|
2641
|
-
state.
|
|
2642
|
+
state.issues = [];
|
|
2642
2643
|
graphData = []; selectedEndpoint = '__all__'; timelineCache = {};
|
|
2643
2644
|
renderFlows(); renderRequests(); renderFetches(); renderErrors(); renderLogs(); renderQueries(); renderGraph(); renderOverview(); renderSecurity(); updateStats();
|
|
2644
2645
|
showToast('Cleared');
|
package/dist/mcp/server.js
CHANGED
|
@@ -51,7 +51,7 @@ var MAX_DISCOVERY_DEPTH = 5;
|
|
|
51
51
|
var MAX_TIMELINE_EVENTS = 20;
|
|
52
52
|
var MAX_RESOLVED_DISPLAY = 5;
|
|
53
53
|
var ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
54
|
-
var MCP_SERVER_VERSION = "0.8.
|
|
54
|
+
var MCP_SERVER_VERSION = "0.8.6";
|
|
55
55
|
|
|
56
56
|
// src/mcp/client.ts
|
|
57
57
|
var BrakitClient = class {
|
|
@@ -67,11 +67,11 @@ var BrakitClient = class {
|
|
|
67
67
|
if (params?.offset) url.searchParams.set("offset", String(params.offset));
|
|
68
68
|
return this.fetchJson(url);
|
|
69
69
|
}
|
|
70
|
-
async
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return this.fetchJson(
|
|
70
|
+
async getIssues(params) {
|
|
71
|
+
const url = new URL(`${this.baseUrl}${DASHBOARD_API_INSIGHTS}`);
|
|
72
|
+
if (params?.state) url.searchParams.set("state", params.state);
|
|
73
|
+
if (params?.category) url.searchParams.set("category", params.category);
|
|
74
|
+
return this.fetchJson(url);
|
|
75
75
|
}
|
|
76
76
|
async getQueries(requestId) {
|
|
77
77
|
const url = new URL(`${this.baseUrl}${DASHBOARD_API_QUERIES}`);
|
|
@@ -145,7 +145,10 @@ import { readFile, readdir, stat } from "fs/promises";
|
|
|
145
145
|
import { resolve, dirname } from "path";
|
|
146
146
|
|
|
147
147
|
// src/constants/limits.ts
|
|
148
|
-
var
|
|
148
|
+
var ISSUE_PRUNE_TTL_MS = 10 * 60 * 1e3;
|
|
149
|
+
|
|
150
|
+
// src/constants/thresholds.ts
|
|
151
|
+
var STALE_ISSUE_TTL_MS = 30 * 60 * 1e3;
|
|
149
152
|
|
|
150
153
|
// src/constants/metrics.ts
|
|
151
154
|
var PORT_FILE = ".brakit/port";
|
|
@@ -156,7 +159,7 @@ var PORT_MIN = 1;
|
|
|
156
159
|
var PORT_MAX = 65535;
|
|
157
160
|
|
|
158
161
|
// src/constants/lifecycle.ts
|
|
159
|
-
var
|
|
162
|
+
var VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
160
163
|
var VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
161
164
|
var VALID_SECURITY_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning"]);
|
|
162
165
|
|
|
@@ -241,13 +244,6 @@ async function waitForBrakit(cwd, timeoutMs = 1e4, pollMs = DISCOVERY_POLL_INTER
|
|
|
241
244
|
);
|
|
242
245
|
}
|
|
243
246
|
|
|
244
|
-
// src/store/finding-id.ts
|
|
245
|
-
import { createHash } from "crypto";
|
|
246
|
-
function computeInsightId(type, endpoint, desc) {
|
|
247
|
-
const key = `${type}:${endpoint}:${desc}`;
|
|
248
|
-
return createHash("sha256").update(key).digest("hex").slice(0, FINDING_ID_HASH_LENGTH);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
247
|
// src/utils/endpoint.ts
|
|
252
248
|
function parseEndpointKey(endpoint) {
|
|
253
249
|
const spaceIdx = endpoint.indexOf(" ");
|
|
@@ -259,14 +255,16 @@ function parseEndpointKey(endpoint) {
|
|
|
259
255
|
|
|
260
256
|
// src/mcp/enrichment.ts
|
|
261
257
|
async function enrichFindings(client) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
const issuesData = await client.getIssues();
|
|
259
|
+
const issues = issuesData.issues.filter(
|
|
260
|
+
(si) => si.state !== "resolved" && si.state !== "stale"
|
|
261
|
+
);
|
|
266
262
|
const contexts = await Promise.all(
|
|
267
|
-
|
|
263
|
+
issues.map(async (si) => {
|
|
264
|
+
const endpoint = si.issue.endpoint;
|
|
265
|
+
if (!endpoint) return si.issue.detail ?? "";
|
|
268
266
|
try {
|
|
269
|
-
const { path } = parseEndpointKey(
|
|
267
|
+
const { path } = parseEndpointKey(endpoint);
|
|
270
268
|
const reqData = await client.getRequests({ search: path, limit: 1 });
|
|
271
269
|
if (reqData.requests.length > 0) {
|
|
272
270
|
const req = reqData.requests[0];
|
|
@@ -280,38 +278,22 @@ async function enrichFindings(client) {
|
|
|
280
278
|
} catch {
|
|
281
279
|
return "(context unavailable)";
|
|
282
280
|
}
|
|
283
|
-
return "";
|
|
281
|
+
return si.issue.detail ?? "";
|
|
284
282
|
})
|
|
285
283
|
);
|
|
286
|
-
const enriched =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
severity: f.severity,
|
|
291
|
-
title: f.title,
|
|
292
|
-
endpoint: f.endpoint,
|
|
293
|
-
description: f.desc,
|
|
294
|
-
hint: f.hint,
|
|
295
|
-
occurrences: f.count,
|
|
296
|
-
context: contexts[i],
|
|
297
|
-
aiStatus: sf.aiStatus,
|
|
298
|
-
aiNotes: sf.aiNotes
|
|
299
|
-
};
|
|
300
|
-
});
|
|
301
|
-
for (const si of insightsData.insights) {
|
|
302
|
-
if (si.state === "resolved") continue;
|
|
303
|
-
const i = si.insight;
|
|
304
|
-
if (!ENRICHMENT_SEVERITY_FILTER.includes(i.severity)) continue;
|
|
305
|
-
const endpoint = i.nav ?? "global";
|
|
284
|
+
const enriched = [];
|
|
285
|
+
for (let i = 0; i < issues.length; i++) {
|
|
286
|
+
const si = issues[i];
|
|
287
|
+
if (!ENRICHMENT_SEVERITY_FILTER.includes(si.issue.severity)) continue;
|
|
306
288
|
enriched.push({
|
|
307
|
-
findingId:
|
|
308
|
-
severity:
|
|
309
|
-
title:
|
|
310
|
-
endpoint,
|
|
311
|
-
description:
|
|
312
|
-
hint:
|
|
313
|
-
occurrences:
|
|
314
|
-
context: i
|
|
289
|
+
findingId: si.issueId,
|
|
290
|
+
severity: si.issue.severity,
|
|
291
|
+
title: si.issue.title,
|
|
292
|
+
endpoint: si.issue.endpoint ?? "global",
|
|
293
|
+
description: si.issue.desc,
|
|
294
|
+
hint: si.issue.hint,
|
|
295
|
+
occurrences: si.occurrences,
|
|
296
|
+
context: contexts[i],
|
|
315
297
|
aiStatus: si.aiStatus,
|
|
316
298
|
aiNotes: si.aiNotes
|
|
317
299
|
});
|
|
@@ -370,8 +352,8 @@ async function buildRequestDetail(client, req) {
|
|
|
370
352
|
function isNonEmptyString(val) {
|
|
371
353
|
return typeof val === "string" && val.trim().length > 0;
|
|
372
354
|
}
|
|
373
|
-
function
|
|
374
|
-
return typeof val === "string" &&
|
|
355
|
+
function isValidIssueState(val) {
|
|
356
|
+
return typeof val === "string" && VALID_ISSUE_STATES.has(val);
|
|
375
357
|
}
|
|
376
358
|
function isValidAiFixStatus(val) {
|
|
377
359
|
return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
|
|
@@ -391,8 +373,8 @@ var getFindings = {
|
|
|
391
373
|
},
|
|
392
374
|
state: {
|
|
393
375
|
type: "string",
|
|
394
|
-
enum: ["open", "fixing", "resolved"],
|
|
395
|
-
description: "Filter by
|
|
376
|
+
enum: ["open", "fixing", "resolved", "stale", "regressed"],
|
|
377
|
+
description: "Filter by issue state"
|
|
396
378
|
}
|
|
397
379
|
}
|
|
398
380
|
},
|
|
@@ -402,17 +384,17 @@ var getFindings = {
|
|
|
402
384
|
if (severity && !VALID_SECURITY_SEVERITIES.has(severity)) {
|
|
403
385
|
return { content: [{ type: "text", text: `Invalid severity "${severity}". Use: critical, warning.` }], isError: true };
|
|
404
386
|
}
|
|
405
|
-
if (state && !
|
|
406
|
-
return { content: [{ type: "text", text: `Invalid state "${state}". Use: open, fixing, resolved.` }], isError: true };
|
|
387
|
+
if (state && !isValidIssueState(state)) {
|
|
388
|
+
return { content: [{ type: "text", text: `Invalid state "${state}". Use: open, fixing, resolved, stale, regressed.` }], isError: true };
|
|
407
389
|
}
|
|
408
390
|
let findings = await enrichFindings(client);
|
|
409
391
|
if (severity) {
|
|
410
392
|
findings = findings.filter((f) => f.severity === severity);
|
|
411
393
|
}
|
|
412
394
|
if (state) {
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
findings = findings.filter((f) =>
|
|
395
|
+
const issuesData = await client.getIssues({ state });
|
|
396
|
+
const issueIds = new Set(issuesData.issues.map((i) => i.issueId));
|
|
397
|
+
findings = findings.filter((f) => issueIds.has(f.findingId));
|
|
416
398
|
}
|
|
417
399
|
if (findings.length === 0) {
|
|
418
400
|
return { content: [{ type: "text", text: "No findings detected. The application looks healthy." }] };
|
|
@@ -570,20 +552,21 @@ var verifyFix = {
|
|
|
570
552
|
}
|
|
571
553
|
if (findingId) {
|
|
572
554
|
const data = await client.getFindings();
|
|
573
|
-
const finding = data.findings.find((f) => f.
|
|
555
|
+
const finding = data.findings.find((f) => f.issueId === findingId);
|
|
574
556
|
if (!finding) {
|
|
575
557
|
return {
|
|
576
558
|
content: [{
|
|
577
559
|
type: "text",
|
|
578
560
|
text: `Finding ${findingId} not found. It may have already been resolved and cleaned up.`
|
|
579
|
-
}]
|
|
561
|
+
}],
|
|
562
|
+
isError: true
|
|
580
563
|
};
|
|
581
564
|
}
|
|
582
565
|
if (finding.state === "resolved") {
|
|
583
566
|
return {
|
|
584
567
|
content: [{
|
|
585
568
|
type: "text",
|
|
586
|
-
text: `RESOLVED: "${finding.
|
|
569
|
+
text: `RESOLVED: "${finding.issue.title}" on ${finding.issue.endpoint ?? "global"} is no longer detected. The fix worked.`
|
|
587
570
|
}]
|
|
588
571
|
};
|
|
589
572
|
}
|
|
@@ -591,12 +574,12 @@ var verifyFix = {
|
|
|
591
574
|
content: [{
|
|
592
575
|
type: "text",
|
|
593
576
|
text: [
|
|
594
|
-
`STILL PRESENT: "${finding.
|
|
577
|
+
`STILL PRESENT: "${finding.issue.title}" on ${finding.issue.endpoint ?? "global"}`,
|
|
595
578
|
` State: ${finding.state}`,
|
|
596
579
|
` Last seen: ${new Date(finding.lastSeenAt).toISOString()}`,
|
|
597
580
|
` Occurrences: ${finding.occurrences}`,
|
|
598
|
-
` Issue: ${finding.
|
|
599
|
-
` Hint: ${finding.
|
|
581
|
+
` Issue: ${finding.issue.desc}`,
|
|
582
|
+
` Hint: ${finding.issue.hint}`,
|
|
600
583
|
"",
|
|
601
584
|
"Make sure the user has triggered the endpoint again after the fix, so Brakit can re-analyze."
|
|
602
585
|
].join("\n")
|
|
@@ -606,7 +589,7 @@ var verifyFix = {
|
|
|
606
589
|
if (endpoint) {
|
|
607
590
|
const data = await client.getFindings();
|
|
608
591
|
const endpointFindings = data.findings.filter(
|
|
609
|
-
(f) => f.
|
|
592
|
+
(f) => f.issue.endpoint === endpoint || f.issue.endpoint && f.issue.endpoint.endsWith(` ${endpoint}`)
|
|
610
593
|
);
|
|
611
594
|
if (endpointFindings.length === 0) {
|
|
612
595
|
return {
|
|
@@ -616,7 +599,7 @@ var verifyFix = {
|
|
|
616
599
|
}]
|
|
617
600
|
};
|
|
618
601
|
}
|
|
619
|
-
const open = endpointFindings.filter((f) => f.state === "open");
|
|
602
|
+
const open = endpointFindings.filter((f) => f.state === "open" || f.state === "regressed");
|
|
620
603
|
const resolved = endpointFindings.filter((f) => f.state === "resolved");
|
|
621
604
|
const lines = [
|
|
622
605
|
`Endpoint: ${endpoint}`,
|
|
@@ -625,10 +608,10 @@ var verifyFix = {
|
|
|
625
608
|
""
|
|
626
609
|
];
|
|
627
610
|
for (const f of open) {
|
|
628
|
-
lines.push(` [${f.
|
|
611
|
+
lines.push(` [${f.issue.severity}] ${f.issue.title}: ${f.issue.desc}`);
|
|
629
612
|
}
|
|
630
613
|
for (const f of resolved) {
|
|
631
|
-
lines.push(` [resolved] ${f.
|
|
614
|
+
lines.push(` [resolved] ${f.issue.title}`);
|
|
632
615
|
}
|
|
633
616
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
634
617
|
}
|
|
@@ -636,7 +619,8 @@ var verifyFix = {
|
|
|
636
619
|
content: [{
|
|
637
620
|
type: "text",
|
|
638
621
|
text: "Please provide either a finding_id or an endpoint to verify."
|
|
639
|
-
}]
|
|
622
|
+
}],
|
|
623
|
+
isError: true
|
|
640
624
|
};
|
|
641
625
|
}
|
|
642
626
|
};
|
|
@@ -650,51 +634,52 @@ var getReport = {
|
|
|
650
634
|
properties: {}
|
|
651
635
|
},
|
|
652
636
|
async handler(client, _args) {
|
|
653
|
-
const [
|
|
654
|
-
client.
|
|
655
|
-
client.getSecurityFindings(),
|
|
656
|
-
client.getInsights(),
|
|
637
|
+
const [issuesData, metricsData] = await Promise.all([
|
|
638
|
+
client.getIssues(),
|
|
657
639
|
client.getLiveMetrics()
|
|
658
640
|
]);
|
|
659
|
-
const
|
|
660
|
-
const open =
|
|
661
|
-
const resolved =
|
|
662
|
-
const fixing =
|
|
663
|
-
const
|
|
664
|
-
const
|
|
641
|
+
const issues = issuesData.issues;
|
|
642
|
+
const open = issues.filter((f) => f.state === "open" || f.state === "regressed");
|
|
643
|
+
const resolved = issues.filter((f) => f.state === "resolved");
|
|
644
|
+
const fixing = issues.filter((f) => f.state === "fixing");
|
|
645
|
+
const stale = issues.filter((f) => f.state === "stale");
|
|
646
|
+
const criticalOpen = open.filter((f) => f.issue.severity === "critical");
|
|
647
|
+
const warningOpen = open.filter((f) => f.issue.severity === "warning");
|
|
648
|
+
const securityIssues = issues.filter((f) => f.category === "security");
|
|
649
|
+
const perfIssues = issues.filter((f) => f.category === "performance");
|
|
665
650
|
const totalRequests = metricsData.endpoints.reduce(
|
|
666
651
|
(s, ep) => s + ep.summary.totalRequests,
|
|
667
652
|
0
|
|
668
653
|
);
|
|
669
|
-
const openInsightCount = insightsData.insights.filter((si) => si.state === "open").length;
|
|
670
654
|
const lines = [
|
|
671
655
|
"=== Brakit Report ===",
|
|
672
656
|
"",
|
|
673
657
|
`Endpoints observed: ${metricsData.endpoints.length}`,
|
|
674
658
|
`Total requests captured: ${totalRequests}`,
|
|
675
|
-
`
|
|
676
|
-
`Performance
|
|
659
|
+
`Security issues: ${securityIssues.length}`,
|
|
660
|
+
`Performance issues: ${perfIssues.length}`,
|
|
677
661
|
"",
|
|
678
|
-
"---
|
|
679
|
-
`Total: ${
|
|
662
|
+
"--- Issue Summary ---",
|
|
663
|
+
`Total: ${issues.length}`,
|
|
680
664
|
` Open: ${open.length} (${criticalOpen.length} critical, ${warningOpen.length} warning)`,
|
|
681
665
|
` In progress: ${fixing.length}`,
|
|
682
|
-
` Resolved: ${resolved.length}
|
|
666
|
+
` Resolved: ${resolved.length}`,
|
|
667
|
+
` Stale: ${stale.length}`
|
|
683
668
|
];
|
|
684
669
|
if (criticalOpen.length > 0) {
|
|
685
670
|
lines.push("");
|
|
686
671
|
lines.push("--- Critical Issues (fix first) ---");
|
|
687
672
|
for (const f of criticalOpen) {
|
|
688
|
-
lines.push(` [CRITICAL] ${f.
|
|
689
|
-
lines.push(` ${f.
|
|
690
|
-
lines.push(` Fix: ${f.
|
|
673
|
+
lines.push(` [CRITICAL] ${f.issue.title} \u2014 ${f.issue.endpoint ?? "global"}`);
|
|
674
|
+
lines.push(` ${f.issue.desc}`);
|
|
675
|
+
lines.push(` Fix: ${f.issue.hint}`);
|
|
691
676
|
}
|
|
692
677
|
}
|
|
693
678
|
if (resolved.length > 0) {
|
|
694
679
|
lines.push("");
|
|
695
680
|
lines.push("--- Recently Resolved ---");
|
|
696
681
|
for (const f of resolved.slice(0, MAX_RESOLVED_DISPLAY)) {
|
|
697
|
-
lines.push(` \u2713 ${f.
|
|
682
|
+
lines.push(` \u2713 ${f.issue.title} \u2014 ${f.issue.endpoint ?? "global"}`);
|
|
698
683
|
}
|
|
699
684
|
if (resolved.length > MAX_RESOLVED_DISPLAY) {
|
|
700
685
|
lines.push(` ... and ${resolved.length - MAX_RESOLVED_DISPLAY} more`);
|