brakit 0.8.0 → 0.8.1
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 +12 -1
- package/dist/api.d.ts +68 -44
- package/dist/api.js +99 -3
- package/dist/bin/brakit.js +14 -5
- package/dist/mcp/server.js +9 -3
- package/dist/runtime/index.js +412 -162
- package/package.json +1 -1
package/dist/runtime/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var init_routes = __esm({
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
// src/constants/limits.ts
|
|
37
|
-
var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH;
|
|
37
|
+
var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH, MAX_INGEST_BYTES, TERMINAL_TRUNCATE_LENGTH, SENSITIVE_MASK_MIN_LENGTH, SENSITIVE_MASK_VISIBLE_CHARS;
|
|
38
38
|
var init_limits = __esm({
|
|
39
39
|
"src/constants/limits.ts"() {
|
|
40
40
|
"use strict";
|
|
@@ -43,11 +43,15 @@ var init_limits = __esm({
|
|
|
43
43
|
DEFAULT_API_LIMIT = 500;
|
|
44
44
|
MAX_TELEMETRY_ENTRIES = 1e3;
|
|
45
45
|
MAX_TAB_NAME_LENGTH = 32;
|
|
46
|
+
MAX_INGEST_BYTES = 10 * 1024 * 1024;
|
|
47
|
+
TERMINAL_TRUNCATE_LENGTH = 80;
|
|
48
|
+
SENSITIVE_MASK_MIN_LENGTH = 8;
|
|
49
|
+
SENSITIVE_MASK_VISIBLE_CHARS = 4;
|
|
46
50
|
}
|
|
47
51
|
});
|
|
48
52
|
|
|
49
53
|
// src/constants/thresholds.ts
|
|
50
|
-
var FLOW_GAP_MS, SLOW_REQUEST_THRESHOLD_MS, MIN_POLLING_SEQUENCE, ENDPOINT_TRUNCATE_LENGTH, N1_QUERY_THRESHOLD, ERROR_RATE_THRESHOLD_PCT, SLOW_ENDPOINT_THRESHOLD_MS, MIN_REQUESTS_FOR_INSIGHT, HIGH_QUERY_COUNT_PER_REQ, CROSS_ENDPOINT_MIN_ENDPOINTS, CROSS_ENDPOINT_PCT, CROSS_ENDPOINT_MIN_OCCURRENCES, REDUNDANT_QUERY_MIN_COUNT, LARGE_RESPONSE_BYTES, HIGH_ROW_COUNT, OVERFETCH_MIN_REQUESTS, OVERFETCH_MIN_FIELDS, OVERFETCH_MIN_INTERNAL_IDS, OVERFETCH_NULL_RATIO, REGRESSION_PCT_THRESHOLD, REGRESSION_MIN_INCREASE_MS, REGRESSION_MIN_REQUESTS, QUERY_COUNT_REGRESSION_RATIO, OVERFETCH_MANY_FIELDS, OVERFETCH_UNWRAP_MIN_SIZE, MAX_DUPLICATE_INSIGHTS;
|
|
54
|
+
var FLOW_GAP_MS, SLOW_REQUEST_THRESHOLD_MS, MIN_POLLING_SEQUENCE, ENDPOINT_TRUNCATE_LENGTH, N1_QUERY_THRESHOLD, ERROR_RATE_THRESHOLD_PCT, SLOW_ENDPOINT_THRESHOLD_MS, MIN_REQUESTS_FOR_INSIGHT, HIGH_QUERY_COUNT_PER_REQ, CROSS_ENDPOINT_MIN_ENDPOINTS, CROSS_ENDPOINT_PCT, CROSS_ENDPOINT_MIN_OCCURRENCES, REDUNDANT_QUERY_MIN_COUNT, LARGE_RESPONSE_BYTES, HIGH_ROW_COUNT, OVERFETCH_MIN_REQUESTS, OVERFETCH_MIN_FIELDS, OVERFETCH_MIN_INTERNAL_IDS, OVERFETCH_NULL_RATIO, REGRESSION_PCT_THRESHOLD, REGRESSION_MIN_INCREASE_MS, REGRESSION_MIN_REQUESTS, QUERY_COUNT_REGRESSION_RATIO, OVERFETCH_MANY_FIELDS, OVERFETCH_UNWRAP_MIN_SIZE, MAX_DUPLICATE_INSIGHTS, INSIGHT_WINDOW_PER_ENDPOINT, RESOLVE_AFTER_ABSENCES, RESOLVED_INSIGHT_TTL_MS;
|
|
51
55
|
var init_thresholds = __esm({
|
|
52
56
|
"src/constants/thresholds.ts"() {
|
|
53
57
|
"use strict";
|
|
@@ -77,6 +81,9 @@ var init_thresholds = __esm({
|
|
|
77
81
|
OVERFETCH_MANY_FIELDS = 12;
|
|
78
82
|
OVERFETCH_UNWRAP_MIN_SIZE = 3;
|
|
79
83
|
MAX_DUPLICATE_INSIGHTS = 3;
|
|
84
|
+
INSIGHT_WINDOW_PER_ENDPOINT = 2;
|
|
85
|
+
RESOLVE_AFTER_ABSENCES = 3;
|
|
86
|
+
RESOLVED_INSIGHT_TTL_MS = 18e5;
|
|
80
87
|
}
|
|
81
88
|
});
|
|
82
89
|
|
|
@@ -110,9 +117,18 @@ var init_metrics = __esm({
|
|
|
110
117
|
});
|
|
111
118
|
|
|
112
119
|
// src/constants/headers.ts
|
|
120
|
+
var SENSITIVE_HEADER_NAMES;
|
|
113
121
|
var init_headers = __esm({
|
|
114
122
|
"src/constants/headers.ts"() {
|
|
115
123
|
"use strict";
|
|
124
|
+
SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
125
|
+
"authorization",
|
|
126
|
+
"cookie",
|
|
127
|
+
"set-cookie",
|
|
128
|
+
"proxy-authorization",
|
|
129
|
+
"x-api-key",
|
|
130
|
+
"x-auth-token"
|
|
131
|
+
]);
|
|
116
132
|
}
|
|
117
133
|
});
|
|
118
134
|
|
|
@@ -1321,9 +1337,14 @@ var init_math = __esm({
|
|
|
1321
1337
|
function getEndpointKey(method, path) {
|
|
1322
1338
|
return `${method} ${path}`;
|
|
1323
1339
|
}
|
|
1340
|
+
function extractEndpointFromDesc(desc) {
|
|
1341
|
+
return desc.match(ENDPOINT_PREFIX_RE)?.[1] ?? null;
|
|
1342
|
+
}
|
|
1343
|
+
var ENDPOINT_PREFIX_RE;
|
|
1324
1344
|
var init_endpoint = __esm({
|
|
1325
1345
|
"src/utils/endpoint.ts"() {
|
|
1326
1346
|
"use strict";
|
|
1347
|
+
ENDPOINT_PREFIX_RE = /^(\S+\s+\S+)/;
|
|
1327
1348
|
}
|
|
1328
1349
|
});
|
|
1329
1350
|
|
|
@@ -1666,7 +1687,7 @@ function maskSensitiveHeaders(headers) {
|
|
|
1666
1687
|
for (const [key, value] of Object.entries(headers)) {
|
|
1667
1688
|
if (SENSITIVE_HEADER_NAMES.has(key.toLowerCase())) {
|
|
1668
1689
|
const s = String(value);
|
|
1669
|
-
masked[key] = s.length <=
|
|
1690
|
+
masked[key] = s.length <= SENSITIVE_MASK_MIN_LENGTH ? "****" : s.slice(0, SENSITIVE_MASK_VISIBLE_CHARS) + "..." + s.slice(-SENSITIVE_MASK_VISIBLE_CHARS);
|
|
1670
1691
|
} else {
|
|
1671
1692
|
masked[key] = value;
|
|
1672
1693
|
}
|
|
@@ -1713,19 +1734,11 @@ function handleTelemetryGet(req, res, store) {
|
|
|
1713
1734
|
const entries = requestId ? store.getByRequest(requestId) : [...store.getAll()];
|
|
1714
1735
|
sendJson(req, res, 200, { total: entries.length, entries: entries.reverse() });
|
|
1715
1736
|
}
|
|
1716
|
-
var SENSITIVE_HEADER_NAMES;
|
|
1717
1737
|
var init_shared2 = __esm({
|
|
1718
1738
|
"src/dashboard/api/shared.ts"() {
|
|
1719
1739
|
"use strict";
|
|
1720
1740
|
init_constants();
|
|
1721
|
-
|
|
1722
|
-
"authorization",
|
|
1723
|
-
"cookie",
|
|
1724
|
-
"set-cookie",
|
|
1725
|
-
"proxy-authorization",
|
|
1726
|
-
"x-api-key",
|
|
1727
|
-
"x-auth-token"
|
|
1728
|
-
]);
|
|
1741
|
+
init_limits();
|
|
1729
1742
|
}
|
|
1730
1743
|
});
|
|
1731
1744
|
|
|
@@ -1782,7 +1795,7 @@ function handleApiFlows(req, res) {
|
|
|
1782
1795
|
}));
|
|
1783
1796
|
sendJson(req, res, 200, { total: flows.length, flows });
|
|
1784
1797
|
}
|
|
1785
|
-
function createClearHandler(metricsStore) {
|
|
1798
|
+
function createClearHandler(metricsStore, findingStore) {
|
|
1786
1799
|
return (req, res) => {
|
|
1787
1800
|
if (req.method !== "POST") {
|
|
1788
1801
|
sendJson(req, res, 405, { error: "Method not allowed" });
|
|
@@ -1794,6 +1807,7 @@ function createClearHandler(metricsStore) {
|
|
|
1794
1807
|
defaultErrorStore.clear();
|
|
1795
1808
|
defaultQueryStore.clear();
|
|
1796
1809
|
metricsStore.reset();
|
|
1810
|
+
findingStore?.clear();
|
|
1797
1811
|
sendJson(req, res, 200, { cleared: true });
|
|
1798
1812
|
};
|
|
1799
1813
|
}
|
|
@@ -1904,7 +1918,6 @@ function handleApiIngest(req, res) {
|
|
|
1904
1918
|
sendJson(req, res, 405, { error: "Method not allowed" });
|
|
1905
1919
|
return;
|
|
1906
1920
|
}
|
|
1907
|
-
const MAX_INGEST_BYTES = 10 * 1024 * 1024;
|
|
1908
1921
|
const chunks = [];
|
|
1909
1922
|
let totalSize = 0;
|
|
1910
1923
|
req.on("data", (chunk) => {
|
|
@@ -1946,6 +1959,7 @@ var init_ingest = __esm({
|
|
|
1946
1959
|
"src/dashboard/api/ingest.ts"() {
|
|
1947
1960
|
"use strict";
|
|
1948
1961
|
init_store();
|
|
1962
|
+
init_limits();
|
|
1949
1963
|
init_shared2();
|
|
1950
1964
|
}
|
|
1951
1965
|
});
|
|
@@ -2051,13 +2065,13 @@ var init_api = __esm({
|
|
|
2051
2065
|
function createInsightsHandler(engine) {
|
|
2052
2066
|
return (req, res) => {
|
|
2053
2067
|
if (!requireGet(req, res)) return;
|
|
2054
|
-
sendJson(req, res, 200, { insights: engine.
|
|
2068
|
+
sendJson(req, res, 200, { insights: engine.getStatefulInsights() });
|
|
2055
2069
|
};
|
|
2056
2070
|
}
|
|
2057
2071
|
function createSecurityHandler(engine) {
|
|
2058
2072
|
return (req, res) => {
|
|
2059
2073
|
if (!requireGet(req, res)) return;
|
|
2060
|
-
sendJson(req, res, 200, { findings: engine.
|
|
2074
|
+
sendJson(req, res, 200, { findings: engine.getStatefulFindings() });
|
|
2061
2075
|
};
|
|
2062
2076
|
}
|
|
2063
2077
|
var init_insights = __esm({
|
|
@@ -2132,9 +2146,9 @@ data: ${data}
|
|
|
2132
2146
|
const queryListener = (entry) => {
|
|
2133
2147
|
writeEvent("query", JSON.stringify(entry));
|
|
2134
2148
|
};
|
|
2135
|
-
const analysisListener = engine ? (
|
|
2136
|
-
writeEvent("insights", JSON.stringify(
|
|
2137
|
-
writeEvent("security", JSON.stringify(
|
|
2149
|
+
const analysisListener = engine ? ({ statefulInsights, statefulFindings }) => {
|
|
2150
|
+
writeEvent("insights", JSON.stringify(statefulInsights));
|
|
2151
|
+
writeEvent("security", JSON.stringify(statefulFindings));
|
|
2138
2152
|
} : void 0;
|
|
2139
2153
|
onRequest(requestListener);
|
|
2140
2154
|
defaultFetchStore.onEntry(fetchListener);
|
|
@@ -2184,6 +2198,7 @@ function getBaseStyles() {
|
|
|
2184
2198
|
--amber:#d97706;
|
|
2185
2199
|
--red:#dc2626;
|
|
2186
2200
|
--cyan:#0891b2;
|
|
2201
|
+
--green-bg:rgba(22,163,74,0.08);--green-bg-subtle:rgba(22,163,74,0.05);--green-border:rgba(22,163,74,0.2);--green-border-subtle:rgba(22,163,74,0.15);
|
|
2187
2202
|
--sidebar-width:232px;--header-height:52px;
|
|
2188
2203
|
--radius:8px;--radius-sm:6px;
|
|
2189
2204
|
--shadow-sm:0 1px 2px rgba(0,0,0,0.05);
|
|
@@ -2300,7 +2315,7 @@ function getFlowStyles() {
|
|
|
2300
2315
|
.flow-label{font-weight:500;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)}
|
|
2301
2316
|
.flow-req-count{font-family:var(--mono);font-size:12px;color:var(--text-muted);flex-shrink:0;text-align:right}
|
|
2302
2317
|
.flow-badge-pill{font-size:11px;flex-shrink:0;font-family:var(--mono);font-weight:600;padding:2px 10px;border-radius:10px;text-align:center}
|
|
2303
|
-
.flow-badge-pill.badge-clean{background:
|
|
2318
|
+
.flow-badge-pill.badge-clean{background:var(--green-bg);color:var(--green)}
|
|
2304
2319
|
.flow-badge-pill.badge-warn{background:rgba(217,119,6,0.07);color:var(--amber)}
|
|
2305
2320
|
.flow-badge-pill.badge-error{background:rgba(220,38,38,0.07);color:var(--red)}
|
|
2306
2321
|
.flow-duration{font-family:var(--mono);font-size:12px;color:var(--text-muted);flex-shrink:0;width:60px;text-align:right}
|
|
@@ -2320,7 +2335,7 @@ function getFlowStyles() {
|
|
|
2320
2335
|
|
|
2321
2336
|
/* Method badges */
|
|
2322
2337
|
.method-badge{display:inline-flex;align-items:center;justify-content:center;padding:3px 8px;border-radius:5px;font-size:10px;font-weight:700;font-family:var(--mono);letter-spacing:.3px;flex-shrink:0}
|
|
2323
|
-
.method-badge-GET{background:
|
|
2338
|
+
.method-badge-GET{background:var(--green-bg);color:var(--green)}
|
|
2324
2339
|
.method-badge-POST{background:rgba(37,99,235,0.08);color:var(--blue)}
|
|
2325
2340
|
.method-badge-PUT,.method-badge-PATCH{background:rgba(217,119,6,0.08);color:var(--amber)}
|
|
2326
2341
|
.method-badge-DELETE{background:rgba(220,38,38,0.08);color:var(--red)}
|
|
@@ -2328,7 +2343,7 @@ function getFlowStyles() {
|
|
|
2328
2343
|
|
|
2329
2344
|
/* Status pills */
|
|
2330
2345
|
.status-pill{display:inline-flex;align-items:center;padding:1px 7px;border-radius:4px;font-size:11px;font-weight:600;font-family:var(--mono);flex-shrink:0}
|
|
2331
|
-
.status-pill-2xx{background:
|
|
2346
|
+
.status-pill-2xx{background:var(--green-bg);color:var(--green)}
|
|
2332
2347
|
.status-pill-3xx{background:rgba(8,145,178,0.07);color:var(--cyan)}
|
|
2333
2348
|
.status-pill-4xx{background:rgba(217,119,6,0.07);color:var(--amber)}
|
|
2334
2349
|
.status-pill-5xx{background:rgba(220,38,38,0.07);color:var(--red)}
|
|
@@ -2595,6 +2610,7 @@ function getOverviewStyles() {
|
|
|
2595
2610
|
.ov-card-icon.critical{background:rgba(220,38,38,.08);color:var(--red)}
|
|
2596
2611
|
.ov-card-icon.warning{background:rgba(217,119,6,.08);color:var(--amber)}
|
|
2597
2612
|
.ov-card-icon.info{background:rgba(37,99,235,.08);color:var(--blue)}
|
|
2613
|
+
.ov-card-icon.resolved{background:var(--green-bg);color:var(--green)}
|
|
2598
2614
|
.ov-card-body{flex:1;min-width:0}
|
|
2599
2615
|
.ov-card-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:2px}
|
|
2600
2616
|
.ov-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
|
|
@@ -2611,8 +2627,13 @@ function getOverviewStyles() {
|
|
|
2611
2627
|
.ov-detail-item{font-size:12px;color:var(--text);font-family:var(--mono);padding:2px 0}
|
|
2612
2628
|
|
|
2613
2629
|
/* All-clear banner */
|
|
2614
|
-
.ov-clear{display:flex;align-items:center;gap:12px;padding:16px 20px;background:
|
|
2630
|
+
.ov-clear{display:flex;align-items:center;gap:12px;padding:16px 20px;background:var(--green-bg-subtle);border:1px solid var(--green-border);border-radius:var(--radius);color:var(--green);font-size:13px;font-weight:500}
|
|
2615
2631
|
.ov-clear-icon{font-size:16px}
|
|
2632
|
+
|
|
2633
|
+
/* Resolved section */
|
|
2634
|
+
.ov-resolved-title{margin-top:24px}
|
|
2635
|
+
.ov-card-resolved{opacity:.7;border-color:var(--green-border);cursor:default}
|
|
2636
|
+
.ov-card-resolved:hover{opacity:1;box-shadow:var(--shadow-sm)}
|
|
2616
2637
|
`;
|
|
2617
2638
|
}
|
|
2618
2639
|
var init_overview = __esm({
|
|
@@ -2628,7 +2649,7 @@ function getSecurityStyles() {
|
|
|
2628
2649
|
.sec-container{padding:24px 28px}
|
|
2629
2650
|
|
|
2630
2651
|
/* All-clear */
|
|
2631
|
-
.sec-clear{display:flex;align-items:center;gap:16px;padding:20px 24px;background:
|
|
2652
|
+
.sec-clear{display:flex;align-items:center;gap:16px;padding:20px 24px;background:var(--green-bg-subtle);border:1px solid var(--green-border-subtle);border-radius:var(--radius);margin-bottom:24px}
|
|
2632
2653
|
.sec-clear-icon{font-size:24px;color:var(--green);flex-shrink:0}
|
|
2633
2654
|
.sec-clear-title{font-size:15px;font-weight:600;color:var(--green);margin-bottom:2px}
|
|
2634
2655
|
.sec-clear-sub{font-size:12px;color:var(--text-dim)}
|
|
@@ -2664,6 +2685,19 @@ function getSecurityStyles() {
|
|
|
2664
2685
|
.sec-item-desc{color:var(--text-dim);line-height:1.5;flex:1;min-width:0}
|
|
2665
2686
|
.sec-item-desc strong{color:var(--text);font-family:var(--mono);font-weight:600}
|
|
2666
2687
|
.sec-item-count{font-size:10px;font-family:var(--mono);color:var(--text-muted);flex-shrink:0;margin-left:12px}
|
|
2688
|
+
|
|
2689
|
+
/* Resolved badge in summary */
|
|
2690
|
+
.sec-resolved-badge{font-size:11px;font-weight:600;padding:3px 10px;border-radius:10px;background:var(--green-bg);color:var(--green);margin-left:12px}
|
|
2691
|
+
|
|
2692
|
+
/* Resolved section */
|
|
2693
|
+
.sec-resolved-title{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;color:var(--text-dim);margin:20px 0 8px 0}
|
|
2694
|
+
.sec-resolved-check{color:var(--green);font-size:14px}
|
|
2695
|
+
.sec-resolved-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 8px;border-radius:10px;border:1px solid var(--border)}
|
|
2696
|
+
.sec-group-resolved{opacity:.7;border-color:var(--green-border)}
|
|
2697
|
+
.sec-group-resolved:hover{opacity:1}
|
|
2698
|
+
.sec-item-resolved{color:var(--text-muted)}
|
|
2699
|
+
.sec-item-resolved .sec-item-desc{text-decoration:line-through;text-decoration-color:var(--text-muted)}
|
|
2700
|
+
.sec-resolved-item-icon{color:var(--green);font-size:12px;flex-shrink:0;margin-right:8px}
|
|
2667
2701
|
`;
|
|
2668
2702
|
}
|
|
2669
2703
|
var init_security = __esm({
|
|
@@ -3589,6 +3623,23 @@ function createEndpointGroup() {
|
|
|
3589
3623
|
queryShapeDurations: /* @__PURE__ */ new Map()
|
|
3590
3624
|
};
|
|
3591
3625
|
}
|
|
3626
|
+
function windowByEndpoint(requests) {
|
|
3627
|
+
const byEndpoint = /* @__PURE__ */ new Map();
|
|
3628
|
+
for (const r of requests) {
|
|
3629
|
+
const ep = getEndpointKey(r.method, r.path);
|
|
3630
|
+
let list = byEndpoint.get(ep);
|
|
3631
|
+
if (!list) {
|
|
3632
|
+
list = [];
|
|
3633
|
+
byEndpoint.set(ep, list);
|
|
3634
|
+
}
|
|
3635
|
+
list.push(r);
|
|
3636
|
+
}
|
|
3637
|
+
const windowed = [];
|
|
3638
|
+
for (const [, reqs] of byEndpoint) {
|
|
3639
|
+
windowed.push(...reqs.slice(-INSIGHT_WINDOW_PER_ENDPOINT));
|
|
3640
|
+
}
|
|
3641
|
+
return windowed;
|
|
3642
|
+
}
|
|
3592
3643
|
function prepareContext(ctx) {
|
|
3593
3644
|
const nonStatic = ctx.requests.filter(
|
|
3594
3645
|
(r) => !r.isStatic && (!r.path || !r.path.startsWith(DASHBOARD_PREFIX))
|
|
@@ -3596,8 +3647,9 @@ function prepareContext(ctx) {
|
|
|
3596
3647
|
const queriesByReq = groupBy(ctx.queries, (q) => q.parentRequestId);
|
|
3597
3648
|
const fetchesByReq = groupBy(ctx.fetches, (f) => f.parentRequestId);
|
|
3598
3649
|
const reqById = new Map(nonStatic.map((r) => [r.id, r]));
|
|
3650
|
+
const recent = windowByEndpoint(nonStatic);
|
|
3599
3651
|
const endpointGroups = /* @__PURE__ */ new Map();
|
|
3600
|
-
for (const r of
|
|
3652
|
+
for (const r of recent) {
|
|
3601
3653
|
const ep = getEndpointKey(r.method, r.path);
|
|
3602
3654
|
let g = endpointGroups.get(ep);
|
|
3603
3655
|
if (!g) {
|
|
@@ -3642,6 +3694,7 @@ var init_prepare = __esm({
|
|
|
3642
3694
|
init_collections();
|
|
3643
3695
|
init_endpoint();
|
|
3644
3696
|
init_constants();
|
|
3697
|
+
init_thresholds();
|
|
3645
3698
|
init_query_helpers();
|
|
3646
3699
|
}
|
|
3647
3700
|
});
|
|
@@ -4330,6 +4383,69 @@ var init_insights3 = __esm({
|
|
|
4330
4383
|
}
|
|
4331
4384
|
});
|
|
4332
4385
|
|
|
4386
|
+
// src/analysis/insight-tracker.ts
|
|
4387
|
+
function computeInsightKey(insight) {
|
|
4388
|
+
const identifier = extractEndpointFromDesc(insight.desc) ?? insight.title;
|
|
4389
|
+
return `${insight.type}:${identifier}`;
|
|
4390
|
+
}
|
|
4391
|
+
var InsightTracker;
|
|
4392
|
+
var init_insight_tracker = __esm({
|
|
4393
|
+
"src/analysis/insight-tracker.ts"() {
|
|
4394
|
+
"use strict";
|
|
4395
|
+
init_endpoint();
|
|
4396
|
+
init_thresholds();
|
|
4397
|
+
InsightTracker = class {
|
|
4398
|
+
tracked = /* @__PURE__ */ new Map();
|
|
4399
|
+
reconcile(current) {
|
|
4400
|
+
const currentKeys = /* @__PURE__ */ new Set();
|
|
4401
|
+
const now = Date.now();
|
|
4402
|
+
for (const insight of current) {
|
|
4403
|
+
const key = computeInsightKey(insight);
|
|
4404
|
+
currentKeys.add(key);
|
|
4405
|
+
const existing = this.tracked.get(key);
|
|
4406
|
+
if (existing) {
|
|
4407
|
+
existing.insight = insight;
|
|
4408
|
+
existing.lastSeenAt = now;
|
|
4409
|
+
existing.consecutiveAbsences = 0;
|
|
4410
|
+
if (existing.state === "resolved") {
|
|
4411
|
+
existing.state = "open";
|
|
4412
|
+
existing.resolvedAt = null;
|
|
4413
|
+
}
|
|
4414
|
+
} else {
|
|
4415
|
+
this.tracked.set(key, {
|
|
4416
|
+
key,
|
|
4417
|
+
state: "open",
|
|
4418
|
+
insight,
|
|
4419
|
+
firstSeenAt: now,
|
|
4420
|
+
lastSeenAt: now,
|
|
4421
|
+
resolvedAt: null,
|
|
4422
|
+
consecutiveAbsences: 0
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
for (const [key, stateful] of this.tracked) {
|
|
4427
|
+
if (stateful.state === "open" && !currentKeys.has(stateful.key)) {
|
|
4428
|
+
stateful.consecutiveAbsences++;
|
|
4429
|
+
if (stateful.consecutiveAbsences >= RESOLVE_AFTER_ABSENCES) {
|
|
4430
|
+
stateful.state = "resolved";
|
|
4431
|
+
stateful.resolvedAt = now;
|
|
4432
|
+
}
|
|
4433
|
+
} else if (stateful.state === "resolved" && stateful.resolvedAt !== null && now - stateful.resolvedAt > RESOLVED_INSIGHT_TTL_MS) {
|
|
4434
|
+
this.tracked.delete(key);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
return [...this.tracked.values()];
|
|
4438
|
+
}
|
|
4439
|
+
getAll() {
|
|
4440
|
+
return [...this.tracked.values()];
|
|
4441
|
+
}
|
|
4442
|
+
clear() {
|
|
4443
|
+
this.tracked.clear();
|
|
4444
|
+
}
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
});
|
|
4448
|
+
|
|
4333
4449
|
// src/analysis/engine.ts
|
|
4334
4450
|
var AnalysisEngine;
|
|
4335
4451
|
var init_engine = __esm({
|
|
@@ -4341,6 +4457,7 @@ var init_engine = __esm({
|
|
|
4341
4457
|
init_group();
|
|
4342
4458
|
init_rules();
|
|
4343
4459
|
init_insights3();
|
|
4460
|
+
init_insight_tracker();
|
|
4344
4461
|
AnalysisEngine = class {
|
|
4345
4462
|
constructor(metricsStore, findingStore, debounceMs = 300) {
|
|
4346
4463
|
this.metricsStore = metricsStore;
|
|
@@ -4353,8 +4470,10 @@ var init_engine = __esm({
|
|
|
4353
4470
|
this.boundLogListener = () => this.scheduleRecompute();
|
|
4354
4471
|
}
|
|
4355
4472
|
scanner;
|
|
4473
|
+
insightTracker = new InsightTracker();
|
|
4356
4474
|
cachedInsights = [];
|
|
4357
4475
|
cachedFindings = [];
|
|
4476
|
+
cachedStatefulInsights = [];
|
|
4358
4477
|
debounceTimer = null;
|
|
4359
4478
|
listeners = [];
|
|
4360
4479
|
boundRequestListener;
|
|
@@ -4390,6 +4509,12 @@ var init_engine = __esm({
|
|
|
4390
4509
|
getFindings() {
|
|
4391
4510
|
return this.cachedFindings;
|
|
4392
4511
|
}
|
|
4512
|
+
getStatefulFindings() {
|
|
4513
|
+
return this.findingStore?.getAll() ?? [];
|
|
4514
|
+
}
|
|
4515
|
+
getStatefulInsights() {
|
|
4516
|
+
return this.cachedStatefulInsights;
|
|
4517
|
+
}
|
|
4393
4518
|
scheduleRecompute() {
|
|
4394
4519
|
if (this.debounceTimer) return;
|
|
4395
4520
|
this.debounceTimer = setTimeout(() => {
|
|
@@ -4420,9 +4545,16 @@ var init_engine = __esm({
|
|
|
4420
4545
|
previousMetrics: this.metricsStore.getAll(),
|
|
4421
4546
|
securityFindings: this.cachedFindings
|
|
4422
4547
|
});
|
|
4548
|
+
this.cachedStatefulInsights = this.insightTracker.reconcile(this.cachedInsights);
|
|
4549
|
+
const update = {
|
|
4550
|
+
insights: this.cachedInsights,
|
|
4551
|
+
findings: this.cachedFindings,
|
|
4552
|
+
statefulFindings: this.getStatefulFindings(),
|
|
4553
|
+
statefulInsights: this.cachedStatefulInsights
|
|
4554
|
+
};
|
|
4423
4555
|
for (const fn of this.listeners) {
|
|
4424
4556
|
try {
|
|
4425
|
-
fn(
|
|
4557
|
+
fn(update);
|
|
4426
4558
|
} catch {
|
|
4427
4559
|
}
|
|
4428
4560
|
}
|
|
@@ -4443,7 +4575,7 @@ var init_src = __esm({
|
|
|
4443
4575
|
init_engine();
|
|
4444
4576
|
init_insights3();
|
|
4445
4577
|
init_insights2();
|
|
4446
|
-
VERSION = "0.8.
|
|
4578
|
+
VERSION = "0.8.1";
|
|
4447
4579
|
}
|
|
4448
4580
|
});
|
|
4449
4581
|
|
|
@@ -4621,7 +4753,7 @@ var init_thresholds2 = __esm({
|
|
|
4621
4753
|
});
|
|
4622
4754
|
|
|
4623
4755
|
// src/dashboard/client/constants/display.ts
|
|
4624
|
-
var QUERY_OP_COLORS, LOG_LEVEL_COLORS, GRAPH_COLORS, DOT_COLORS, HEALTH_GRADES, CHART_GRID_COLOR, CHART_LABEL_COLOR, CHART_FONT, CHART_FONT_SM, CHART_FONT_XS, CHART_PAD, TL_TYPE_COLORS, TL_TYPE_LABELS, SENSITIVE_HEADERS, HTTP_STATUS_MAP, NAV_LABELS, CURL_SKIP_HEADERS;
|
|
4756
|
+
var QUERY_OP_COLORS, LOG_LEVEL_COLORS, GRAPH_COLORS, DOT_COLORS, HEALTH_GRADES, CHART_GRID_COLOR, CHART_LABEL_COLOR, CHART_FONT, CHART_FONT_SM, CHART_FONT_XS, CHART_PAD, TL_TYPE_COLORS, TL_TYPE_LABELS, SENSITIVE_HEADERS, HTTP_STATUS_MAP, NAV_LABELS, CURL_SKIP_HEADERS, SEVERITY_MAP;
|
|
4625
4757
|
var init_display = __esm({
|
|
4626
4758
|
"src/dashboard/client/constants/display.ts"() {
|
|
4627
4759
|
"use strict";
|
|
@@ -4649,6 +4781,7 @@ var init_display = __esm({
|
|
|
4649
4781
|
HTTP_STATUS_MAP = `{400:'Bad Request',401:'Unauthorized',403:'Forbidden',404:'Not Found',405:'Method Not Allowed',408:'Timeout',409:'Conflict',422:'Unprocessable',429:'Too Many Requests',500:'Internal Server Error',502:'Bad Gateway',503:'Service Unavailable',504:'Gateway Timeout'}`;
|
|
4650
4782
|
NAV_LABELS = `{ queries: 'Queries', requests: 'Requests', actions: 'Actions', errors: 'Errors', security: 'Security', fetches: 'Fetches', logs: 'Logs', performance: 'Performance' }`;
|
|
4651
4783
|
CURL_SKIP_HEADERS = `['host', 'connection', 'accept-encoding']`;
|
|
4784
|
+
SEVERITY_MAP = `{ critical: { icon: '\\u2717', cls: 'critical', sort: 0 }, warning: { icon: '\\u26A0', cls: 'warning', sort: 1 }, info: { icon: '\\u2139', cls: 'info', sort: 2 } }`;
|
|
4652
4785
|
}
|
|
4653
4786
|
});
|
|
4654
4787
|
|
|
@@ -6388,9 +6521,11 @@ function getOverviewRender() {
|
|
|
6388
6521
|
'<div class="ov-stat"><span class="ov-stat-value">' + state.fetches.length + '</span><span class="ov-stat-label">Fetches</span></div>';
|
|
6389
6522
|
container.appendChild(summary);
|
|
6390
6523
|
|
|
6391
|
-
var
|
|
6524
|
+
var all = state.insights || [];
|
|
6525
|
+
var open = all.filter(function(si) { return si.state === 'open'; });
|
|
6526
|
+
var resolved = all.filter(function(si) { return si.state === 'resolved'; });
|
|
6392
6527
|
|
|
6393
|
-
if (
|
|
6528
|
+
if (open.length === 0 && resolved.length === 0) {
|
|
6394
6529
|
var clear = document.createElement('div');
|
|
6395
6530
|
clear.className = 'ov-clear';
|
|
6396
6531
|
clear.innerHTML = '<span class="ov-clear-icon">\\u2713</span>All clear \u2014 no issues detected';
|
|
@@ -6398,67 +6533,104 @@ function getOverviewRender() {
|
|
|
6398
6533
|
return;
|
|
6399
6534
|
}
|
|
6400
6535
|
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
cards.className = 'ov-cards';
|
|
6536
|
+
if (open.length === 0 && resolved.length > 0) {
|
|
6537
|
+
var allFixed = document.createElement('div');
|
|
6538
|
+
allFixed.className = 'ov-clear';
|
|
6539
|
+
allFixed.innerHTML = '<span class="ov-clear-icon">\\u2713</span>All issues resolved \u2014 ' + resolved.length + ' finding' + (resolved.length !== 1 ? 's were' : ' was') + ' detected and fixed';
|
|
6540
|
+
container.appendChild(allFixed);
|
|
6541
|
+
}
|
|
6408
6542
|
|
|
6409
6543
|
var NAV_LABELS = ${NAV_LABELS};
|
|
6544
|
+
var SEV = ${SEVERITY_MAP};
|
|
6410
6545
|
|
|
6411
|
-
|
|
6412
|
-
(
|
|
6413
|
-
|
|
6414
|
-
|
|
6546
|
+
if (open.length > 0) {
|
|
6547
|
+
var title = document.createElement('div');
|
|
6548
|
+
title.className = 'ov-section-title';
|
|
6549
|
+
title.innerHTML = 'Issues Found <span class="ov-issue-count">' + open.length + '</span>';
|
|
6550
|
+
container.appendChild(title);
|
|
6551
|
+
|
|
6552
|
+
var cards = document.createElement('div');
|
|
6553
|
+
cards.className = 'ov-cards';
|
|
6554
|
+
|
|
6555
|
+
for (var i = 0; i < open.length; i++) {
|
|
6556
|
+
(function(si) {
|
|
6557
|
+
var insight = si.insight;
|
|
6558
|
+
var card = document.createElement('div');
|
|
6559
|
+
card.className = 'ov-card';
|
|
6560
|
+
|
|
6561
|
+
var sevCfg = SEV[insight.severity];
|
|
6562
|
+
var iconCls = sevCfg.cls;
|
|
6563
|
+
var iconChar = sevCfg.icon;
|
|
6564
|
+
|
|
6565
|
+
var expandHtml = '';
|
|
6566
|
+
if (insight.detail) expandHtml += insight.detail;
|
|
6567
|
+
if (insight.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(insight.hint) + '</div>';
|
|
6568
|
+
expandHtml += '<span class="ov-card-link" data-nav="' + insight.nav + '">View in ' + (NAV_LABELS[insight.nav] || insight.nav) + ' \\u2192</span>';
|
|
6569
|
+
|
|
6570
|
+
card.innerHTML =
|
|
6571
|
+
'<span class="ov-card-icon ' + iconCls + '">' + iconChar + '</span>' +
|
|
6572
|
+
'<div class="ov-card-body">' +
|
|
6573
|
+
'<div class="ov-card-title">' + escHtml(insight.title) + '</div>' +
|
|
6574
|
+
'<div class="ov-card-desc">' + insight.desc + '</div>' +
|
|
6575
|
+
'<div class="ov-card-expand">' + expandHtml + '</div>' +
|
|
6576
|
+
'</div>' +
|
|
6577
|
+
'<span class="ov-card-arrow">\\u2192</span>';
|
|
6578
|
+
|
|
6579
|
+
card.addEventListener('click', function(e) {
|
|
6580
|
+
var target = e.target;
|
|
6581
|
+
while (target && target !== card) {
|
|
6582
|
+
if (target.classList && target.classList.contains('ov-card-link')) {
|
|
6583
|
+
var navView = target.getAttribute('data-nav');
|
|
6584
|
+
var sidebarItem = document.querySelector('.sidebar-item[data-view="' + navView + '"]');
|
|
6585
|
+
if (sidebarItem) sidebarItem.click();
|
|
6586
|
+
return;
|
|
6587
|
+
}
|
|
6588
|
+
target = target.parentElement;
|
|
6589
|
+
}
|
|
6590
|
+
var expand = card.querySelector('.ov-card-expand');
|
|
6591
|
+
var arrow = card.querySelector('.ov-card-arrow');
|
|
6592
|
+
if (card.classList.contains('expanded')) {
|
|
6593
|
+
card.classList.remove('expanded');
|
|
6594
|
+
expand.style.display = 'none';
|
|
6595
|
+
arrow.textContent = '\\u2192';
|
|
6596
|
+
} else {
|
|
6597
|
+
card.classList.add('expanded');
|
|
6598
|
+
expand.style.display = 'block';
|
|
6599
|
+
arrow.textContent = '\\u2193';
|
|
6600
|
+
}
|
|
6601
|
+
});
|
|
6415
6602
|
|
|
6416
|
-
|
|
6417
|
-
|
|
6603
|
+
cards.appendChild(card);
|
|
6604
|
+
})(open[i]);
|
|
6605
|
+
}
|
|
6418
6606
|
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
if (insight.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(insight.hint) + '</div>';
|
|
6422
|
-
expandHtml += '<span class="ov-card-link" data-nav="' + insight.nav + '">View in ' + (NAV_LABELS[insight.nav] || insight.nav) + ' \\u2192</span>';
|
|
6607
|
+
container.appendChild(cards);
|
|
6608
|
+
}
|
|
6423
6609
|
|
|
6424
|
-
|
|
6425
|
-
|
|
6610
|
+
if (resolved.length > 0) {
|
|
6611
|
+
var resolvedTitle = document.createElement('div');
|
|
6612
|
+
resolvedTitle.className = 'ov-section-title ov-resolved-title';
|
|
6613
|
+
resolvedTitle.innerHTML = '<span style="color:var(--green)">\\u2713</span> Resolved <span class="ov-issue-count">' + resolved.length + '</span>';
|
|
6614
|
+
container.appendChild(resolvedTitle);
|
|
6615
|
+
|
|
6616
|
+
var resolvedCards = document.createElement('div');
|
|
6617
|
+
resolvedCards.className = 'ov-cards';
|
|
6618
|
+
|
|
6619
|
+
for (var ri = 0; ri < resolved.length; ri++) {
|
|
6620
|
+
var rInsight = resolved[ri].insight;
|
|
6621
|
+
var rCard = document.createElement('div');
|
|
6622
|
+
rCard.className = 'ov-card ov-card-resolved';
|
|
6623
|
+
rCard.innerHTML =
|
|
6624
|
+
'<span class="ov-card-icon resolved">\\u2713</span>' +
|
|
6426
6625
|
'<div class="ov-card-body">' +
|
|
6427
|
-
'<div class="ov-card-title">' + escHtml(
|
|
6428
|
-
'<div class="ov-card-desc">' +
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
card.addEventListener('click', function(e) {
|
|
6434
|
-
var target = e.target;
|
|
6435
|
-
while (target && target !== card) {
|
|
6436
|
-
if (target.classList && target.classList.contains('ov-card-link')) {
|
|
6437
|
-
var navView = target.getAttribute('data-nav');
|
|
6438
|
-
var sidebarItem = document.querySelector('.sidebar-item[data-view="' + navView + '"]');
|
|
6439
|
-
if (sidebarItem) sidebarItem.click();
|
|
6440
|
-
return;
|
|
6441
|
-
}
|
|
6442
|
-
target = target.parentElement;
|
|
6443
|
-
}
|
|
6444
|
-
var expand = card.querySelector('.ov-card-expand');
|
|
6445
|
-
var arrow = card.querySelector('.ov-card-arrow');
|
|
6446
|
-
if (card.classList.contains('expanded')) {
|
|
6447
|
-
card.classList.remove('expanded');
|
|
6448
|
-
expand.style.display = 'none';
|
|
6449
|
-
arrow.textContent = '\\u2192';
|
|
6450
|
-
} else {
|
|
6451
|
-
card.classList.add('expanded');
|
|
6452
|
-
expand.style.display = 'block';
|
|
6453
|
-
arrow.textContent = '\\u2193';
|
|
6454
|
-
}
|
|
6455
|
-
});
|
|
6626
|
+
'<div class="ov-card-title" style="text-decoration:line-through;color:var(--text-muted)">' + escHtml(rInsight.title) + '</div>' +
|
|
6627
|
+
'<div class="ov-card-desc">' + rInsight.desc + '</div>' +
|
|
6628
|
+
'</div>';
|
|
6629
|
+
resolvedCards.appendChild(rCard);
|
|
6630
|
+
}
|
|
6456
6631
|
|
|
6457
|
-
|
|
6458
|
-
})(insights[i]);
|
|
6632
|
+
container.appendChild(resolvedCards);
|
|
6459
6633
|
}
|
|
6460
|
-
|
|
6461
|
-
container.appendChild(cards);
|
|
6462
6634
|
}
|
|
6463
6635
|
`;
|
|
6464
6636
|
}
|
|
@@ -6488,10 +6660,13 @@ function getSecurityView() {
|
|
|
6488
6660
|
var container = document.getElementById('security-content');
|
|
6489
6661
|
if (!container) return;
|
|
6490
6662
|
container.innerHTML = '';
|
|
6663
|
+
var SEV = ${SEVERITY_MAP};
|
|
6491
6664
|
|
|
6492
|
-
var
|
|
6665
|
+
var all = state.findings || [];
|
|
6666
|
+
var open = all.filter(function(f) { return f.state === 'open' || f.state === 'fixing'; });
|
|
6667
|
+
var resolved = all.filter(function(f) { return f.state === 'resolved'; });
|
|
6493
6668
|
|
|
6494
|
-
if (
|
|
6669
|
+
if (open.length === 0 && resolved.length === 0) {
|
|
6495
6670
|
var hasData = state.requests.length > 0 || state.logs.length > 0 || state.queries.length > 0;
|
|
6496
6671
|
if (!hasData) {
|
|
6497
6672
|
container.innerHTML = '<div class="empty" style="height:400px"><span class="empty-title">Waiting for requests...</span><span class="empty-sub">Start using your app to see security findings here</span></div>';
|
|
@@ -6502,17 +6677,20 @@ function getSecurityView() {
|
|
|
6502
6677
|
}
|
|
6503
6678
|
|
|
6504
6679
|
var critCount = 0, warnCount = 0, infoCount = 0;
|
|
6505
|
-
for (var ci = 0; ci <
|
|
6506
|
-
|
|
6507
|
-
|
|
6680
|
+
for (var ci = 0; ci < open.length; ci++) {
|
|
6681
|
+
var sev = open[ci].finding.severity;
|
|
6682
|
+
if (sev === 'critical') critCount++;
|
|
6683
|
+
else if (sev === 'info') infoCount++;
|
|
6508
6684
|
else warnCount++;
|
|
6509
6685
|
}
|
|
6686
|
+
|
|
6510
6687
|
var summaryEl = document.createElement('div');
|
|
6511
6688
|
summaryEl.className = 'sec-summary';
|
|
6512
6689
|
summaryEl.innerHTML =
|
|
6513
6690
|
'<div class="sec-summary-left">' +
|
|
6514
|
-
'<span class="sec-summary-count">' +
|
|
6515
|
-
'<span class="sec-summary-label">issue' + (
|
|
6691
|
+
'<span class="sec-summary-count">' + open.length + '</span>' +
|
|
6692
|
+
'<span class="sec-summary-label">open issue' + (open.length !== 1 ? 's' : '') + '</span>' +
|
|
6693
|
+
(resolved.length > 0 ? '<span class="sec-resolved-badge">' + resolved.length + ' resolved</span>' : '') +
|
|
6516
6694
|
'</div>' +
|
|
6517
6695
|
'<div class="sec-summary-right">' +
|
|
6518
6696
|
(critCount > 0 ? '<span class="sec-badge critical">' + critCount + ' critical</span>' : '') +
|
|
@@ -6521,60 +6699,93 @@ function getSecurityView() {
|
|
|
6521
6699
|
'</div>';
|
|
6522
6700
|
container.appendChild(summaryEl);
|
|
6523
6701
|
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
groups[f.rule] = { rule: f.rule, title: f.title, severity: f.severity, hint: f.hint, items: [] };
|
|
6530
|
-
groupOrder.push(f.rule);
|
|
6531
|
-
}
|
|
6532
|
-
groups[f.rule].items.push(f);
|
|
6702
|
+
if (open.length === 0 && resolved.length > 0) {
|
|
6703
|
+
var allFixed = document.createElement('div');
|
|
6704
|
+
allFixed.className = 'sec-clear';
|
|
6705
|
+
allFixed.innerHTML = '<span class="sec-clear-icon">\\u2713</span><div class="sec-clear-text"><div class="sec-clear-title">All issues resolved</div><div class="sec-clear-sub">' + resolved.length + ' finding' + (resolved.length !== 1 ? 's were' : ' was') + ' detected and fixed</div></div>';
|
|
6706
|
+
container.appendChild(allFixed);
|
|
6533
6707
|
}
|
|
6534
6708
|
|
|
6535
|
-
|
|
6536
|
-
var
|
|
6537
|
-
var
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6709
|
+
if (open.length > 0) {
|
|
6710
|
+
var groups = {};
|
|
6711
|
+
var groupOrder = [];
|
|
6712
|
+
for (var gi = 0; gi < open.length; gi++) {
|
|
6713
|
+
var f = open[gi].finding;
|
|
6714
|
+
if (!groups[f.rule]) {
|
|
6715
|
+
groups[f.rule] = { rule: f.rule, title: f.title, severity: f.severity, hint: f.hint, items: [] };
|
|
6716
|
+
groupOrder.push(f.rule);
|
|
6717
|
+
}
|
|
6718
|
+
groups[f.rule].items.push(f);
|
|
6719
|
+
}
|
|
6541
6720
|
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6721
|
+
groupOrder.sort(function(a, b) {
|
|
6722
|
+
var sa = SEV[groups[a].severity].sort;
|
|
6723
|
+
var sb = SEV[groups[b].severity].sort;
|
|
6724
|
+
if (sa !== sb) return sa - sb;
|
|
6725
|
+
return groups[b].items.length - groups[a].items.length;
|
|
6726
|
+
});
|
|
6546
6727
|
|
|
6547
|
-
var
|
|
6548
|
-
|
|
6728
|
+
for (var oi = 0; oi < groupOrder.length; oi++) {
|
|
6729
|
+
var group = groups[groupOrder[oi]];
|
|
6730
|
+
var section = document.createElement('div');
|
|
6731
|
+
section.className = 'sec-group';
|
|
6732
|
+
|
|
6733
|
+
var sevCfg = SEV[group.severity];
|
|
6734
|
+
var iconCls = sevCfg.cls;
|
|
6735
|
+
var iconChar = sevCfg.icon;
|
|
6736
|
+
|
|
6737
|
+
var header = document.createElement('div');
|
|
6738
|
+
header.className = 'sec-group-header';
|
|
6739
|
+
header.innerHTML =
|
|
6740
|
+
'<span class="sec-group-icon ' + iconCls + '">' + iconChar + '</span>' +
|
|
6741
|
+
'<span class="sec-group-title">' + escHtml(group.title) + '</span>' +
|
|
6742
|
+
'<span class="sec-group-count">' + group.items.length + '</span>';
|
|
6743
|
+
section.appendChild(header);
|
|
6744
|
+
|
|
6745
|
+
if (group.hint) {
|
|
6746
|
+
var hintEl = document.createElement('div');
|
|
6747
|
+
hintEl.className = 'sec-hint';
|
|
6748
|
+
hintEl.textContent = group.hint;
|
|
6749
|
+
section.appendChild(hintEl);
|
|
6750
|
+
}
|
|
6549
6751
|
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
}
|
|
6564
|
-
|
|
6565
|
-
var list = document.createElement('div');
|
|
6566
|
-
list.className = 'sec-items';
|
|
6567
|
-
for (var ii = 0; ii < group.items.length; ii++) {
|
|
6568
|
-
var item = group.items[ii];
|
|
6569
|
-
var row = document.createElement('div');
|
|
6570
|
-
row.className = 'sec-item';
|
|
6571
|
-
row.innerHTML =
|
|
6572
|
-
'<div class="sec-item-desc">' + item.desc + '</div>' +
|
|
6573
|
-
(item.count > 1 ? '<span class="sec-item-count">' + item.count + 'x</span>' : '');
|
|
6574
|
-
list.appendChild(row);
|
|
6752
|
+
var list = document.createElement('div');
|
|
6753
|
+
list.className = 'sec-items';
|
|
6754
|
+
for (var ii = 0; ii < group.items.length; ii++) {
|
|
6755
|
+
var item = group.items[ii];
|
|
6756
|
+
var row = document.createElement('div');
|
|
6757
|
+
row.className = 'sec-item';
|
|
6758
|
+
row.innerHTML =
|
|
6759
|
+
'<div class="sec-item-desc">' + item.desc + '</div>' +
|
|
6760
|
+
(item.count > 1 ? '<span class="sec-item-count">' + item.count + 'x</span>' : '');
|
|
6761
|
+
list.appendChild(row);
|
|
6762
|
+
}
|
|
6763
|
+
section.appendChild(list);
|
|
6764
|
+
container.appendChild(section);
|
|
6575
6765
|
}
|
|
6576
|
-
|
|
6577
|
-
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6768
|
+
if (resolved.length > 0) {
|
|
6769
|
+
var resolvedTitle = document.createElement('div');
|
|
6770
|
+
resolvedTitle.className = 'sec-resolved-title';
|
|
6771
|
+
resolvedTitle.innerHTML = '<span class="sec-resolved-check">\\u2713</span> Resolved <span class="sec-resolved-count">' + resolved.length + '</span>';
|
|
6772
|
+
container.appendChild(resolvedTitle);
|
|
6773
|
+
|
|
6774
|
+
var resolvedGroup = document.createElement('div');
|
|
6775
|
+
resolvedGroup.className = 'sec-group sec-group-resolved';
|
|
6776
|
+
var resolvedItems = document.createElement('div');
|
|
6777
|
+
resolvedItems.className = 'sec-items';
|
|
6778
|
+
for (var ri = 0; ri < resolved.length; ri++) {
|
|
6779
|
+
var rf = resolved[ri].finding;
|
|
6780
|
+
var rRow = document.createElement('div');
|
|
6781
|
+
rRow.className = 'sec-item sec-item-resolved';
|
|
6782
|
+
rRow.innerHTML =
|
|
6783
|
+
'<span class="sec-resolved-item-icon">\\u2713</span>' +
|
|
6784
|
+
'<div class="sec-item-desc">' + escHtml(rf.title) + ' \\u2014 ' + escHtml(rf.endpoint) + '</div>';
|
|
6785
|
+
resolvedItems.appendChild(rRow);
|
|
6786
|
+
}
|
|
6787
|
+
resolvedGroup.appendChild(resolvedItems);
|
|
6788
|
+
container.appendChild(resolvedGroup);
|
|
6578
6789
|
}
|
|
6579
6790
|
}
|
|
6580
6791
|
`;
|
|
@@ -6582,6 +6793,7 @@ function getSecurityView() {
|
|
|
6582
6793
|
var init_security3 = __esm({
|
|
6583
6794
|
"src/dashboard/client/views/security.ts"() {
|
|
6584
6795
|
"use strict";
|
|
6796
|
+
init_display();
|
|
6585
6797
|
}
|
|
6586
6798
|
});
|
|
6587
6799
|
|
|
@@ -6762,7 +6974,7 @@ function getApp() {
|
|
|
6762
6974
|
if (queryCount) queryCount.textContent = state.queries.length;
|
|
6763
6975
|
var secCount = document.getElementById('sidebar-count-security');
|
|
6764
6976
|
if (secCount) {
|
|
6765
|
-
var numFindings = (state.findings || []).length;
|
|
6977
|
+
var numFindings = (state.findings || []).filter(function(f) { return f.state !== 'resolved'; }).length;
|
|
6766
6978
|
secCount.textContent = numFindings;
|
|
6767
6979
|
secCount.style.display = numFindings > 0 ? '' : 'none';
|
|
6768
6980
|
}
|
|
@@ -6931,7 +7143,7 @@ function createDashboardHandler(deps) {
|
|
|
6931
7143
|
[DASHBOARD_API_REQUESTS]: handleApiRequests,
|
|
6932
7144
|
[DASHBOARD_API_EVENTS]: createSSEHandler(deps.analysisEngine),
|
|
6933
7145
|
[DASHBOARD_API_FLOWS]: handleApiFlows,
|
|
6934
|
-
[DASHBOARD_API_CLEAR]: createClearHandler(deps.metricsStore),
|
|
7146
|
+
[DASHBOARD_API_CLEAR]: createClearHandler(deps.metricsStore, deps.findingStore),
|
|
6935
7147
|
[DASHBOARD_API_LOGS]: handleApiLogs,
|
|
6936
7148
|
[DASHBOARD_API_FETCHES]: handleApiFetches,
|
|
6937
7149
|
[DASHBOARD_API_ERRORS]: handleApiErrors,
|
|
@@ -7003,22 +7215,32 @@ var init_router = __esm({
|
|
|
7003
7215
|
}
|
|
7004
7216
|
});
|
|
7005
7217
|
|
|
7218
|
+
// src/constants/severity.ts
|
|
7219
|
+
var SEVERITY_ICON;
|
|
7220
|
+
var init_severity = __esm({
|
|
7221
|
+
"src/constants/severity.ts"() {
|
|
7222
|
+
"use strict";
|
|
7223
|
+
SEVERITY_ICON = {
|
|
7224
|
+
critical: "\u2717",
|
|
7225
|
+
warning: "\u26A0",
|
|
7226
|
+
info: "\u2139"
|
|
7227
|
+
};
|
|
7228
|
+
}
|
|
7229
|
+
});
|
|
7230
|
+
|
|
7006
7231
|
// src/output/terminal.ts
|
|
7007
7232
|
import pc from "picocolors";
|
|
7008
7233
|
function print(line) {
|
|
7009
7234
|
process.stdout.write(line + "\n");
|
|
7010
7235
|
}
|
|
7011
7236
|
function severityIcon(severity) {
|
|
7012
|
-
|
|
7013
|
-
if (severity === "warning") return pc.yellow("\u26A0");
|
|
7014
|
-
return pc.dim("\u25CB");
|
|
7237
|
+
return SEVERITY_COLOR[severity](SEVERITY_ICON[severity]);
|
|
7015
7238
|
}
|
|
7016
7239
|
function colorTitle(severity, text) {
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
return pc.dim(text);
|
|
7240
|
+
const color = SEVERITY_COLOR[severity];
|
|
7241
|
+
return severity === "info" ? color(text) : color(pc.bold(text));
|
|
7020
7242
|
}
|
|
7021
|
-
function truncate(s, max =
|
|
7243
|
+
function truncate(s, max = TERMINAL_TRUNCATE_LENGTH) {
|
|
7022
7244
|
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
7023
7245
|
}
|
|
7024
7246
|
function formatConsoleLine(insight, suffix) {
|
|
@@ -7034,38 +7256,66 @@ function formatConsoleLine(insight, suffix) {
|
|
|
7034
7256
|
}
|
|
7035
7257
|
function createConsoleInsightListener(proxyPort, metricsStore) {
|
|
7036
7258
|
const printedKeys = /* @__PURE__ */ new Set();
|
|
7259
|
+
const resolvedKeys = /* @__PURE__ */ new Set();
|
|
7037
7260
|
const dashUrl = `localhost:${proxyPort}${DASHBOARD_PREFIX}`;
|
|
7038
|
-
return (
|
|
7039
|
-
const
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7261
|
+
return ({ statefulInsights }) => {
|
|
7262
|
+
const newLines = [];
|
|
7263
|
+
const resolvedLines = [];
|
|
7264
|
+
for (const si of statefulInsights) {
|
|
7265
|
+
if (si.state === "resolved") {
|
|
7266
|
+
if (resolvedKeys.has(si.key)) continue;
|
|
7267
|
+
resolvedKeys.add(si.key);
|
|
7268
|
+
printedKeys.delete(si.key);
|
|
7269
|
+
const title = pc.green(pc.bold(`\u2713 ${si.insight.title}`));
|
|
7270
|
+
const desc = pc.dim(truncate(si.insight.desc));
|
|
7271
|
+
resolvedLines.push(` ${title} \u2014 ${desc} ${pc.green("resolved")}`);
|
|
7272
|
+
continue;
|
|
7273
|
+
}
|
|
7274
|
+
resolvedKeys.delete(si.key);
|
|
7275
|
+
if (si.insight.severity === "info") continue;
|
|
7276
|
+
if (printedKeys.has(si.key)) continue;
|
|
7277
|
+
printedKeys.add(si.key);
|
|
7046
7278
|
let suffix;
|
|
7047
|
-
if (insight.type === "slow") {
|
|
7048
|
-
const
|
|
7049
|
-
if (
|
|
7050
|
-
const
|
|
7051
|
-
|
|
7279
|
+
if (si.insight.type === "slow") {
|
|
7280
|
+
const endpoint = extractEndpointFromDesc(si.insight.desc);
|
|
7281
|
+
if (endpoint) {
|
|
7282
|
+
const ep = metricsStore.getEndpoint(endpoint);
|
|
7283
|
+
if (ep && ep.sessions.length > 1) {
|
|
7284
|
+
const prev = ep.sessions[ep.sessions.length - 2];
|
|
7285
|
+
suffix = ` (\u2191 from ${prev.p95DurationMs < 1e3 ? prev.p95DurationMs + "ms" : (prev.p95DurationMs / 1e3).toFixed(1) + "s"})`;
|
|
7286
|
+
}
|
|
7052
7287
|
}
|
|
7053
7288
|
}
|
|
7054
|
-
|
|
7289
|
+
newLines.push(formatConsoleLine(si.insight, suffix));
|
|
7055
7290
|
}
|
|
7056
|
-
if (
|
|
7291
|
+
if (newLines.length > 0) {
|
|
7057
7292
|
print("");
|
|
7058
|
-
for (const line of
|
|
7293
|
+
for (const line of newLines) print(line);
|
|
7059
7294
|
print("");
|
|
7060
7295
|
print(` ${pc.magenta(pc.bold("brakit"))} ${pc.dim("\u2192")} ${pc.dim("Dashboard:")} ${pc.underline(`http://${dashUrl}`)} ${pc.dim("or ask your AI:")} ${pc.bold('"Fix brakit findings"')}`);
|
|
7061
7296
|
}
|
|
7297
|
+
if (resolvedLines.length > 0) {
|
|
7298
|
+
print("");
|
|
7299
|
+
for (const line of resolvedLines) print(line);
|
|
7300
|
+
print("");
|
|
7301
|
+
print(` ${pc.magenta(pc.bold("brakit"))} ${pc.dim("\u2192")} ${pc.green("Issues fixed!")}`);
|
|
7302
|
+
}
|
|
7062
7303
|
};
|
|
7063
7304
|
}
|
|
7305
|
+
var SEVERITY_COLOR;
|
|
7064
7306
|
var init_terminal = __esm({
|
|
7065
7307
|
"src/output/terminal.ts"() {
|
|
7066
7308
|
"use strict";
|
|
7067
7309
|
init_src();
|
|
7068
7310
|
init_constants();
|
|
7311
|
+
init_limits();
|
|
7312
|
+
init_severity();
|
|
7313
|
+
init_endpoint();
|
|
7314
|
+
SEVERITY_COLOR = {
|
|
7315
|
+
critical: pc.red,
|
|
7316
|
+
warning: pc.yellow,
|
|
7317
|
+
info: pc.dim
|
|
7318
|
+
};
|
|
7069
7319
|
}
|
|
7070
7320
|
});
|
|
7071
7321
|
|