brakit 0.8.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +26 -9
- package/dist/api.js +221 -112
- package/dist/bin/brakit.js +598 -282
- package/dist/dashboard.html +2652 -0
- package/dist/mcp/server.js +195 -90
- package/dist/runtime/index.js +1045 -386
- package/package.json +3 -2
package/dist/runtime/index.js
CHANGED
|
@@ -9,28 +9,29 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/constants/routes.ts
|
|
12
|
-
var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, VALID_TABS;
|
|
12
|
+
var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, VALID_TABS_TUPLE, VALID_TABS;
|
|
13
13
|
var init_routes = __esm({
|
|
14
14
|
"src/constants/routes.ts"() {
|
|
15
15
|
"use strict";
|
|
16
16
|
DASHBOARD_PREFIX = "/__brakit";
|
|
17
|
-
DASHBOARD_API_REQUESTS =
|
|
18
|
-
DASHBOARD_API_EVENTS =
|
|
19
|
-
DASHBOARD_API_FLOWS =
|
|
20
|
-
DASHBOARD_API_CLEAR =
|
|
21
|
-
DASHBOARD_API_LOGS =
|
|
22
|
-
DASHBOARD_API_FETCHES =
|
|
23
|
-
DASHBOARD_API_ERRORS =
|
|
24
|
-
DASHBOARD_API_QUERIES =
|
|
25
|
-
DASHBOARD_API_INGEST =
|
|
26
|
-
DASHBOARD_API_METRICS =
|
|
27
|
-
DASHBOARD_API_ACTIVITY =
|
|
28
|
-
DASHBOARD_API_METRICS_LIVE =
|
|
29
|
-
DASHBOARD_API_INSIGHTS =
|
|
30
|
-
DASHBOARD_API_SECURITY =
|
|
31
|
-
DASHBOARD_API_TAB =
|
|
32
|
-
DASHBOARD_API_FINDINGS =
|
|
33
|
-
|
|
17
|
+
DASHBOARD_API_REQUESTS = `${DASHBOARD_PREFIX}/api/requests`;
|
|
18
|
+
DASHBOARD_API_EVENTS = `${DASHBOARD_PREFIX}/api/events`;
|
|
19
|
+
DASHBOARD_API_FLOWS = `${DASHBOARD_PREFIX}/api/flows`;
|
|
20
|
+
DASHBOARD_API_CLEAR = `${DASHBOARD_PREFIX}/api/clear`;
|
|
21
|
+
DASHBOARD_API_LOGS = `${DASHBOARD_PREFIX}/api/logs`;
|
|
22
|
+
DASHBOARD_API_FETCHES = `${DASHBOARD_PREFIX}/api/fetches`;
|
|
23
|
+
DASHBOARD_API_ERRORS = `${DASHBOARD_PREFIX}/api/errors`;
|
|
24
|
+
DASHBOARD_API_QUERIES = `${DASHBOARD_PREFIX}/api/queries`;
|
|
25
|
+
DASHBOARD_API_INGEST = `${DASHBOARD_PREFIX}/api/ingest`;
|
|
26
|
+
DASHBOARD_API_METRICS = `${DASHBOARD_PREFIX}/api/metrics`;
|
|
27
|
+
DASHBOARD_API_ACTIVITY = `${DASHBOARD_PREFIX}/api/activity`;
|
|
28
|
+
DASHBOARD_API_METRICS_LIVE = `${DASHBOARD_PREFIX}/api/metrics/live`;
|
|
29
|
+
DASHBOARD_API_INSIGHTS = `${DASHBOARD_PREFIX}/api/insights`;
|
|
30
|
+
DASHBOARD_API_SECURITY = `${DASHBOARD_PREFIX}/api/security`;
|
|
31
|
+
DASHBOARD_API_TAB = `${DASHBOARD_PREFIX}/api/tab`;
|
|
32
|
+
DASHBOARD_API_FINDINGS = `${DASHBOARD_PREFIX}/api/findings`;
|
|
33
|
+
DASHBOARD_API_FINDINGS_REPORT = `${DASHBOARD_PREFIX}/api/findings/report`;
|
|
34
|
+
VALID_TABS_TUPLE = [
|
|
34
35
|
"overview",
|
|
35
36
|
"actions",
|
|
36
37
|
"requests",
|
|
@@ -40,12 +41,13 @@ var init_routes = __esm({
|
|
|
40
41
|
"logs",
|
|
41
42
|
"performance",
|
|
42
43
|
"security"
|
|
43
|
-
]
|
|
44
|
+
];
|
|
45
|
+
VALID_TABS = new Set(VALID_TABS_TUPLE);
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
// src/constants/limits.ts
|
|
48
|
-
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;
|
|
50
|
+
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, MAX_JSON_BODY_BYTES, ANALYSIS_DEBOUNCE_MS, FINDING_ID_HASH_LENGTH, FINDINGS_DATA_VERSION, SENSITIVE_MASK_PLACEHOLDER;
|
|
49
51
|
var init_limits = __esm({
|
|
50
52
|
"src/constants/limits.ts"() {
|
|
51
53
|
"use strict";
|
|
@@ -54,10 +56,15 @@ var init_limits = __esm({
|
|
|
54
56
|
DEFAULT_API_LIMIT = 500;
|
|
55
57
|
MAX_TELEMETRY_ENTRIES = 1e3;
|
|
56
58
|
MAX_TAB_NAME_LENGTH = 32;
|
|
57
|
-
MAX_INGEST_BYTES =
|
|
59
|
+
MAX_INGEST_BYTES = 10485760;
|
|
58
60
|
TERMINAL_TRUNCATE_LENGTH = 80;
|
|
59
61
|
SENSITIVE_MASK_MIN_LENGTH = 8;
|
|
60
62
|
SENSITIVE_MASK_VISIBLE_CHARS = 4;
|
|
63
|
+
MAX_JSON_BODY_BYTES = 65536;
|
|
64
|
+
ANALYSIS_DEBOUNCE_MS = 300;
|
|
65
|
+
FINDING_ID_HASH_LENGTH = 16;
|
|
66
|
+
FINDINGS_DATA_VERSION = 1;
|
|
67
|
+
SENSITIVE_MASK_PLACEHOLDER = "****";
|
|
61
68
|
}
|
|
62
69
|
});
|
|
63
70
|
|
|
@@ -104,31 +111,23 @@ var init_transport = __esm({
|
|
|
104
111
|
"src/constants/transport.ts"() {
|
|
105
112
|
"use strict";
|
|
106
113
|
SSE_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
107
|
-
NOISE_HOSTS = [
|
|
108
|
-
|
|
109
|
-
"telemetry.nextjs.org",
|
|
110
|
-
"vitejs.dev"
|
|
111
|
-
];
|
|
112
|
-
NOISE_PATH_PATTERNS = [
|
|
113
|
-
".hot-update.",
|
|
114
|
-
"__webpack",
|
|
115
|
-
"__vite"
|
|
116
|
-
];
|
|
114
|
+
NOISE_HOSTS = ["registry.npmjs.org", "telemetry.nextjs.org", "vitejs.dev"];
|
|
115
|
+
NOISE_PATH_PATTERNS = [".hot-update.", "__webpack", "__vite"];
|
|
117
116
|
}
|
|
118
117
|
});
|
|
119
118
|
|
|
120
119
|
// src/constants/metrics.ts
|
|
121
|
-
var METRICS_DIR, METRICS_FILE,
|
|
120
|
+
var METRICS_DIR, METRICS_FILE, PORT_FILE, FINDINGS_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS, FINDINGS_FLUSH_INTERVAL_MS;
|
|
122
121
|
var init_metrics = __esm({
|
|
123
122
|
"src/constants/metrics.ts"() {
|
|
124
123
|
"use strict";
|
|
125
124
|
METRICS_DIR = ".brakit";
|
|
126
125
|
METRICS_FILE = ".brakit/metrics.json";
|
|
126
|
+
PORT_FILE = ".brakit/port";
|
|
127
|
+
FINDINGS_FILE = ".brakit/findings.json";
|
|
127
128
|
METRICS_FLUSH_INTERVAL_MS = 3e4;
|
|
128
129
|
METRICS_MAX_SESSIONS = 50;
|
|
129
130
|
METRICS_MAX_DATA_POINTS = 200;
|
|
130
|
-
PORT_FILE = ".brakit/port";
|
|
131
|
-
FINDINGS_FILE = ".brakit/findings.json";
|
|
132
131
|
FINDINGS_FLUSH_INTERVAL_MS = 1e4;
|
|
133
132
|
}
|
|
134
133
|
});
|
|
@@ -150,38 +149,36 @@ var init_headers = __esm({
|
|
|
150
149
|
});
|
|
151
150
|
|
|
152
151
|
// src/constants/network.ts
|
|
153
|
-
var LOCALHOST_IPS, LOCALHOST_HOSTNAMES,
|
|
152
|
+
var CLOUD_SIGNALS, MAX_HEALTH_ERRORS, RECOVERY_WINDOW_MS, LOCALHOST_IPS, LOCALHOST_HOSTNAMES, URL_PARSE_BASE;
|
|
154
153
|
var init_network = __esm({
|
|
155
154
|
"src/constants/network.ts"() {
|
|
156
155
|
"use strict";
|
|
157
|
-
LOCALHOST_IPS = /* @__PURE__ */ new Set([
|
|
158
|
-
"127.0.0.1",
|
|
159
|
-
"::1",
|
|
160
|
-
"::ffff:127.0.0.1"
|
|
161
|
-
]);
|
|
162
|
-
LOCALHOST_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
163
|
-
"localhost",
|
|
164
|
-
"127.0.0.1",
|
|
165
|
-
"::1"
|
|
166
|
-
]);
|
|
167
156
|
CLOUD_SIGNALS = [
|
|
168
157
|
"VERCEL",
|
|
169
158
|
"VERCEL_ENV",
|
|
170
159
|
"NETLIFY",
|
|
171
160
|
"AWS_LAMBDA_FUNCTION_NAME",
|
|
172
161
|
"AWS_EXECUTION_ENV",
|
|
162
|
+
"ECS_CONTAINER_METADATA_URI",
|
|
173
163
|
"GOOGLE_CLOUD_PROJECT",
|
|
174
164
|
"GCP_PROJECT",
|
|
165
|
+
"K_SERVICE",
|
|
175
166
|
"AZURE_FUNCTIONS_ENVIRONMENT",
|
|
167
|
+
"WEBSITE_SITE_NAME",
|
|
176
168
|
"FLY_APP_NAME",
|
|
177
169
|
"RAILWAY_ENVIRONMENT",
|
|
178
170
|
"RENDER",
|
|
179
|
-
"
|
|
171
|
+
"HEROKU_APP_NAME",
|
|
172
|
+
"DYNO",
|
|
173
|
+
"CF_INSTANCE_GUID",
|
|
180
174
|
"CF_PAGES",
|
|
181
|
-
"KUBERNETES_SERVICE_HOST"
|
|
182
|
-
"ECS_CONTAINER_METADATA_URI"
|
|
175
|
+
"KUBERNETES_SERVICE_HOST"
|
|
183
176
|
];
|
|
184
177
|
MAX_HEALTH_ERRORS = 10;
|
|
178
|
+
RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
179
|
+
LOCALHOST_IPS = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
180
|
+
LOCALHOST_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
181
|
+
URL_PARSE_BASE = "http://localhost";
|
|
185
182
|
}
|
|
186
183
|
});
|
|
187
184
|
|
|
@@ -204,7 +201,7 @@ var init_encoding = __esm({
|
|
|
204
201
|
});
|
|
205
202
|
|
|
206
203
|
// src/constants/severity.ts
|
|
207
|
-
var SEVERITY_ICON
|
|
204
|
+
var SEVERITY_ICON;
|
|
208
205
|
var init_severity = __esm({
|
|
209
206
|
"src/constants/severity.ts"() {
|
|
210
207
|
"use strict";
|
|
@@ -213,14 +210,27 @@ var init_severity = __esm({
|
|
|
213
210
|
warning: "\u26A0",
|
|
214
211
|
info: "\u2139"
|
|
215
212
|
};
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// src/constants/telemetry.ts
|
|
217
|
+
var POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS;
|
|
218
|
+
var init_telemetry = __esm({
|
|
219
|
+
"src/constants/telemetry.ts"() {
|
|
220
|
+
"use strict";
|
|
221
|
+
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
222
|
+
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
223
|
+
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// src/constants/lifecycle.ts
|
|
228
|
+
var VALID_FINDING_STATES, VALID_AI_FIX_STATUSES;
|
|
229
|
+
var init_lifecycle = __esm({
|
|
230
|
+
"src/constants/lifecycle.ts"() {
|
|
231
|
+
"use strict";
|
|
232
|
+
VALID_FINDING_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved"]);
|
|
233
|
+
VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
224
234
|
}
|
|
225
235
|
});
|
|
226
236
|
|
|
@@ -238,6 +248,8 @@ var init_constants = __esm({
|
|
|
238
248
|
init_mcp();
|
|
239
249
|
init_encoding();
|
|
240
250
|
init_severity();
|
|
251
|
+
init_telemetry();
|
|
252
|
+
init_lifecycle();
|
|
241
253
|
}
|
|
242
254
|
});
|
|
243
255
|
|
|
@@ -404,11 +416,12 @@ function createCaptureError(emit) {
|
|
|
404
416
|
}
|
|
405
417
|
function setupErrorHook(emit) {
|
|
406
418
|
const captureError = createCaptureError(emit);
|
|
407
|
-
|
|
419
|
+
const brakitExceptionHandler = (err) => {
|
|
408
420
|
captureError(err);
|
|
409
|
-
process.
|
|
421
|
+
process.removeListener("uncaughtException", brakitExceptionHandler);
|
|
410
422
|
throw err;
|
|
411
|
-
}
|
|
423
|
+
};
|
|
424
|
+
process.on("uncaughtException", brakitExceptionHandler);
|
|
412
425
|
process.on("unhandledRejection", (reason) => {
|
|
413
426
|
captureError(reason);
|
|
414
427
|
});
|
|
@@ -595,7 +608,10 @@ var init_pg = __esm({
|
|
|
595
608
|
const result = saved.apply(this, args);
|
|
596
609
|
if (result && typeof result.then === "function") {
|
|
597
610
|
return result.then((res) => {
|
|
598
|
-
|
|
611
|
+
try {
|
|
612
|
+
emitQuery(res?.rowCount ?? void 0);
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
599
615
|
return res;
|
|
600
616
|
});
|
|
601
617
|
}
|
|
@@ -673,7 +689,10 @@ var init_mysql2 = __esm({
|
|
|
673
689
|
const result = orig.apply(this, args);
|
|
674
690
|
if (result && typeof result.then === "function") {
|
|
675
691
|
return result.then((res) => {
|
|
676
|
-
|
|
692
|
+
try {
|
|
693
|
+
emitQuery();
|
|
694
|
+
} catch {
|
|
695
|
+
}
|
|
677
696
|
return res;
|
|
678
697
|
});
|
|
679
698
|
}
|
|
@@ -788,6 +807,27 @@ var init_adapters = __esm({
|
|
|
788
807
|
}
|
|
789
808
|
});
|
|
790
809
|
|
|
810
|
+
// src/constants/http.ts
|
|
811
|
+
var HTTP_OK, HTTP_NO_CONTENT, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_METHOD_NOT_ALLOWED, HTTP_PAYLOAD_TOO_LARGE, HTTP_INTERNAL_ERROR, SECURITY_HEADERS;
|
|
812
|
+
var init_http = __esm({
|
|
813
|
+
"src/constants/http.ts"() {
|
|
814
|
+
"use strict";
|
|
815
|
+
HTTP_OK = 200;
|
|
816
|
+
HTTP_NO_CONTENT = 204;
|
|
817
|
+
HTTP_BAD_REQUEST = 400;
|
|
818
|
+
HTTP_NOT_FOUND = 404;
|
|
819
|
+
HTTP_METHOD_NOT_ALLOWED = 405;
|
|
820
|
+
HTTP_PAYLOAD_TOO_LARGE = 413;
|
|
821
|
+
HTTP_INTERNAL_ERROR = 500;
|
|
822
|
+
SECURITY_HEADERS = {
|
|
823
|
+
"x-content-type-options": "nosniff",
|
|
824
|
+
"x-frame-options": "DENY",
|
|
825
|
+
"referrer-policy": "no-referrer",
|
|
826
|
+
"content-security-policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src data:"
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
791
831
|
// src/analysis/categorize.ts
|
|
792
832
|
function detectCategory(req) {
|
|
793
833
|
const { method, url, statusCode, responseHeaders } = req;
|
|
@@ -1169,12 +1209,12 @@ var init_group = __esm({
|
|
|
1169
1209
|
});
|
|
1170
1210
|
|
|
1171
1211
|
// src/dashboard/api/shared.ts
|
|
1172
|
-
function maskSensitiveHeaders(
|
|
1212
|
+
function maskSensitiveHeaders(headers2) {
|
|
1173
1213
|
const masked = {};
|
|
1174
|
-
for (const [key, value] of Object.entries(
|
|
1214
|
+
for (const [key, value] of Object.entries(headers2)) {
|
|
1175
1215
|
if (SENSITIVE_HEADER_NAMES.has(key.toLowerCase())) {
|
|
1176
1216
|
const s = String(value);
|
|
1177
|
-
masked[key] = s.length <= SENSITIVE_MASK_MIN_LENGTH ?
|
|
1217
|
+
masked[key] = s.length <= SENSITIVE_MASK_MIN_LENGTH ? SENSITIVE_MASK_PLACEHOLDER : s.slice(0, SENSITIVE_MASK_VISIBLE_CHARS) + "..." + s.slice(-SENSITIVE_MASK_VISIBLE_CHARS);
|
|
1178
1218
|
} else {
|
|
1179
1219
|
masked[key] = value;
|
|
1180
1220
|
}
|
|
@@ -1194,14 +1234,14 @@ function getCorsOrigin(req) {
|
|
|
1194
1234
|
}
|
|
1195
1235
|
function getJsonHeaders(req) {
|
|
1196
1236
|
const corsOrigin = getCorsOrigin(req);
|
|
1197
|
-
const
|
|
1237
|
+
const headers2 = {
|
|
1198
1238
|
"content-type": "application/json",
|
|
1199
1239
|
"cache-control": "no-cache"
|
|
1200
1240
|
};
|
|
1201
1241
|
if (corsOrigin) {
|
|
1202
|
-
|
|
1242
|
+
headers2["access-control-allow-origin"] = corsOrigin;
|
|
1203
1243
|
}
|
|
1204
|
-
return
|
|
1244
|
+
return headers2;
|
|
1205
1245
|
}
|
|
1206
1246
|
function sendJson(req, res, status, data) {
|
|
1207
1247
|
res.writeHead(status, getJsonHeaders(req));
|
|
@@ -1209,23 +1249,58 @@ function sendJson(req, res, status, data) {
|
|
|
1209
1249
|
}
|
|
1210
1250
|
function requireGet(req, res) {
|
|
1211
1251
|
if (req.method !== "GET") {
|
|
1212
|
-
sendJson(req, res,
|
|
1252
|
+
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
1213
1253
|
return false;
|
|
1214
1254
|
}
|
|
1215
1255
|
return true;
|
|
1216
1256
|
}
|
|
1257
|
+
function parseRequestUrl(req) {
|
|
1258
|
+
return new URL(req.url ?? "/", URL_PARSE_BASE);
|
|
1259
|
+
}
|
|
1260
|
+
function readJsonBody(req, res, maxBytes = MAX_JSON_BODY_BYTES) {
|
|
1261
|
+
return new Promise((resolve5) => {
|
|
1262
|
+
const chunks = [];
|
|
1263
|
+
let size = 0;
|
|
1264
|
+
req.on("data", (chunk) => {
|
|
1265
|
+
size += chunk.length;
|
|
1266
|
+
if (size > maxBytes) {
|
|
1267
|
+
sendJson(req, res, HTTP_PAYLOAD_TOO_LARGE, { error: "Payload too large" });
|
|
1268
|
+
req.destroy();
|
|
1269
|
+
resolve5(null);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
chunks.push(chunk);
|
|
1273
|
+
});
|
|
1274
|
+
req.on("end", () => {
|
|
1275
|
+
if (size > maxBytes) {
|
|
1276
|
+
resolve5(null);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
try {
|
|
1280
|
+
resolve5(JSON.parse(Buffer.concat(chunks).toString()));
|
|
1281
|
+
} catch {
|
|
1282
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "Invalid JSON body" });
|
|
1283
|
+
resolve5(null);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
req.on("error", () => {
|
|
1287
|
+
resolve5(null);
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1217
1291
|
function handleTelemetryGet(req, res, store) {
|
|
1218
1292
|
if (!requireGet(req, res)) return;
|
|
1219
|
-
const url =
|
|
1293
|
+
const url = parseRequestUrl(req);
|
|
1220
1294
|
const requestId = url.searchParams.get("requestId");
|
|
1221
1295
|
const entries = requestId ? store.getByRequest(requestId) : [...store.getAll()];
|
|
1222
|
-
sendJson(req, res,
|
|
1296
|
+
sendJson(req, res, HTTP_OK, { total: entries.length, entries: entries.reverse() });
|
|
1223
1297
|
}
|
|
1224
1298
|
var init_shared2 = __esm({
|
|
1225
1299
|
"src/dashboard/api/shared.ts"() {
|
|
1226
1300
|
"use strict";
|
|
1227
1301
|
init_constants();
|
|
1228
1302
|
init_limits();
|
|
1303
|
+
init_http();
|
|
1229
1304
|
}
|
|
1230
1305
|
});
|
|
1231
1306
|
|
|
@@ -1240,7 +1315,7 @@ function sanitizeRequest(r) {
|
|
|
1240
1315
|
function createRequestsHandler(registry) {
|
|
1241
1316
|
return (req, res) => {
|
|
1242
1317
|
if (!requireGet(req, res)) return;
|
|
1243
|
-
const url =
|
|
1318
|
+
const url = parseRequestUrl(req);
|
|
1244
1319
|
const method = url.searchParams.get("method");
|
|
1245
1320
|
const status = url.searchParams.get("status");
|
|
1246
1321
|
const search = url.searchParams.get("search");
|
|
@@ -1273,7 +1348,7 @@ function createRequestsHandler(registry) {
|
|
|
1273
1348
|
const total = results.length;
|
|
1274
1349
|
results = results.slice(offset, offset + limit);
|
|
1275
1350
|
const sanitized = results.map(sanitizeRequest);
|
|
1276
|
-
sendJson(req, res,
|
|
1351
|
+
sendJson(req, res, HTTP_OK, { total, requests: sanitized });
|
|
1277
1352
|
};
|
|
1278
1353
|
}
|
|
1279
1354
|
function createFlowsHandler(registry) {
|
|
@@ -1283,13 +1358,13 @@ function createFlowsHandler(registry) {
|
|
|
1283
1358
|
...flow,
|
|
1284
1359
|
requests: flow.requests.map(sanitizeRequest)
|
|
1285
1360
|
}));
|
|
1286
|
-
sendJson(req, res,
|
|
1361
|
+
sendJson(req, res, HTTP_OK, { total: flows.length, flows });
|
|
1287
1362
|
};
|
|
1288
1363
|
}
|
|
1289
1364
|
function createClearHandler(registry) {
|
|
1290
1365
|
return (req, res) => {
|
|
1291
1366
|
if (req.method !== "POST") {
|
|
1292
|
-
sendJson(req, res,
|
|
1367
|
+
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
1293
1368
|
return;
|
|
1294
1369
|
}
|
|
1295
1370
|
registry.get("request-store").clear();
|
|
@@ -1300,7 +1375,7 @@ function createClearHandler(registry) {
|
|
|
1300
1375
|
registry.get("metrics-store").reset();
|
|
1301
1376
|
if (registry.has("finding-store")) registry.get("finding-store").clear();
|
|
1302
1377
|
registry.get("event-bus").emit("store:cleared", void 0);
|
|
1303
|
-
sendJson(req, res,
|
|
1378
|
+
sendJson(req, res, HTTP_OK, { cleared: true });
|
|
1304
1379
|
};
|
|
1305
1380
|
}
|
|
1306
1381
|
function createFetchesHandler(registry) {
|
|
@@ -1320,10 +1395,158 @@ var init_handlers = __esm({
|
|
|
1320
1395
|
"use strict";
|
|
1321
1396
|
init_group();
|
|
1322
1397
|
init_constants();
|
|
1398
|
+
init_http();
|
|
1323
1399
|
init_shared2();
|
|
1324
1400
|
}
|
|
1325
1401
|
});
|
|
1326
1402
|
|
|
1403
|
+
// src/utils/type-guards.ts
|
|
1404
|
+
function isString(val) {
|
|
1405
|
+
return typeof val === "string";
|
|
1406
|
+
}
|
|
1407
|
+
function isNumber(val) {
|
|
1408
|
+
return typeof val === "number" && !isNaN(val);
|
|
1409
|
+
}
|
|
1410
|
+
function isBoolean(val) {
|
|
1411
|
+
return typeof val === "boolean";
|
|
1412
|
+
}
|
|
1413
|
+
function getErrorMessage(err) {
|
|
1414
|
+
if (err instanceof Error) return err.message;
|
|
1415
|
+
if (typeof err === "string") return err;
|
|
1416
|
+
return String(err);
|
|
1417
|
+
}
|
|
1418
|
+
function isValidFindingState(val) {
|
|
1419
|
+
return typeof val === "string" && VALID_FINDING_STATES.has(val);
|
|
1420
|
+
}
|
|
1421
|
+
function isValidAiFixStatus(val) {
|
|
1422
|
+
return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
|
|
1423
|
+
}
|
|
1424
|
+
var init_type_guards = __esm({
|
|
1425
|
+
"src/utils/type-guards.ts"() {
|
|
1426
|
+
"use strict";
|
|
1427
|
+
init_lifecycle();
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
|
|
1431
|
+
// src/dashboard/api/sdk-event-parser.ts
|
|
1432
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1433
|
+
function str(val, fallback) {
|
|
1434
|
+
return isString(val) ? val : fallback;
|
|
1435
|
+
}
|
|
1436
|
+
function strOrUndef(val) {
|
|
1437
|
+
return isString(val) ? val : void 0;
|
|
1438
|
+
}
|
|
1439
|
+
function num(val, fallback) {
|
|
1440
|
+
return isNumber(val) ? val : fallback;
|
|
1441
|
+
}
|
|
1442
|
+
function numOrUndef(val) {
|
|
1443
|
+
return isNumber(val) ? val : void 0;
|
|
1444
|
+
}
|
|
1445
|
+
function headers(val) {
|
|
1446
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
1447
|
+
return val;
|
|
1448
|
+
}
|
|
1449
|
+
return {};
|
|
1450
|
+
}
|
|
1451
|
+
function parseQueryEvent(data, ts, parentRequestId) {
|
|
1452
|
+
return {
|
|
1453
|
+
driver: str(data.source, "sdk"),
|
|
1454
|
+
source: strOrUndef(data.source),
|
|
1455
|
+
sql: strOrUndef(data.sql),
|
|
1456
|
+
model: strOrUndef(data.model),
|
|
1457
|
+
operation: strOrUndef(data.operation),
|
|
1458
|
+
normalizedOp: strOrUndef(data.normalizedOp) ?? strOrUndef(data.operation) ?? "OTHER",
|
|
1459
|
+
table: str(data.table, ""),
|
|
1460
|
+
durationMs: num(data.duration, num(data.durationMs, 0)),
|
|
1461
|
+
rowCount: numOrUndef(data.rowCount),
|
|
1462
|
+
parentRequestId,
|
|
1463
|
+
timestamp: ts
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
function parseFetchEvent(data, ts, parentRequestId) {
|
|
1467
|
+
return {
|
|
1468
|
+
url: str(data.url, ""),
|
|
1469
|
+
method: str(data.method, "GET"),
|
|
1470
|
+
statusCode: num(data.statusCode, 0),
|
|
1471
|
+
durationMs: num(data.duration, num(data.durationMs, 0)),
|
|
1472
|
+
parentRequestId,
|
|
1473
|
+
timestamp: ts
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function parseLogEvent(data, ts, parentRequestId) {
|
|
1477
|
+
return {
|
|
1478
|
+
level: str(data.level, "log"),
|
|
1479
|
+
message: str(data.message, ""),
|
|
1480
|
+
parentRequestId,
|
|
1481
|
+
timestamp: ts
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
function parseErrorEvent(data, ts, parentRequestId) {
|
|
1485
|
+
return {
|
|
1486
|
+
name: str(data.name, "Error"),
|
|
1487
|
+
message: str(data.message, ""),
|
|
1488
|
+
stack: str(data.stack, ""),
|
|
1489
|
+
parentRequestId,
|
|
1490
|
+
timestamp: ts
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function parseAuthEvent(data, ts, parentRequestId) {
|
|
1494
|
+
return {
|
|
1495
|
+
level: "info",
|
|
1496
|
+
message: `[auth] ${str(data.provider, "unknown")}: ${str(data.result, "check")}`,
|
|
1497
|
+
parentRequestId,
|
|
1498
|
+
timestamp: ts
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
function parseRequestEvent(data, ts) {
|
|
1502
|
+
const url = str(data.url, "");
|
|
1503
|
+
return {
|
|
1504
|
+
id: str(data.id, randomUUID3()),
|
|
1505
|
+
method: str(data.method, "GET"),
|
|
1506
|
+
url,
|
|
1507
|
+
path: url.split("?")[0],
|
|
1508
|
+
headers: headers(data.headers),
|
|
1509
|
+
requestBody: isString(data.requestBody) ? data.requestBody : null,
|
|
1510
|
+
statusCode: num(data.statusCode, 200),
|
|
1511
|
+
responseHeaders: headers(data.responseHeaders),
|
|
1512
|
+
responseBody: isString(data.responseBody) ? data.responseBody : null,
|
|
1513
|
+
startedAt: ts,
|
|
1514
|
+
durationMs: num(data.durationMs, 0),
|
|
1515
|
+
responseSize: num(data.responseSize, 0),
|
|
1516
|
+
isStatic: isBoolean(data.isStatic) ? data.isStatic : false
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
function routeSDKEvent(event, stores) {
|
|
1520
|
+
const ts = event.timestamp || Date.now();
|
|
1521
|
+
const parentRequestId = event.requestId ?? null;
|
|
1522
|
+
switch (event.type) {
|
|
1523
|
+
case "db.query":
|
|
1524
|
+
stores.addQuery(parseQueryEvent(event.data, ts, parentRequestId));
|
|
1525
|
+
break;
|
|
1526
|
+
case "fetch":
|
|
1527
|
+
stores.addFetch(parseFetchEvent(event.data, ts, parentRequestId));
|
|
1528
|
+
break;
|
|
1529
|
+
case "log":
|
|
1530
|
+
stores.addLog(parseLogEvent(event.data, ts, parentRequestId));
|
|
1531
|
+
break;
|
|
1532
|
+
case "error":
|
|
1533
|
+
stores.addError(parseErrorEvent(event.data, ts, parentRequestId));
|
|
1534
|
+
break;
|
|
1535
|
+
case "auth.check":
|
|
1536
|
+
stores.addLog(parseAuthEvent(event.data, ts, parentRequestId));
|
|
1537
|
+
break;
|
|
1538
|
+
case "request":
|
|
1539
|
+
stores.addRequest(parseRequestEvent(event.data, ts));
|
|
1540
|
+
break;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
var init_sdk_event_parser = __esm({
|
|
1544
|
+
"src/dashboard/api/sdk-event-parser.ts"() {
|
|
1545
|
+
"use strict";
|
|
1546
|
+
init_type_guards();
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1327
1550
|
// src/dashboard/api/ingest.ts
|
|
1328
1551
|
function isBrakitBatch(msg) {
|
|
1329
1552
|
return typeof msg === "object" && msg !== null && "_brakit" in msg && msg._brakit === true && !("version" in msg);
|
|
@@ -1348,65 +1571,21 @@ function createIngestHandler(registry) {
|
|
|
1348
1571
|
break;
|
|
1349
1572
|
}
|
|
1350
1573
|
};
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
normalizedOp: event.data.normalizedOp ?? event.data.operation ?? "OTHER",
|
|
1363
|
-
table: event.data.table ?? "",
|
|
1364
|
-
durationMs: event.data.duration ?? event.data.durationMs ?? 0,
|
|
1365
|
-
rowCount: event.data.rowCount,
|
|
1366
|
-
parentRequestId,
|
|
1367
|
-
timestamp: ts
|
|
1368
|
-
});
|
|
1369
|
-
break;
|
|
1370
|
-
case "fetch":
|
|
1371
|
-
registry.get("fetch-store").add({
|
|
1372
|
-
url: event.data.url ?? "",
|
|
1373
|
-
method: event.data.method ?? "GET",
|
|
1374
|
-
statusCode: event.data.statusCode ?? 0,
|
|
1375
|
-
durationMs: event.data.duration ?? event.data.durationMs ?? 0,
|
|
1376
|
-
parentRequestId,
|
|
1377
|
-
timestamp: ts
|
|
1378
|
-
});
|
|
1379
|
-
break;
|
|
1380
|
-
case "log":
|
|
1381
|
-
registry.get("log-store").add({
|
|
1382
|
-
level: event.data.level ?? "log",
|
|
1383
|
-
message: event.data.message ?? "",
|
|
1384
|
-
parentRequestId,
|
|
1385
|
-
timestamp: ts
|
|
1386
|
-
});
|
|
1387
|
-
break;
|
|
1388
|
-
case "error":
|
|
1389
|
-
registry.get("error-store").add({
|
|
1390
|
-
name: event.data.name ?? "Error",
|
|
1391
|
-
message: event.data.message ?? "",
|
|
1392
|
-
stack: event.data.stack ?? "",
|
|
1393
|
-
parentRequestId,
|
|
1394
|
-
timestamp: ts
|
|
1395
|
-
});
|
|
1396
|
-
break;
|
|
1397
|
-
case "auth.check":
|
|
1398
|
-
registry.get("log-store").add({
|
|
1399
|
-
level: "info",
|
|
1400
|
-
message: `[auth] ${event.data.provider ?? "unknown"}: ${event.data.result ?? "check"}`,
|
|
1401
|
-
parentRequestId,
|
|
1402
|
-
timestamp: ts
|
|
1403
|
-
});
|
|
1404
|
-
break;
|
|
1405
|
-
}
|
|
1574
|
+
const queryStore = registry.get("query-store");
|
|
1575
|
+
const fetchStore = registry.get("fetch-store");
|
|
1576
|
+
const logStore = registry.get("log-store");
|
|
1577
|
+
const errorStore = registry.get("error-store");
|
|
1578
|
+
const requestStore = registry.get("request-store");
|
|
1579
|
+
const stores = {
|
|
1580
|
+
addQuery: (data) => queryStore.add(data),
|
|
1581
|
+
addFetch: (data) => fetchStore.add(data),
|
|
1582
|
+
addLog: (data) => logStore.add(data),
|
|
1583
|
+
addError: (data) => errorStore.add(data),
|
|
1584
|
+
addRequest: (data) => requestStore.add(data)
|
|
1406
1585
|
};
|
|
1407
1586
|
return (req, res) => {
|
|
1408
1587
|
if (req.method !== "POST") {
|
|
1409
|
-
sendJson(req, res,
|
|
1588
|
+
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
1410
1589
|
return;
|
|
1411
1590
|
}
|
|
1412
1591
|
const chunks = [];
|
|
@@ -1414,7 +1593,7 @@ function createIngestHandler(registry) {
|
|
|
1414
1593
|
req.on("data", (chunk) => {
|
|
1415
1594
|
totalSize += chunk.length;
|
|
1416
1595
|
if (totalSize > MAX_INGEST_BYTES) {
|
|
1417
|
-
sendJson(req, res,
|
|
1596
|
+
sendJson(req, res, HTTP_PAYLOAD_TOO_LARGE, { error: "Payload too large" });
|
|
1418
1597
|
req.destroy();
|
|
1419
1598
|
return;
|
|
1420
1599
|
}
|
|
@@ -1426,9 +1605,9 @@ function createIngestHandler(registry) {
|
|
|
1426
1605
|
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
1427
1606
|
if (isSDKPayload(body)) {
|
|
1428
1607
|
for (const event of body.events) {
|
|
1429
|
-
routeSDKEvent(event);
|
|
1608
|
+
routeSDKEvent(event, stores);
|
|
1430
1609
|
}
|
|
1431
|
-
res.writeHead(
|
|
1610
|
+
res.writeHead(HTTP_NO_CONTENT);
|
|
1432
1611
|
res.end();
|
|
1433
1612
|
return;
|
|
1434
1613
|
}
|
|
@@ -1436,13 +1615,19 @@ function createIngestHandler(registry) {
|
|
|
1436
1615
|
for (const event of body.events) {
|
|
1437
1616
|
routeEvent(event);
|
|
1438
1617
|
}
|
|
1439
|
-
res.writeHead(
|
|
1618
|
+
res.writeHead(HTTP_NO_CONTENT);
|
|
1440
1619
|
res.end();
|
|
1441
1620
|
return;
|
|
1442
1621
|
}
|
|
1443
|
-
sendJson(req, res,
|
|
1622
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "Invalid batch" });
|
|
1444
1623
|
} catch {
|
|
1445
|
-
sendJson(req, res,
|
|
1624
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "Invalid JSON" });
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
1627
|
+
req.on("error", () => {
|
|
1628
|
+
if (!res.headersSent) {
|
|
1629
|
+
res.writeHead(HTTP_BAD_REQUEST);
|
|
1630
|
+
res.end();
|
|
1446
1631
|
}
|
|
1447
1632
|
});
|
|
1448
1633
|
};
|
|
@@ -1451,7 +1636,9 @@ var init_ingest = __esm({
|
|
|
1451
1636
|
"src/dashboard/api/ingest.ts"() {
|
|
1452
1637
|
"use strict";
|
|
1453
1638
|
init_limits();
|
|
1639
|
+
init_http();
|
|
1454
1640
|
init_shared2();
|
|
1641
|
+
init_sdk_event_parser();
|
|
1455
1642
|
}
|
|
1456
1643
|
});
|
|
1457
1644
|
|
|
@@ -1459,20 +1646,21 @@ var init_ingest = __esm({
|
|
|
1459
1646
|
function createMetricsHandler(metricsStore) {
|
|
1460
1647
|
return (req, res) => {
|
|
1461
1648
|
if (!requireGet(req, res)) return;
|
|
1462
|
-
const url =
|
|
1649
|
+
const url = parseRequestUrl(req);
|
|
1463
1650
|
const endpoint = url.searchParams.get("endpoint");
|
|
1464
1651
|
if (endpoint) {
|
|
1465
1652
|
const ep = metricsStore.getEndpoint(endpoint);
|
|
1466
|
-
sendJson(req, res,
|
|
1653
|
+
sendJson(req, res, HTTP_OK, { endpoints: ep ? [ep] : [] });
|
|
1467
1654
|
return;
|
|
1468
1655
|
}
|
|
1469
|
-
sendJson(req, res,
|
|
1656
|
+
sendJson(req, res, HTTP_OK, { endpoints: metricsStore.getAll() });
|
|
1470
1657
|
};
|
|
1471
1658
|
}
|
|
1472
1659
|
var init_metrics2 = __esm({
|
|
1473
1660
|
"src/dashboard/api/metrics.ts"() {
|
|
1474
1661
|
"use strict";
|
|
1475
1662
|
init_shared2();
|
|
1663
|
+
init_http();
|
|
1476
1664
|
}
|
|
1477
1665
|
});
|
|
1478
1666
|
|
|
@@ -1490,15 +1678,34 @@ var init_metrics_live = __esm({
|
|
|
1490
1678
|
}
|
|
1491
1679
|
});
|
|
1492
1680
|
|
|
1681
|
+
// src/utils/log.ts
|
|
1682
|
+
function brakitWarn(message) {
|
|
1683
|
+
process.stderr.write(`${PREFIX} ${message}
|
|
1684
|
+
`);
|
|
1685
|
+
}
|
|
1686
|
+
function brakitDebug(message) {
|
|
1687
|
+
if (process.env.DEBUG_BRAKIT) {
|
|
1688
|
+
process.stderr.write(`${PREFIX}:debug ${message}
|
|
1689
|
+
`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
var PREFIX;
|
|
1693
|
+
var init_log = __esm({
|
|
1694
|
+
"src/utils/log.ts"() {
|
|
1695
|
+
"use strict";
|
|
1696
|
+
PREFIX = "[brakit]";
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1493
1700
|
// src/dashboard/api/activity.ts
|
|
1494
1701
|
function createActivityHandler(registry) {
|
|
1495
1702
|
return (req, res) => {
|
|
1496
1703
|
if (!requireGet(req, res)) return;
|
|
1497
1704
|
try {
|
|
1498
|
-
const url =
|
|
1705
|
+
const url = parseRequestUrl(req);
|
|
1499
1706
|
const requestId = url.searchParams.get("requestId");
|
|
1500
1707
|
if (!requestId) {
|
|
1501
|
-
sendJson(req, res,
|
|
1708
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "requestId parameter required" });
|
|
1502
1709
|
return;
|
|
1503
1710
|
}
|
|
1504
1711
|
const fetches = registry.get("fetch-store").getByRequest(requestId);
|
|
@@ -1515,7 +1722,7 @@ function createActivityHandler(registry) {
|
|
|
1515
1722
|
for (const q of queries)
|
|
1516
1723
|
timeline.push({ type: "query", timestamp: q.timestamp, data: { ...q } });
|
|
1517
1724
|
timeline.sort((a, b) => a.timestamp - b.timestamp);
|
|
1518
|
-
sendJson(req, res,
|
|
1725
|
+
sendJson(req, res, HTTP_OK, {
|
|
1519
1726
|
requestId,
|
|
1520
1727
|
total: timeline.length,
|
|
1521
1728
|
timeline,
|
|
@@ -1527,9 +1734,9 @@ function createActivityHandler(registry) {
|
|
|
1527
1734
|
}
|
|
1528
1735
|
});
|
|
1529
1736
|
} catch (err) {
|
|
1530
|
-
|
|
1737
|
+
brakitDebug(`activity handler error: ${err}`);
|
|
1531
1738
|
if (!res.headersSent) {
|
|
1532
|
-
sendJson(req, res,
|
|
1739
|
+
sendJson(req, res, HTTP_INTERNAL_ERROR, { error: "Internal error" });
|
|
1533
1740
|
}
|
|
1534
1741
|
}
|
|
1535
1742
|
};
|
|
@@ -1538,6 +1745,8 @@ var init_activity = __esm({
|
|
|
1538
1745
|
"src/dashboard/api/activity.ts"() {
|
|
1539
1746
|
"use strict";
|
|
1540
1747
|
init_shared2();
|
|
1748
|
+
init_http();
|
|
1749
|
+
init_log();
|
|
1541
1750
|
}
|
|
1542
1751
|
});
|
|
1543
1752
|
|
|
@@ -1557,19 +1766,20 @@ var init_api = __esm({
|
|
|
1557
1766
|
function createInsightsHandler(engine) {
|
|
1558
1767
|
return (req, res) => {
|
|
1559
1768
|
if (!requireGet(req, res)) return;
|
|
1560
|
-
sendJson(req, res,
|
|
1769
|
+
sendJson(req, res, HTTP_OK, { insights: engine.getStatefulInsights() });
|
|
1561
1770
|
};
|
|
1562
1771
|
}
|
|
1563
1772
|
function createSecurityHandler(engine) {
|
|
1564
1773
|
return (req, res) => {
|
|
1565
1774
|
if (!requireGet(req, res)) return;
|
|
1566
|
-
sendJson(req, res,
|
|
1775
|
+
sendJson(req, res, HTTP_OK, { findings: engine.getStatefulFindings() });
|
|
1567
1776
|
};
|
|
1568
1777
|
}
|
|
1569
1778
|
var init_insights = __esm({
|
|
1570
1779
|
"src/dashboard/api/insights.ts"() {
|
|
1571
1780
|
"use strict";
|
|
1572
1781
|
init_shared2();
|
|
1782
|
+
init_http();
|
|
1573
1783
|
}
|
|
1574
1784
|
});
|
|
1575
1785
|
|
|
@@ -1577,99 +1787,160 @@ var init_insights = __esm({
|
|
|
1577
1787
|
function createFindingsHandler(findingStore) {
|
|
1578
1788
|
return (req, res) => {
|
|
1579
1789
|
if (!requireGet(req, res)) return;
|
|
1580
|
-
const url =
|
|
1790
|
+
const url = parseRequestUrl(req);
|
|
1581
1791
|
const stateParam = url.searchParams.get("state");
|
|
1582
1792
|
let findings;
|
|
1583
|
-
if (stateParam &&
|
|
1793
|
+
if (stateParam && isValidFindingState(stateParam)) {
|
|
1584
1794
|
findings = findingStore.getByState(stateParam);
|
|
1585
1795
|
} else {
|
|
1586
1796
|
findings = findingStore.getAll();
|
|
1587
1797
|
}
|
|
1588
|
-
sendJson(req, res,
|
|
1798
|
+
sendJson(req, res, HTTP_OK, {
|
|
1589
1799
|
total: findings.length,
|
|
1590
1800
|
findings
|
|
1591
1801
|
});
|
|
1592
1802
|
};
|
|
1593
1803
|
}
|
|
1594
|
-
|
|
1804
|
+
function createFindingsReportHandler(findingStore, eventBus, analysisEngine) {
|
|
1805
|
+
return async (req, res) => {
|
|
1806
|
+
if (req.method !== "POST") {
|
|
1807
|
+
sendJson(req, res, HTTP_METHOD_NOT_ALLOWED, { error: "Method not allowed" });
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
const body = await readJsonBody(req, res);
|
|
1811
|
+
if (!body) return;
|
|
1812
|
+
const { findingId, status, notes } = body;
|
|
1813
|
+
if (!findingId || typeof findingId !== "string") {
|
|
1814
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "findingId is required" });
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
if (!isValidAiFixStatus(status)) {
|
|
1818
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "status must be 'fixed' or 'wont_fix'" });
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (!notes || typeof notes !== "string") {
|
|
1822
|
+
sendJson(req, res, HTTP_BAD_REQUEST, { error: "notes is required" });
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const findingOk = findingStore.reportFix(findingId, status, notes);
|
|
1826
|
+
if (findingOk) {
|
|
1827
|
+
eventBus.emit("findings:changed", findingStore.getAll());
|
|
1828
|
+
sendJson(req, res, HTTP_OK, { ok: true });
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
if (analysisEngine?.reportInsightFix(findingId, status, notes)) {
|
|
1832
|
+
eventBus.emit("analysis:updated", {
|
|
1833
|
+
insights: analysisEngine.getInsights(),
|
|
1834
|
+
findings: analysisEngine.getFindings(),
|
|
1835
|
+
statefulFindings: analysisEngine.getStatefulFindings(),
|
|
1836
|
+
statefulInsights: analysisEngine.getStatefulInsights()
|
|
1837
|
+
});
|
|
1838
|
+
sendJson(req, res, HTTP_OK, { ok: true });
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
sendJson(req, res, HTTP_NOT_FOUND, { error: "Finding not found" });
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1595
1844
|
var init_findings = __esm({
|
|
1596
1845
|
"src/dashboard/api/findings.ts"() {
|
|
1597
1846
|
"use strict";
|
|
1598
1847
|
init_shared2();
|
|
1599
|
-
|
|
1848
|
+
init_type_guards();
|
|
1849
|
+
init_http();
|
|
1600
1850
|
}
|
|
1601
1851
|
});
|
|
1602
1852
|
|
|
1603
|
-
// src/
|
|
1604
|
-
var
|
|
1605
|
-
var
|
|
1606
|
-
"src/
|
|
1853
|
+
// src/constants/events.ts
|
|
1854
|
+
var SSE_EVENT_FETCH, SSE_EVENT_LOG, SSE_EVENT_ERROR, SSE_EVENT_QUERY, SSE_EVENT_INSIGHTS, SSE_EVENT_SECURITY;
|
|
1855
|
+
var init_events = __esm({
|
|
1856
|
+
"src/constants/events.ts"() {
|
|
1607
1857
|
"use strict";
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
for (const d of this.items) d.dispose();
|
|
1615
|
-
this.items.length = 0;
|
|
1616
|
-
}
|
|
1617
|
-
};
|
|
1858
|
+
SSE_EVENT_FETCH = "fetch";
|
|
1859
|
+
SSE_EVENT_LOG = "log";
|
|
1860
|
+
SSE_EVENT_ERROR = "error_event";
|
|
1861
|
+
SSE_EVENT_QUERY = "query";
|
|
1862
|
+
SSE_EVENT_INSIGHTS = "insights";
|
|
1863
|
+
SSE_EVENT_SECURITY = "security";
|
|
1618
1864
|
}
|
|
1619
1865
|
});
|
|
1620
1866
|
|
|
1621
1867
|
// src/dashboard/sse.ts
|
|
1622
1868
|
function createSSEHandler(registry) {
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
connection: "keep-alive",
|
|
1628
|
-
"access-control-allow-origin": "*"
|
|
1629
|
-
});
|
|
1630
|
-
res.write(":ok\n\n");
|
|
1631
|
-
const writeEvent = (eventType, data) => {
|
|
1632
|
-
if (res.destroyed) return;
|
|
1633
|
-
if (eventType) {
|
|
1634
|
-
res.write(`event: ${eventType}
|
|
1869
|
+
const clients = /* @__PURE__ */ new Set();
|
|
1870
|
+
function broadcast(eventType, data) {
|
|
1871
|
+
if (clients.size === 0) return;
|
|
1872
|
+
const frame = eventType ? `event: ${eventType}
|
|
1635
1873
|
data: ${data}
|
|
1636
1874
|
|
|
1637
|
-
`
|
|
1638
|
-
} else {
|
|
1639
|
-
res.write(`data: ${data}
|
|
1875
|
+
` : `data: ${data}
|
|
1640
1876
|
|
|
1641
|
-
|
|
1877
|
+
`;
|
|
1878
|
+
for (const client of clients) {
|
|
1879
|
+
if (client.res.destroyed) {
|
|
1880
|
+
clients.delete(client);
|
|
1881
|
+
continue;
|
|
1642
1882
|
}
|
|
1883
|
+
try {
|
|
1884
|
+
client.res.write(frame);
|
|
1885
|
+
} catch {
|
|
1886
|
+
clients.delete(client);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
const bus = registry.get("event-bus");
|
|
1891
|
+
bus.on("request:completed", (r) => broadcast(null, JSON.stringify(r)));
|
|
1892
|
+
bus.on("telemetry:fetch", (e) => broadcast(SSE_EVENT_FETCH, JSON.stringify(e)));
|
|
1893
|
+
bus.on("telemetry:log", (e) => broadcast(SSE_EVENT_LOG, JSON.stringify(e)));
|
|
1894
|
+
bus.on("telemetry:error", (e) => broadcast(SSE_EVENT_ERROR, JSON.stringify(e)));
|
|
1895
|
+
bus.on("telemetry:query", (e) => broadcast(SSE_EVENT_QUERY, JSON.stringify(e)));
|
|
1896
|
+
bus.on("analysis:updated", ({ statefulInsights, statefulFindings }) => {
|
|
1897
|
+
broadcast(SSE_EVENT_INSIGHTS, JSON.stringify(statefulInsights));
|
|
1898
|
+
broadcast(SSE_EVENT_SECURITY, JSON.stringify(statefulFindings));
|
|
1899
|
+
});
|
|
1900
|
+
bus.on("findings:changed", (findings) => {
|
|
1901
|
+
broadcast(SSE_EVENT_SECURITY, JSON.stringify(findings));
|
|
1902
|
+
});
|
|
1903
|
+
return (req, res) => {
|
|
1904
|
+
const headers2 = {
|
|
1905
|
+
"content-type": "text/event-stream",
|
|
1906
|
+
"cache-control": "no-cache",
|
|
1907
|
+
connection: "keep-alive"
|
|
1643
1908
|
};
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
subs.add(bus.on("telemetry:query", (e) => writeEvent("query", JSON.stringify(e))));
|
|
1651
|
-
subs.add(bus.on("analysis:updated", ({ statefulInsights, statefulFindings }) => {
|
|
1652
|
-
writeEvent("insights", JSON.stringify(statefulInsights));
|
|
1653
|
-
writeEvent("security", JSON.stringify(statefulFindings));
|
|
1654
|
-
}));
|
|
1909
|
+
const corsOrigin = getCorsOrigin(req);
|
|
1910
|
+
if (corsOrigin) {
|
|
1911
|
+
headers2["access-control-allow-origin"] = corsOrigin;
|
|
1912
|
+
}
|
|
1913
|
+
res.writeHead(HTTP_OK, headers2);
|
|
1914
|
+
res.write(":ok\n\n");
|
|
1655
1915
|
const heartbeat = setInterval(() => {
|
|
1656
1916
|
if (res.destroyed) {
|
|
1657
1917
|
clearInterval(heartbeat);
|
|
1918
|
+
clients.delete(client);
|
|
1658
1919
|
return;
|
|
1659
1920
|
}
|
|
1660
|
-
|
|
1921
|
+
try {
|
|
1922
|
+
res.write(":heartbeat\n\n");
|
|
1923
|
+
} catch {
|
|
1924
|
+
clearInterval(heartbeat);
|
|
1925
|
+
clients.delete(client);
|
|
1926
|
+
}
|
|
1661
1927
|
}, SSE_HEARTBEAT_INTERVAL_MS);
|
|
1928
|
+
heartbeat.unref();
|
|
1929
|
+
const client = { res, heartbeat };
|
|
1930
|
+
clients.add(client);
|
|
1662
1931
|
req.on("close", () => {
|
|
1663
1932
|
clearInterval(heartbeat);
|
|
1664
|
-
|
|
1933
|
+
clients.delete(client);
|
|
1665
1934
|
});
|
|
1666
1935
|
};
|
|
1667
1936
|
}
|
|
1668
1937
|
var init_sse = __esm({
|
|
1669
1938
|
"src/dashboard/sse.ts"() {
|
|
1670
1939
|
"use strict";
|
|
1671
|
-
init_disposable();
|
|
1672
1940
|
init_constants();
|
|
1941
|
+
init_http();
|
|
1942
|
+
init_events();
|
|
1943
|
+
init_shared2();
|
|
1673
1944
|
}
|
|
1674
1945
|
});
|
|
1675
1946
|
|
|
@@ -2188,6 +2459,13 @@ function getSecurityStyles() {
|
|
|
2188
2459
|
.sec-item-resolved{color:var(--text-muted)}
|
|
2189
2460
|
.sec-item-resolved .sec-item-desc{text-decoration:line-through;text-decoration-color:var(--text-muted)}
|
|
2190
2461
|
.sec-resolved-item-icon{color:var(--green);font-size:12px;flex-shrink:0;margin-right:8px}
|
|
2462
|
+
|
|
2463
|
+
/* AI status badges */
|
|
2464
|
+
.sec-ai-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:8px;margin-left:8px;white-space:nowrap}
|
|
2465
|
+
.sec-ai-fixing{background:rgba(217,119,6,.1);color:var(--amber)}
|
|
2466
|
+
.sec-ai-wontfix{background:rgba(107,114,128,.1);color:var(--text-muted)}
|
|
2467
|
+
.sec-ai-verified{background:rgba(22,163,74,.1);color:var(--green)}
|
|
2468
|
+
.sec-ai-notes{font-size:11px;color:var(--text-muted);font-style:italic;margin-top:2px;padding-left:0}
|
|
2191
2469
|
`;
|
|
2192
2470
|
}
|
|
2193
2471
|
var init_security = __esm({
|
|
@@ -2251,9 +2529,17 @@ var init_styles = __esm({
|
|
|
2251
2529
|
});
|
|
2252
2530
|
|
|
2253
2531
|
// src/utils/fs.ts
|
|
2254
|
-
import { access } from "fs/promises";
|
|
2532
|
+
import { access, readFile, writeFile } from "fs/promises";
|
|
2255
2533
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2256
2534
|
import { resolve } from "path";
|
|
2535
|
+
async function fileExists(path) {
|
|
2536
|
+
try {
|
|
2537
|
+
await access(path);
|
|
2538
|
+
return true;
|
|
2539
|
+
} catch {
|
|
2540
|
+
return false;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2257
2543
|
function ensureGitignore(dir, entry) {
|
|
2258
2544
|
try {
|
|
2259
2545
|
const gitignorePath = resolve(dir, "../.gitignore");
|
|
@@ -2267,28 +2553,22 @@ function ensureGitignore(dir, entry) {
|
|
|
2267
2553
|
} catch {
|
|
2268
2554
|
}
|
|
2269
2555
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
}
|
|
2281
|
-
function brakitDebug(message) {
|
|
2282
|
-
if (process.env.DEBUG_BRAKIT) {
|
|
2283
|
-
process.stderr.write(`${PREFIX}:debug ${message}
|
|
2284
|
-
`);
|
|
2556
|
+
async function ensureGitignoreAsync(dir, entry) {
|
|
2557
|
+
try {
|
|
2558
|
+
const gitignorePath = resolve(dir, "../.gitignore");
|
|
2559
|
+
if (await fileExists(gitignorePath)) {
|
|
2560
|
+
const content = await readFile(gitignorePath, "utf-8");
|
|
2561
|
+
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
2562
|
+
await writeFile(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
|
|
2563
|
+
} else {
|
|
2564
|
+
await writeFile(gitignorePath, entry + "\n");
|
|
2565
|
+
}
|
|
2566
|
+
} catch {
|
|
2285
2567
|
}
|
|
2286
2568
|
}
|
|
2287
|
-
var
|
|
2288
|
-
|
|
2289
|
-
"src/utils/log.ts"() {
|
|
2569
|
+
var init_fs = __esm({
|
|
2570
|
+
"src/utils/fs.ts"() {
|
|
2290
2571
|
"use strict";
|
|
2291
|
-
PREFIX = "[brakit]";
|
|
2292
2572
|
}
|
|
2293
2573
|
});
|
|
2294
2574
|
|
|
@@ -2306,6 +2586,7 @@ var init_atomic_writer = __esm({
|
|
|
2306
2586
|
"use strict";
|
|
2307
2587
|
init_fs();
|
|
2308
2588
|
init_log();
|
|
2589
|
+
init_type_guards();
|
|
2309
2590
|
AtomicWriter = class {
|
|
2310
2591
|
constructor(opts) {
|
|
2311
2592
|
this.opts = opts;
|
|
@@ -2320,7 +2601,7 @@ var init_atomic_writer = __esm({
|
|
|
2320
2601
|
writeFileSync2(this.tmpPath, content);
|
|
2321
2602
|
renameSync(this.tmpPath, this.opts.filePath);
|
|
2322
2603
|
} catch (err) {
|
|
2323
|
-
brakitWarn(`failed to save ${this.opts.label}: ${err
|
|
2604
|
+
brakitWarn(`failed to save ${this.opts.label}: ${getErrorMessage(err)}`);
|
|
2324
2605
|
}
|
|
2325
2606
|
}
|
|
2326
2607
|
async writeAsync(content) {
|
|
@@ -2334,13 +2615,14 @@ var init_atomic_writer = __esm({
|
|
|
2334
2615
|
await writeFile2(this.tmpPath, content);
|
|
2335
2616
|
await rename(this.tmpPath, this.opts.filePath);
|
|
2336
2617
|
} catch (err) {
|
|
2337
|
-
brakitWarn(`failed to save ${this.opts.label}: ${err
|
|
2618
|
+
brakitWarn(`failed to save ${this.opts.label}: ${getErrorMessage(err)}`);
|
|
2338
2619
|
} finally {
|
|
2339
2620
|
this.writing = false;
|
|
2340
2621
|
if (this.pendingContent !== null) {
|
|
2341
2622
|
const next = this.pendingContent;
|
|
2342
2623
|
this.pendingContent = null;
|
|
2343
|
-
this.writeAsync(next)
|
|
2624
|
+
this.writeAsync(next).catch(() => {
|
|
2625
|
+
});
|
|
2344
2626
|
}
|
|
2345
2627
|
}
|
|
2346
2628
|
}
|
|
@@ -2353,10 +2635,10 @@ var init_atomic_writer = __esm({
|
|
|
2353
2635
|
}
|
|
2354
2636
|
}
|
|
2355
2637
|
async ensureDirAsync() {
|
|
2356
|
-
if (!
|
|
2638
|
+
if (!await fileExists(this.opts.dir)) {
|
|
2357
2639
|
await mkdir(this.opts.dir, { recursive: true });
|
|
2358
2640
|
if (this.opts.gitignoreEntry) {
|
|
2359
|
-
|
|
2641
|
+
await ensureGitignoreAsync(this.opts.dir, this.opts.gitignoreEntry);
|
|
2360
2642
|
}
|
|
2361
2643
|
}
|
|
2362
2644
|
}
|
|
@@ -2368,23 +2650,32 @@ var init_atomic_writer = __esm({
|
|
|
2368
2650
|
import { createHash } from "crypto";
|
|
2369
2651
|
function computeFindingId(finding) {
|
|
2370
2652
|
const key = `${finding.rule}:${finding.endpoint}:${finding.desc}`;
|
|
2371
|
-
return createHash("sha256").update(key).digest("hex").slice(0,
|
|
2653
|
+
return createHash("sha256").update(key).digest("hex").slice(0, FINDING_ID_HASH_LENGTH);
|
|
2654
|
+
}
|
|
2655
|
+
function computeInsightId(type, endpoint, desc) {
|
|
2656
|
+
const key = `${type}:${endpoint}:${desc}`;
|
|
2657
|
+
return createHash("sha256").update(key).digest("hex").slice(0, FINDING_ID_HASH_LENGTH);
|
|
2372
2658
|
}
|
|
2373
2659
|
var init_finding_id = __esm({
|
|
2374
2660
|
"src/store/finding-id.ts"() {
|
|
2375
2661
|
"use strict";
|
|
2662
|
+
init_limits();
|
|
2376
2663
|
}
|
|
2377
2664
|
});
|
|
2378
2665
|
|
|
2379
2666
|
// src/store/finding-store.ts
|
|
2667
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2380
2668
|
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
2381
2669
|
import { resolve as resolve2 } from "path";
|
|
2382
2670
|
var FindingStore;
|
|
2383
2671
|
var init_finding_store = __esm({
|
|
2384
2672
|
"src/store/finding-store.ts"() {
|
|
2385
2673
|
"use strict";
|
|
2674
|
+
init_fs();
|
|
2386
2675
|
init_constants();
|
|
2676
|
+
init_limits();
|
|
2387
2677
|
init_atomic_writer();
|
|
2678
|
+
init_log();
|
|
2388
2679
|
init_finding_id();
|
|
2389
2680
|
FindingStore = class {
|
|
2390
2681
|
constructor(rootDir) {
|
|
@@ -2397,7 +2688,6 @@ var init_finding_store = __esm({
|
|
|
2397
2688
|
gitignoreEntry: METRICS_DIR,
|
|
2398
2689
|
label: "findings"
|
|
2399
2690
|
});
|
|
2400
|
-
this.load();
|
|
2401
2691
|
}
|
|
2402
2692
|
findings = /* @__PURE__ */ new Map();
|
|
2403
2693
|
flushTimer = null;
|
|
@@ -2405,6 +2695,8 @@ var init_finding_store = __esm({
|
|
|
2405
2695
|
writer;
|
|
2406
2696
|
findingsPath;
|
|
2407
2697
|
start() {
|
|
2698
|
+
this.loadAsync().catch(() => {
|
|
2699
|
+
});
|
|
2408
2700
|
this.flushTimer = setInterval(
|
|
2409
2701
|
() => this.flush(),
|
|
2410
2702
|
FINDINGS_FLUSH_INTERVAL_MS
|
|
@@ -2441,7 +2733,9 @@ var init_finding_store = __esm({
|
|
|
2441
2733
|
firstSeenAt: now,
|
|
2442
2734
|
lastSeenAt: now,
|
|
2443
2735
|
resolvedAt: null,
|
|
2444
|
-
occurrences: 1
|
|
2736
|
+
occurrences: 1,
|
|
2737
|
+
aiStatus: null,
|
|
2738
|
+
aiNotes: null
|
|
2445
2739
|
};
|
|
2446
2740
|
this.findings.set(id, stateful);
|
|
2447
2741
|
this.dirty = true;
|
|
@@ -2457,6 +2751,17 @@ var init_finding_store = __esm({
|
|
|
2457
2751
|
this.dirty = true;
|
|
2458
2752
|
return true;
|
|
2459
2753
|
}
|
|
2754
|
+
reportFix(findingId, status, notes) {
|
|
2755
|
+
const finding = this.findings.get(findingId);
|
|
2756
|
+
if (!finding) return false;
|
|
2757
|
+
finding.aiStatus = status;
|
|
2758
|
+
finding.aiNotes = notes;
|
|
2759
|
+
if (status === "fixed") {
|
|
2760
|
+
finding.state = "fixing";
|
|
2761
|
+
}
|
|
2762
|
+
this.dirty = true;
|
|
2763
|
+
return true;
|
|
2764
|
+
}
|
|
2460
2765
|
/**
|
|
2461
2766
|
* Reconcile passive findings against the current analysis results.
|
|
2462
2767
|
*
|
|
@@ -2469,7 +2774,7 @@ var init_finding_store = __esm({
|
|
|
2469
2774
|
reconcilePassive(currentFindings) {
|
|
2470
2775
|
const currentIds = new Set(currentFindings.map(computeFindingId));
|
|
2471
2776
|
for (const [id, stateful] of this.findings) {
|
|
2472
|
-
if (stateful.source === "passive" && stateful.state === "open" && !currentIds.has(id)) {
|
|
2777
|
+
if (stateful.source === "passive" && (stateful.state === "open" || stateful.state === "fixing") && !currentIds.has(id)) {
|
|
2473
2778
|
stateful.state = "resolved";
|
|
2474
2779
|
stateful.resolvedAt = Date.now();
|
|
2475
2780
|
this.dirty = true;
|
|
@@ -2489,18 +2794,35 @@ var init_finding_store = __esm({
|
|
|
2489
2794
|
this.findings.clear();
|
|
2490
2795
|
this.dirty = true;
|
|
2491
2796
|
}
|
|
2492
|
-
|
|
2797
|
+
async loadAsync() {
|
|
2798
|
+
try {
|
|
2799
|
+
if (await fileExists(this.findingsPath)) {
|
|
2800
|
+
const raw = await readFile2(this.findingsPath, "utf-8");
|
|
2801
|
+
const parsed = JSON.parse(raw);
|
|
2802
|
+
if (parsed?.version === FINDINGS_DATA_VERSION && Array.isArray(parsed.findings)) {
|
|
2803
|
+
for (const f of parsed.findings) {
|
|
2804
|
+
this.findings.set(f.findingId, f);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
} catch (err) {
|
|
2809
|
+
brakitDebug(`FindingStore: could not load findings file, starting fresh: ${err}`);
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
/** Sync load for tests only — not used in production paths. */
|
|
2813
|
+
loadSync() {
|
|
2493
2814
|
try {
|
|
2494
2815
|
if (existsSync3(this.findingsPath)) {
|
|
2495
2816
|
const raw = readFileSync2(this.findingsPath, "utf-8");
|
|
2496
2817
|
const parsed = JSON.parse(raw);
|
|
2497
|
-
if (parsed?.version ===
|
|
2818
|
+
if (parsed?.version === FINDINGS_DATA_VERSION && Array.isArray(parsed.findings)) {
|
|
2498
2819
|
for (const f of parsed.findings) {
|
|
2499
2820
|
this.findings.set(f.findingId, f);
|
|
2500
2821
|
}
|
|
2501
2822
|
}
|
|
2502
2823
|
}
|
|
2503
|
-
} catch {
|
|
2824
|
+
} catch (err) {
|
|
2825
|
+
brakitDebug(`FindingStore: could not load findings file, starting fresh: ${err}`);
|
|
2504
2826
|
}
|
|
2505
2827
|
}
|
|
2506
2828
|
flush() {
|
|
@@ -2515,7 +2837,7 @@ var init_finding_store = __esm({
|
|
|
2515
2837
|
}
|
|
2516
2838
|
serialize() {
|
|
2517
2839
|
const data = {
|
|
2518
|
-
version:
|
|
2840
|
+
version: FINDINGS_DATA_VERSION,
|
|
2519
2841
|
findings: [...this.findings.values()]
|
|
2520
2842
|
};
|
|
2521
2843
|
return JSON.stringify(data);
|
|
@@ -2525,12 +2847,34 @@ var init_finding_store = __esm({
|
|
|
2525
2847
|
});
|
|
2526
2848
|
|
|
2527
2849
|
// src/detect/project.ts
|
|
2528
|
-
import { readFile as
|
|
2529
|
-
import {
|
|
2850
|
+
import { readFile as readFile3, readdir } from "fs/promises";
|
|
2851
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2852
|
+
import { join, relative } from "path";
|
|
2853
|
+
function detectFrameworkFromDeps(allDeps) {
|
|
2854
|
+
for (const f of FRAMEWORKS) {
|
|
2855
|
+
if (allDeps[f.dep]) return f.name;
|
|
2856
|
+
}
|
|
2857
|
+
return "unknown";
|
|
2858
|
+
}
|
|
2859
|
+
function detectPackageManagerSync(rootDir) {
|
|
2860
|
+
if (existsSync4(join(rootDir, "bun.lockb")) || existsSync4(join(rootDir, "bun.lock"))) return "bun";
|
|
2861
|
+
if (existsSync4(join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2862
|
+
if (existsSync4(join(rootDir, "yarn.lock"))) return "yarn";
|
|
2863
|
+
if (existsSync4(join(rootDir, "package-lock.json"))) return "npm";
|
|
2864
|
+
return "unknown";
|
|
2865
|
+
}
|
|
2866
|
+
var FRAMEWORKS;
|
|
2530
2867
|
var init_project = __esm({
|
|
2531
2868
|
"src/detect/project.ts"() {
|
|
2532
2869
|
"use strict";
|
|
2533
2870
|
init_fs();
|
|
2871
|
+
FRAMEWORKS = [
|
|
2872
|
+
{ name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
|
|
2873
|
+
{ name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
|
|
2874
|
+
{ name: "nuxt", dep: "nuxt", devCmd: "nuxt dev", bin: "nuxt", defaultPort: 3e3, devArgs: ["dev", "--port"] },
|
|
2875
|
+
{ name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] },
|
|
2876
|
+
{ name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] }
|
|
2877
|
+
];
|
|
2534
2878
|
}
|
|
2535
2879
|
});
|
|
2536
2880
|
|
|
@@ -2542,11 +2886,11 @@ var init_patterns = __esm({
|
|
|
2542
2886
|
SECRET_KEYS = /^(password|passwd|secret|api_key|apiKey|api_secret|apiSecret|private_key|privateKey|client_secret|clientSecret)$/;
|
|
2543
2887
|
TOKEN_PARAMS = /^(token|api_key|apiKey|secret|password|access_token|session_id|sessionId)$/;
|
|
2544
2888
|
SAFE_PARAMS = /^(_rsc|__clerk_handshake|__clerk_db_jwt|callback|code|state|nonce|redirect_uri|utm_|fbclid|gclid)$/;
|
|
2545
|
-
STACK_TRACE_RE = /at\s+.+\(.+:\d+:\d+\)|at\s+Module\._compile|at\s+Object\.<anonymous>|at\s+processTicksAndRejections
|
|
2889
|
+
STACK_TRACE_RE = /at\s+.+\(.+:\d+:\d+\)|at\s+Module\._compile|at\s+Object\.<anonymous>|at\s+processTicksAndRejections|Traceback \(most recent call last\)|File ".+", line \d+/;
|
|
2546
2890
|
DB_CONN_RE = /(postgres|mysql|mongodb|redis):\/\//;
|
|
2547
2891
|
SQL_FRAGMENT_RE = /\b(SELECT\s+[\w.*]+\s+FROM|INSERT\s+INTO|UPDATE\s+\w+\s+SET|DELETE\s+FROM)\b/i;
|
|
2548
|
-
SECRET_VAL_RE = /(api_key|apiKey|secret|token)\s*[:=]\s*["']?[A-Za-z0-9_
|
|
2549
|
-
LOG_SECRET_RE = /(password|secret|token|api_key|apiKey)\s*[:=]\s*["']?[A-Za-z0-9_
|
|
2892
|
+
SECRET_VAL_RE = /(api_key|apiKey|secret|token)\s*[:=]\s*["']?[A-Za-z0-9_\-.+/]{8,}/;
|
|
2893
|
+
LOG_SECRET_RE = /(password|secret|token|api_key|apiKey)\s*[:=]\s*["']?[A-Za-z0-9_\-.+/]{8,}/i;
|
|
2550
2894
|
MASKED_RE = /^\*+$|\[REDACTED\]|\[FILTERED\]|CHANGE_ME|^x{3,}$/i;
|
|
2551
2895
|
EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
|
|
2552
2896
|
INTERNAL_ID_KEYS = /^(id|_id|userId|user_id|createdBy|updatedBy|organizationId|org_id|tenantId|tenant_id)$/;
|
|
@@ -2558,9 +2902,9 @@ var init_patterns = __esm({
|
|
|
2558
2902
|
"token-in-url": "Pass tokens in the Authorization header, not URL query parameters.",
|
|
2559
2903
|
"stack-trace-leak": "Use a custom error handler that returns generic messages in production.",
|
|
2560
2904
|
"error-info-leak": "Sanitize error responses. Return generic messages instead of internal details.",
|
|
2905
|
+
"insecure-cookie": "Set HttpOnly and SameSite flags on all cookies.",
|
|
2561
2906
|
"sensitive-logs": "Redact PII before logging. Never log passwords or tokens.",
|
|
2562
2907
|
"cors-credentials": "Cannot use credentials:true with origin:*. Specify explicit origins.",
|
|
2563
|
-
"insecure-cookie": "Set HttpOnly and SameSite flags on all cookies.",
|
|
2564
2908
|
"response-pii-leak": "API responses should return minimal data. Don't echo back full user records \u2014 select only the fields the client needs."
|
|
2565
2909
|
};
|
|
2566
2910
|
}
|
|
@@ -2984,48 +3328,47 @@ function hasInternalIds(obj) {
|
|
|
2984
3328
|
}
|
|
2985
3329
|
return false;
|
|
2986
3330
|
}
|
|
2987
|
-
function
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
return { reason: "echo", emailCount: echoed.length };
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3331
|
+
function detectEchoPII(method, reqBody, target) {
|
|
3332
|
+
if (!WRITE_METHODS.has(method) || !reqBody || typeof reqBody !== "object") return null;
|
|
3333
|
+
const reqEmails = findEmails(reqBody);
|
|
3334
|
+
if (reqEmails.length === 0) return null;
|
|
3335
|
+
const resEmails = findEmails(target);
|
|
3336
|
+
const echoed = reqEmails.filter((e) => resEmails.includes(e));
|
|
3337
|
+
if (echoed.length === 0) return null;
|
|
3338
|
+
const inspectObj = Array.isArray(target) && target.length > 0 ? target[0] : target;
|
|
3339
|
+
if (hasInternalIds(inspectObj) || topLevelFieldCount(inspectObj) >= FULL_RECORD_MIN_FIELDS) {
|
|
3340
|
+
return { reason: "echo", emailCount: echoed.length };
|
|
3001
3341
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3342
|
+
return null;
|
|
3343
|
+
}
|
|
3344
|
+
function detectFullRecordPII(target) {
|
|
3345
|
+
if (!target || typeof target !== "object" || Array.isArray(target)) return null;
|
|
3346
|
+
const fields = topLevelFieldCount(target);
|
|
3347
|
+
if (fields < FULL_RECORD_MIN_FIELDS || !hasInternalIds(target)) return null;
|
|
3348
|
+
const emails = findEmails(target);
|
|
3349
|
+
if (emails.length === 0) return null;
|
|
3350
|
+
return { reason: "full-record", emailCount: emails.length };
|
|
3351
|
+
}
|
|
3352
|
+
function detectListPII(target) {
|
|
3353
|
+
if (!Array.isArray(target) || target.length < LIST_PII_MIN_ITEMS) return null;
|
|
3354
|
+
let itemsWithEmail = 0;
|
|
3355
|
+
for (let i = 0; i < Math.min(target.length, 10); i++) {
|
|
3356
|
+
const item = target[i];
|
|
3357
|
+
if (item && typeof item === "object" && findEmails(item).length > 0) {
|
|
3358
|
+
itemsWithEmail++;
|
|
3009
3359
|
}
|
|
3010
3360
|
}
|
|
3011
|
-
if (
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
if (item && typeof item === "object") {
|
|
3016
|
-
const emails = findEmails(item);
|
|
3017
|
-
if (emails.length > 0) itemsWithEmail++;
|
|
3018
|
-
}
|
|
3019
|
-
}
|
|
3020
|
-
if (itemsWithEmail >= LIST_PII_MIN_ITEMS) {
|
|
3021
|
-
const first = target[0];
|
|
3022
|
-
if (hasInternalIds(first) || topLevelFieldCount(first) >= FULL_RECORD_MIN_FIELDS) {
|
|
3023
|
-
return { reason: "list-pii", emailCount: itemsWithEmail };
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3361
|
+
if (itemsWithEmail < LIST_PII_MIN_ITEMS) return null;
|
|
3362
|
+
const first = target[0];
|
|
3363
|
+
if (hasInternalIds(first) || topLevelFieldCount(first) >= FULL_RECORD_MIN_FIELDS) {
|
|
3364
|
+
return { reason: "list-pii", emailCount: itemsWithEmail };
|
|
3026
3365
|
}
|
|
3027
3366
|
return null;
|
|
3028
3367
|
}
|
|
3368
|
+
function detectPII(method, reqBody, resBody) {
|
|
3369
|
+
const target = unwrapResponse(resBody);
|
|
3370
|
+
return detectEchoPII(method, reqBody, target) ?? detectFullRecordPII(target) ?? detectListPII(target);
|
|
3371
|
+
}
|
|
3029
3372
|
var WRITE_METHODS, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, REASON_LABELS, responsePiiLeakRule;
|
|
3030
3373
|
var init_response_pii_leak = __esm({
|
|
3031
3374
|
"src/analysis/rules/response-pii-leak.ts"() {
|
|
@@ -3143,6 +3486,24 @@ var init_rules = __esm({
|
|
|
3143
3486
|
}
|
|
3144
3487
|
});
|
|
3145
3488
|
|
|
3489
|
+
// src/core/disposable.ts
|
|
3490
|
+
var SubscriptionBag;
|
|
3491
|
+
var init_disposable = __esm({
|
|
3492
|
+
"src/core/disposable.ts"() {
|
|
3493
|
+
"use strict";
|
|
3494
|
+
SubscriptionBag = class {
|
|
3495
|
+
items = [];
|
|
3496
|
+
add(teardown) {
|
|
3497
|
+
this.items.push(typeof teardown === "function" ? { dispose: teardown } : teardown);
|
|
3498
|
+
}
|
|
3499
|
+
dispose() {
|
|
3500
|
+
for (const d of this.items) d.dispose();
|
|
3501
|
+
this.items.length = 0;
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
});
|
|
3506
|
+
|
|
3146
3507
|
// src/utils/collections.ts
|
|
3147
3508
|
function groupBy(items, keyFn) {
|
|
3148
3509
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -3984,14 +4345,19 @@ function computeInsightKey(insight) {
|
|
|
3984
4345
|
const identifier = extractEndpointFromDesc(insight.desc) ?? insight.title;
|
|
3985
4346
|
return `${insight.type}:${identifier}`;
|
|
3986
4347
|
}
|
|
4348
|
+
function enrichedIdFromInsight(insight) {
|
|
4349
|
+
return computeInsightId(insight.type, insight.nav ?? "global", insight.desc);
|
|
4350
|
+
}
|
|
3987
4351
|
var InsightTracker;
|
|
3988
4352
|
var init_insight_tracker = __esm({
|
|
3989
4353
|
"src/analysis/insight-tracker.ts"() {
|
|
3990
4354
|
"use strict";
|
|
3991
4355
|
init_endpoint();
|
|
4356
|
+
init_finding_id();
|
|
3992
4357
|
init_thresholds();
|
|
3993
4358
|
InsightTracker = class {
|
|
3994
4359
|
tracked = /* @__PURE__ */ new Map();
|
|
4360
|
+
enrichedIndex = /* @__PURE__ */ new Map();
|
|
3995
4361
|
reconcile(current) {
|
|
3996
4362
|
const currentKeys = /* @__PURE__ */ new Set();
|
|
3997
4363
|
const now = Date.now();
|
|
@@ -3999,6 +4365,7 @@ var init_insight_tracker = __esm({
|
|
|
3999
4365
|
const key = computeInsightKey(insight);
|
|
4000
4366
|
currentKeys.add(key);
|
|
4001
4367
|
const existing = this.tracked.get(key);
|
|
4368
|
+
this.enrichedIndex.set(enrichedIdFromInsight(insight), key);
|
|
4002
4369
|
if (existing) {
|
|
4003
4370
|
existing.insight = insight;
|
|
4004
4371
|
existing.lastSeenAt = now;
|
|
@@ -4015,28 +4382,44 @@ var init_insight_tracker = __esm({
|
|
|
4015
4382
|
firstSeenAt: now,
|
|
4016
4383
|
lastSeenAt: now,
|
|
4017
4384
|
resolvedAt: null,
|
|
4018
|
-
consecutiveAbsences: 0
|
|
4385
|
+
consecutiveAbsences: 0,
|
|
4386
|
+
aiStatus: null,
|
|
4387
|
+
aiNotes: null
|
|
4019
4388
|
});
|
|
4020
4389
|
}
|
|
4021
4390
|
}
|
|
4022
|
-
for (const [
|
|
4023
|
-
if (stateful.state === "open" && !currentKeys.has(stateful.key)) {
|
|
4391
|
+
for (const [, stateful] of this.tracked) {
|
|
4392
|
+
if ((stateful.state === "open" || stateful.state === "fixing") && !currentKeys.has(stateful.key)) {
|
|
4024
4393
|
stateful.consecutiveAbsences++;
|
|
4025
4394
|
if (stateful.consecutiveAbsences >= RESOLVE_AFTER_ABSENCES) {
|
|
4026
4395
|
stateful.state = "resolved";
|
|
4027
4396
|
stateful.resolvedAt = now;
|
|
4028
4397
|
}
|
|
4029
4398
|
} else if (stateful.state === "resolved" && stateful.resolvedAt !== null && now - stateful.resolvedAt > RESOLVED_INSIGHT_TTL_MS) {
|
|
4030
|
-
this.tracked.delete(key);
|
|
4399
|
+
this.tracked.delete(stateful.key);
|
|
4400
|
+
this.enrichedIndex.delete(enrichedIdFromInsight(stateful.insight));
|
|
4031
4401
|
}
|
|
4032
4402
|
}
|
|
4033
4403
|
return [...this.tracked.values()];
|
|
4034
4404
|
}
|
|
4405
|
+
reportFix(enrichedId, status, notes) {
|
|
4406
|
+
const key = this.enrichedIndex.get(enrichedId);
|
|
4407
|
+
if (!key) return false;
|
|
4408
|
+
const stateful = this.tracked.get(key);
|
|
4409
|
+
if (!stateful) return false;
|
|
4410
|
+
stateful.aiStatus = status;
|
|
4411
|
+
stateful.aiNotes = notes;
|
|
4412
|
+
if (status === "fixed") {
|
|
4413
|
+
stateful.state = "fixing";
|
|
4414
|
+
}
|
|
4415
|
+
return true;
|
|
4416
|
+
}
|
|
4035
4417
|
getAll() {
|
|
4036
4418
|
return [...this.tracked.values()];
|
|
4037
4419
|
}
|
|
4038
4420
|
clear() {
|
|
4039
4421
|
this.tracked.clear();
|
|
4422
|
+
this.enrichedIndex.clear();
|
|
4040
4423
|
}
|
|
4041
4424
|
};
|
|
4042
4425
|
}
|
|
@@ -4047,13 +4430,14 @@ var AnalysisEngine;
|
|
|
4047
4430
|
var init_engine = __esm({
|
|
4048
4431
|
"src/analysis/engine.ts"() {
|
|
4049
4432
|
"use strict";
|
|
4433
|
+
init_limits();
|
|
4050
4434
|
init_disposable();
|
|
4051
4435
|
init_group();
|
|
4052
4436
|
init_rules();
|
|
4053
4437
|
init_insights3();
|
|
4054
4438
|
init_insight_tracker();
|
|
4055
4439
|
AnalysisEngine = class {
|
|
4056
|
-
constructor(registry, debounceMs =
|
|
4440
|
+
constructor(registry, debounceMs = ANALYSIS_DEBOUNCE_MS) {
|
|
4057
4441
|
this.registry = registry;
|
|
4058
4442
|
this.debounceMs = debounceMs;
|
|
4059
4443
|
this.scanner = createDefaultScanner();
|
|
@@ -4089,7 +4473,10 @@ var init_engine = __esm({
|
|
|
4089
4473
|
return this.registry.has("finding-store") ? this.registry.get("finding-store").getAll() : [];
|
|
4090
4474
|
}
|
|
4091
4475
|
getStatefulInsights() {
|
|
4092
|
-
return this.
|
|
4476
|
+
return this.insightTracker.getAll();
|
|
4477
|
+
}
|
|
4478
|
+
reportInsightFix(enrichedId, status, notes) {
|
|
4479
|
+
return this.insightTracker.reportFix(enrichedId, status, notes);
|
|
4093
4480
|
}
|
|
4094
4481
|
scheduleRecompute() {
|
|
4095
4482
|
if (this.debounceTimer) return;
|
|
@@ -4147,7 +4534,7 @@ var init_src = __esm({
|
|
|
4147
4534
|
init_engine();
|
|
4148
4535
|
init_insights3();
|
|
4149
4536
|
init_insights2();
|
|
4150
|
-
VERSION = "0.8.
|
|
4537
|
+
VERSION = "0.8.5";
|
|
4151
4538
|
}
|
|
4152
4539
|
});
|
|
4153
4540
|
|
|
@@ -4836,7 +5223,7 @@ function getFlowDetail() {
|
|
|
4836
5223
|
h += '<span>' + req.durationMs + 'ms</span>';
|
|
4837
5224
|
if (req.responseSize) h += '<span>' + formatSize(req.responseSize) + '</span>';
|
|
4838
5225
|
h += '</div>';
|
|
4839
|
-
h += '<div class="request-timeline tl-hidden" data-request-id="' + req.id + '" data-request-started="' + req.startedAt + '"></div>';
|
|
5226
|
+
h += '<div class="request-timeline tl-hidden" data-request-id="' + escHtml(req.id) + '" data-request-started="' + escHtml(String(req.startedAt)) + '"></div>';
|
|
4840
5227
|
h += '<div class="detail-grid">';
|
|
4841
5228
|
h += '<div class="detail-section"><h4>Request Headers</h4><pre>' + formatHeaders(req.headers) + '</pre></div>';
|
|
4842
5229
|
h += '<div class="detail-section"><h4>Response Headers</h4><pre>' + formatHeaders(req.responseHeaders) + '</pre></div>';
|
|
@@ -6094,7 +6481,7 @@ function getOverviewRender() {
|
|
|
6094
6481
|
container.appendChild(summary);
|
|
6095
6482
|
|
|
6096
6483
|
var all = state.insights || [];
|
|
6097
|
-
var open = all.filter(function(si) { return si.state === 'open'; });
|
|
6484
|
+
var open = all.filter(function(si) { return si.state === 'open' || si.state === 'fixing'; });
|
|
6098
6485
|
var resolved = all.filter(function(si) { return si.state === 'resolved'; });
|
|
6099
6486
|
|
|
6100
6487
|
if (open.length === 0 && resolved.length === 0) {
|
|
@@ -6139,10 +6526,17 @@ function getOverviewRender() {
|
|
|
6139
6526
|
if (insight.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(insight.hint) + '</div>';
|
|
6140
6527
|
expandHtml += '<span class="ov-card-link" data-nav="' + insight.nav + '">View in ' + (NAV_LABELS[insight.nav] || insight.nav) + ' \\u2192</span>';
|
|
6141
6528
|
|
|
6529
|
+
var aiBadge = '';
|
|
6530
|
+
if (si.state === 'fixing' && si.aiStatus === 'fixed') {
|
|
6531
|
+
aiBadge = '<span class="sec-ai-badge sec-ai-fixing">AI fixed \\u2014 awaiting verification</span>';
|
|
6532
|
+
} else if (si.aiStatus === 'wont_fix') {
|
|
6533
|
+
aiBadge = '<span class="sec-ai-badge sec-ai-wontfix">AI: won\\u2019t fix</span>';
|
|
6534
|
+
}
|
|
6535
|
+
|
|
6142
6536
|
card.innerHTML =
|
|
6143
6537
|
'<span class="ov-card-icon ' + iconCls + '">' + iconChar + '</span>' +
|
|
6144
6538
|
'<div class="ov-card-body">' +
|
|
6145
|
-
'<div class="ov-card-title">' + escHtml(insight.title) + '</div>' +
|
|
6539
|
+
'<div class="ov-card-title">' + escHtml(insight.title) + aiBadge + '</div>' +
|
|
6146
6540
|
'<div class="ov-card-desc">' + insight.desc + '</div>' +
|
|
6147
6541
|
'<div class="ov-card-expand">' + expandHtml + '</div>' +
|
|
6148
6542
|
'</div>' +
|
|
@@ -6234,7 +6628,26 @@ function getSecurityView() {
|
|
|
6234
6628
|
container.innerHTML = '';
|
|
6235
6629
|
var SEV = ${SEVERITY_MAP};
|
|
6236
6630
|
|
|
6237
|
-
var all = state.findings || [];
|
|
6631
|
+
var all = (state.findings || []).slice();
|
|
6632
|
+
var insightsList = state.insights || [];
|
|
6633
|
+
for (var ix = 0; ix < insightsList.length; ix++) {
|
|
6634
|
+
var si = insightsList[ix];
|
|
6635
|
+
all.push({
|
|
6636
|
+
findingId: si.key,
|
|
6637
|
+
state: si.state,
|
|
6638
|
+
aiStatus: si.aiStatus,
|
|
6639
|
+
aiNotes: si.aiNotes,
|
|
6640
|
+
finding: {
|
|
6641
|
+
severity: si.insight.severity,
|
|
6642
|
+
rule: 'insight-' + si.insight.type,
|
|
6643
|
+
title: si.insight.title,
|
|
6644
|
+
desc: si.insight.desc,
|
|
6645
|
+
hint: si.insight.hint,
|
|
6646
|
+
endpoint: si.insight.nav || 'global',
|
|
6647
|
+
count: 1
|
|
6648
|
+
}
|
|
6649
|
+
});
|
|
6650
|
+
}
|
|
6238
6651
|
var open = all.filter(function(f) { return f.state === 'open' || f.state === 'fixing'; });
|
|
6239
6652
|
var resolved = all.filter(function(f) { return f.state === 'resolved'; });
|
|
6240
6653
|
|
|
@@ -6282,12 +6695,13 @@ function getSecurityView() {
|
|
|
6282
6695
|
var groups = {};
|
|
6283
6696
|
var groupOrder = [];
|
|
6284
6697
|
for (var gi = 0; gi < open.length; gi++) {
|
|
6285
|
-
var
|
|
6698
|
+
var sf = open[gi];
|
|
6699
|
+
var f = sf.finding;
|
|
6286
6700
|
if (!groups[f.rule]) {
|
|
6287
6701
|
groups[f.rule] = { rule: f.rule, title: f.title, severity: f.severity, hint: f.hint, items: [] };
|
|
6288
6702
|
groupOrder.push(f.rule);
|
|
6289
6703
|
}
|
|
6290
|
-
groups[f.rule].items.push(
|
|
6704
|
+
groups[f.rule].items.push(sf);
|
|
6291
6705
|
}
|
|
6292
6706
|
|
|
6293
6707
|
groupOrder.sort(function(a, b) {
|
|
@@ -6324,12 +6738,21 @@ function getSecurityView() {
|
|
|
6324
6738
|
var list = document.createElement('div');
|
|
6325
6739
|
list.className = 'sec-items';
|
|
6326
6740
|
for (var ii = 0; ii < group.items.length; ii++) {
|
|
6327
|
-
var
|
|
6741
|
+
var sf2 = group.items[ii];
|
|
6742
|
+
var item = sf2.finding;
|
|
6328
6743
|
var row = document.createElement('div');
|
|
6329
6744
|
row.className = 'sec-item';
|
|
6745
|
+
var aiBadge = '';
|
|
6746
|
+
if (sf2.state === 'fixing' && sf2.aiStatus === 'fixed') {
|
|
6747
|
+
aiBadge = '<span class="sec-ai-badge sec-ai-fixing">AI fixed \\u2014 awaiting verification</span>';
|
|
6748
|
+
} else if (sf2.aiStatus === 'wont_fix') {
|
|
6749
|
+
aiBadge = '<span class="sec-ai-badge sec-ai-wontfix">AI: won\\u2019t fix</span>';
|
|
6750
|
+
}
|
|
6751
|
+
var aiNotes = sf2.aiNotes ? '<div class="sec-ai-notes">' + escHtml(sf2.aiNotes) + '</div>' : '';
|
|
6330
6752
|
row.innerHTML =
|
|
6331
6753
|
'<div class="sec-item-desc">' + escHtml(item.desc) + '</div>' +
|
|
6332
|
-
(item.count > 1 ? '<span class="sec-item-count">' + item.count + 'x</span>' : '')
|
|
6754
|
+
(item.count > 1 ? '<span class="sec-item-count">' + item.count + 'x</span>' : '') +
|
|
6755
|
+
aiBadge + aiNotes;
|
|
6333
6756
|
list.appendChild(row);
|
|
6334
6757
|
}
|
|
6335
6758
|
section.appendChild(list);
|
|
@@ -6348,12 +6771,16 @@ function getSecurityView() {
|
|
|
6348
6771
|
var resolvedItems = document.createElement('div');
|
|
6349
6772
|
resolvedItems.className = 'sec-items';
|
|
6350
6773
|
for (var ri = 0; ri < resolved.length; ri++) {
|
|
6351
|
-
var
|
|
6774
|
+
var rsf = resolved[ri];
|
|
6775
|
+
var rf = rsf.finding;
|
|
6352
6776
|
var rRow = document.createElement('div');
|
|
6353
6777
|
rRow.className = 'sec-item sec-item-resolved';
|
|
6778
|
+
var verifiedBadge = rsf.aiStatus === 'fixed' ? '<span class="sec-ai-badge sec-ai-verified">Verified fix</span>' : '';
|
|
6779
|
+
var rNotes = rsf.aiNotes ? '<div class="sec-ai-notes">' + escHtml(rsf.aiNotes) + '</div>' : '';
|
|
6354
6780
|
rRow.innerHTML =
|
|
6355
6781
|
'<span class="sec-resolved-item-icon">\\u2713</span>' +
|
|
6356
|
-
'<div class="sec-item-desc">' + escHtml(rf.title) + ' \\u2014 ' + escHtml(rf.endpoint) + '</div>'
|
|
6782
|
+
'<div class="sec-item-desc">' + escHtml(rf.title) + ' \\u2014 ' + escHtml(rf.endpoint) + '</div>' +
|
|
6783
|
+
verifiedBadge + rNotes;
|
|
6357
6784
|
resolvedItems.appendChild(rRow);
|
|
6358
6785
|
}
|
|
6359
6786
|
resolvedGroup.appendChild(resolvedItems);
|
|
@@ -6444,6 +6871,7 @@ function getApp() {
|
|
|
6444
6871
|
events.addEventListener('insights', function(e) {
|
|
6445
6872
|
state.insights = JSON.parse(e.data);
|
|
6446
6873
|
if (state.activeView === 'overview') renderOverview();
|
|
6874
|
+
if (state.activeView === 'security') renderSecurity();
|
|
6447
6875
|
updateStats();
|
|
6448
6876
|
});
|
|
6449
6877
|
|
|
@@ -6646,47 +7074,178 @@ var init_page = __esm({
|
|
|
6646
7074
|
// src/telemetry/config.ts
|
|
6647
7075
|
import { homedir } from "os";
|
|
6648
7076
|
import { join as join2 } from "path";
|
|
6649
|
-
import { existsSync as
|
|
6650
|
-
import { randomUUID as
|
|
7077
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
7078
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
6651
7079
|
function readConfig() {
|
|
6652
7080
|
try {
|
|
6653
|
-
if (!
|
|
7081
|
+
if (!existsSync5(CONFIG_PATH)) return null;
|
|
6654
7082
|
return JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
|
|
6655
7083
|
} catch {
|
|
6656
7084
|
return null;
|
|
6657
7085
|
}
|
|
6658
7086
|
}
|
|
7087
|
+
function writeConfig(config) {
|
|
7088
|
+
try {
|
|
7089
|
+
if (!existsSync5(CONFIG_DIR))
|
|
7090
|
+
mkdirSync3(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
7091
|
+
writeFileSync3(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
|
|
7092
|
+
mode: 384
|
|
7093
|
+
});
|
|
7094
|
+
} catch {
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
function getOrCreateConfig() {
|
|
7098
|
+
const existing = readConfig();
|
|
7099
|
+
if (existing && typeof existing.telemetry === "boolean" && existing.anonymousId) {
|
|
7100
|
+
return existing;
|
|
7101
|
+
}
|
|
7102
|
+
const config = { telemetry: true, anonymousId: randomUUID4() };
|
|
7103
|
+
writeConfig(config);
|
|
7104
|
+
return config;
|
|
7105
|
+
}
|
|
6659
7106
|
function isTelemetryEnabled() {
|
|
7107
|
+
if (cachedEnabled !== null) return cachedEnabled;
|
|
6660
7108
|
const env = process.env.BRAKIT_TELEMETRY;
|
|
6661
|
-
if (env !== void 0)
|
|
6662
|
-
|
|
7109
|
+
if (env !== void 0) {
|
|
7110
|
+
cachedEnabled = env !== "false" && env !== "0" && env !== "off";
|
|
7111
|
+
return cachedEnabled;
|
|
7112
|
+
}
|
|
7113
|
+
cachedEnabled = readConfig()?.telemetry ?? true;
|
|
7114
|
+
return cachedEnabled;
|
|
6663
7115
|
}
|
|
6664
|
-
var CONFIG_DIR, CONFIG_PATH;
|
|
7116
|
+
var CONFIG_DIR, CONFIG_PATH, cachedEnabled;
|
|
6665
7117
|
var init_config = __esm({
|
|
6666
7118
|
"src/telemetry/config.ts"() {
|
|
6667
7119
|
"use strict";
|
|
6668
7120
|
CONFIG_DIR = join2(homedir(), ".brakit");
|
|
6669
7121
|
CONFIG_PATH = join2(CONFIG_DIR, "config.json");
|
|
7122
|
+
cachedEnabled = null;
|
|
6670
7123
|
}
|
|
6671
7124
|
});
|
|
6672
7125
|
|
|
6673
7126
|
// src/telemetry/index.ts
|
|
6674
7127
|
import { platform, release, arch } from "os";
|
|
7128
|
+
import { spawn } from "child_process";
|
|
7129
|
+
function initSession(framework, packageManager, isCustomCommand, adapters) {
|
|
7130
|
+
session.startTime = Date.now();
|
|
7131
|
+
session.framework = framework;
|
|
7132
|
+
session.packageManager = packageManager;
|
|
7133
|
+
session.isCustomCommand = isCustomCommand;
|
|
7134
|
+
session.adapters = adapters;
|
|
7135
|
+
}
|
|
7136
|
+
function recordRequestCount(count) {
|
|
7137
|
+
session.requestCount = count;
|
|
7138
|
+
}
|
|
7139
|
+
function recordInsightTypes(types) {
|
|
7140
|
+
for (const t of types) session.insightTypes.add(t);
|
|
7141
|
+
}
|
|
7142
|
+
function recordRulesTriggered(rules) {
|
|
7143
|
+
for (const r of rules) session.rulesTriggered.add(r);
|
|
7144
|
+
}
|
|
6675
7145
|
function recordTabViewed(tab) {
|
|
6676
|
-
tabsViewed.add(tab);
|
|
7146
|
+
session.tabsViewed.add(tab);
|
|
6677
7147
|
}
|
|
6678
7148
|
function recordDashboardOpened() {
|
|
6679
|
-
dashboardOpened = true;
|
|
7149
|
+
session.dashboardOpened = true;
|
|
7150
|
+
}
|
|
7151
|
+
function speedBucket(ms) {
|
|
7152
|
+
if (ms === 0) return "none";
|
|
7153
|
+
if (ms < 200) return "<200ms";
|
|
7154
|
+
if (ms < 500) return "200-500ms";
|
|
7155
|
+
if (ms < 1e3) return "500-1000ms";
|
|
7156
|
+
if (ms < 2e3) return "1000-2000ms";
|
|
7157
|
+
if (ms < 5e3) return "2000-5000ms";
|
|
7158
|
+
return ">5000ms";
|
|
7159
|
+
}
|
|
7160
|
+
function trackSession(registry) {
|
|
7161
|
+
if (!isTelemetryEnabled()) return;
|
|
7162
|
+
const isFirstSession = readConfig() === null;
|
|
7163
|
+
const config = getOrCreateConfig();
|
|
7164
|
+
const metricsStore = registry.get("metrics-store");
|
|
7165
|
+
const analysisEngine = registry.get("analysis-engine");
|
|
7166
|
+
const live = metricsStore.getLiveEndpoints();
|
|
7167
|
+
const insights = analysisEngine.getInsights();
|
|
7168
|
+
const findings = analysisEngine.getFindings();
|
|
7169
|
+
let totalRequests = 0;
|
|
7170
|
+
let totalDuration = 0;
|
|
7171
|
+
let slowestP95 = 0;
|
|
7172
|
+
for (const ep of live) {
|
|
7173
|
+
totalRequests += ep.summary.totalRequests;
|
|
7174
|
+
totalDuration += ep.summary.p95Ms * ep.summary.totalRequests;
|
|
7175
|
+
if (ep.summary.p95Ms > slowestP95) slowestP95 = ep.summary.p95Ms;
|
|
7176
|
+
}
|
|
7177
|
+
const payload = {
|
|
7178
|
+
api_key: POSTHOG_KEY,
|
|
7179
|
+
event: "session",
|
|
7180
|
+
distinct_id: config.anonymousId,
|
|
7181
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7182
|
+
properties: {
|
|
7183
|
+
brakit_version: VERSION,
|
|
7184
|
+
node_version: process.version,
|
|
7185
|
+
os: `${platform()}-${release()}`,
|
|
7186
|
+
arch: arch(),
|
|
7187
|
+
framework: session.framework,
|
|
7188
|
+
package_manager: session.packageManager,
|
|
7189
|
+
is_custom_command: session.isCustomCommand,
|
|
7190
|
+
first_session: isFirstSession,
|
|
7191
|
+
adapters_detected: session.adapters,
|
|
7192
|
+
request_count: session.requestCount,
|
|
7193
|
+
error_count: registry.get("error-store").getAll().length,
|
|
7194
|
+
query_count: registry.get("query-store").getAll().length,
|
|
7195
|
+
fetch_count: registry.get("fetch-store").getAll().length,
|
|
7196
|
+
insight_count: insights.length,
|
|
7197
|
+
finding_count: findings.length,
|
|
7198
|
+
insight_types: [...session.insightTypes],
|
|
7199
|
+
rules_triggered: [...session.rulesTriggered],
|
|
7200
|
+
endpoint_count: live.length,
|
|
7201
|
+
avg_duration_ms: totalRequests > 0 ? Math.round(totalDuration / totalRequests) : 0,
|
|
7202
|
+
slowest_endpoint_bucket: speedBucket(slowestP95),
|
|
7203
|
+
tabs_viewed: [...session.tabsViewed],
|
|
7204
|
+
dashboard_opened: session.dashboardOpened,
|
|
7205
|
+
explain_used: session.explainUsed,
|
|
7206
|
+
session_duration_s: Math.round((Date.now() - session.startTime) / 1e3),
|
|
7207
|
+
$lib: "brakit",
|
|
7208
|
+
$process_person_profile: false,
|
|
7209
|
+
$geoip_disable: true
|
|
7210
|
+
}
|
|
7211
|
+
};
|
|
7212
|
+
try {
|
|
7213
|
+
const body = JSON.stringify(payload);
|
|
7214
|
+
const url = `${POSTHOG_HOST}${POSTHOG_CAPTURE_PATH}`;
|
|
7215
|
+
const child = spawn(
|
|
7216
|
+
process.execPath,
|
|
7217
|
+
[
|
|
7218
|
+
"-e",
|
|
7219
|
+
`fetch(${JSON.stringify(url)},{method:"POST",headers:{"content-type":"application/json"},body:${JSON.stringify(body)},signal:AbortSignal.timeout(${POSTHOG_REQUEST_TIMEOUT_MS})}).catch(()=>{})`
|
|
7220
|
+
],
|
|
7221
|
+
{ detached: true, stdio: "ignore" }
|
|
7222
|
+
);
|
|
7223
|
+
child.unref();
|
|
7224
|
+
} catch {
|
|
7225
|
+
}
|
|
6680
7226
|
}
|
|
6681
|
-
var
|
|
6682
|
-
var
|
|
7227
|
+
var POSTHOG_KEY, session;
|
|
7228
|
+
var init_telemetry2 = __esm({
|
|
6683
7229
|
"src/telemetry/index.ts"() {
|
|
6684
7230
|
"use strict";
|
|
6685
7231
|
init_src();
|
|
6686
7232
|
init_config();
|
|
7233
|
+
init_telemetry();
|
|
6687
7234
|
init_config();
|
|
6688
|
-
|
|
6689
|
-
|
|
7235
|
+
POSTHOG_KEY = "phc_E9TwydCGnSfPLIUhNxChpeg32TSowjk31KiPhnLPP0x";
|
|
7236
|
+
session = {
|
|
7237
|
+
startTime: 0,
|
|
7238
|
+
framework: "",
|
|
7239
|
+
packageManager: "",
|
|
7240
|
+
isCustomCommand: false,
|
|
7241
|
+
adapters: [],
|
|
7242
|
+
requestCount: 0,
|
|
7243
|
+
insightTypes: /* @__PURE__ */ new Set(),
|
|
7244
|
+
rulesTriggered: /* @__PURE__ */ new Set(),
|
|
7245
|
+
tabsViewed: /* @__PURE__ */ new Set(),
|
|
7246
|
+
dashboardOpened: false,
|
|
7247
|
+
explainUsed: false
|
|
7248
|
+
};
|
|
6690
7249
|
}
|
|
6691
7250
|
});
|
|
6692
7251
|
|
|
@@ -6716,7 +7275,13 @@ function createDashboardHandler(registry) {
|
|
|
6716
7275
|
routes[DASHBOARD_API_SECURITY] = createSecurityHandler(analysisEngine);
|
|
6717
7276
|
}
|
|
6718
7277
|
if (registry.has("finding-store")) {
|
|
6719
|
-
|
|
7278
|
+
const findingStore = registry.get("finding-store");
|
|
7279
|
+
routes[DASHBOARD_API_FINDINGS] = createFindingsHandler(findingStore);
|
|
7280
|
+
routes[DASHBOARD_API_FINDINGS_REPORT] = createFindingsReportHandler(
|
|
7281
|
+
findingStore,
|
|
7282
|
+
registry.get("event-bus"),
|
|
7283
|
+
analysisEngine
|
|
7284
|
+
);
|
|
6720
7285
|
}
|
|
6721
7286
|
routes[DASHBOARD_API_TAB] = (req, res) => {
|
|
6722
7287
|
const raw = (req.url ?? "").split("tab=")[1];
|
|
@@ -6724,7 +7289,7 @@ function createDashboardHandler(registry) {
|
|
|
6724
7289
|
const tab = decodeURIComponent(raw).slice(0, MAX_TAB_NAME_LENGTH);
|
|
6725
7290
|
if (VALID_TABS.has(tab) && isTelemetryEnabled()) recordTabViewed(tab);
|
|
6726
7291
|
}
|
|
6727
|
-
res.writeHead(
|
|
7292
|
+
res.writeHead(HTTP_NO_CONTENT);
|
|
6728
7293
|
res.end();
|
|
6729
7294
|
};
|
|
6730
7295
|
return (req, res, config) => {
|
|
@@ -6735,7 +7300,7 @@ function createDashboardHandler(registry) {
|
|
|
6735
7300
|
return;
|
|
6736
7301
|
}
|
|
6737
7302
|
if (isTelemetryEnabled()) recordDashboardOpened();
|
|
6738
|
-
res.writeHead(
|
|
7303
|
+
res.writeHead(HTTP_OK, {
|
|
6739
7304
|
"content-type": "text/html; charset=utf-8",
|
|
6740
7305
|
"cache-control": "no-cache",
|
|
6741
7306
|
...SECURITY_HEADERS
|
|
@@ -6743,23 +7308,17 @@ function createDashboardHandler(registry) {
|
|
|
6743
7308
|
res.end(getDashboardHtml(config));
|
|
6744
7309
|
};
|
|
6745
7310
|
}
|
|
6746
|
-
var SECURITY_HEADERS;
|
|
6747
7311
|
var init_router = __esm({
|
|
6748
7312
|
"src/dashboard/router.ts"() {
|
|
6749
7313
|
"use strict";
|
|
6750
7314
|
init_constants();
|
|
7315
|
+
init_http();
|
|
6751
7316
|
init_api();
|
|
6752
7317
|
init_insights();
|
|
6753
7318
|
init_findings();
|
|
6754
7319
|
init_sse();
|
|
6755
7320
|
init_page();
|
|
6756
|
-
|
|
6757
|
-
SECURITY_HEADERS = {
|
|
6758
|
-
"x-content-type-options": "nosniff",
|
|
6759
|
-
"x-frame-options": "DENY",
|
|
6760
|
-
"referrer-policy": "no-referrer",
|
|
6761
|
-
"content-security-policy": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src data:"
|
|
6762
|
-
};
|
|
7321
|
+
init_telemetry2();
|
|
6763
7322
|
}
|
|
6764
7323
|
});
|
|
6765
7324
|
|
|
@@ -6768,6 +7327,7 @@ var EventBus;
|
|
|
6768
7327
|
var init_event_bus = __esm({
|
|
6769
7328
|
"src/core/event-bus.ts"() {
|
|
6770
7329
|
"use strict";
|
|
7330
|
+
init_log();
|
|
6771
7331
|
EventBus = class {
|
|
6772
7332
|
listeners = /* @__PURE__ */ new Map();
|
|
6773
7333
|
emit(channel, data) {
|
|
@@ -6776,7 +7336,8 @@ var init_event_bus = __esm({
|
|
|
6776
7336
|
for (const fn of set) {
|
|
6777
7337
|
try {
|
|
6778
7338
|
fn(data);
|
|
6779
|
-
} catch {
|
|
7339
|
+
} catch (err) {
|
|
7340
|
+
brakitDebug(`EventBus listener threw on channel "${channel}": ${err}`);
|
|
6780
7341
|
}
|
|
6781
7342
|
}
|
|
6782
7343
|
}
|
|
@@ -6840,9 +7401,9 @@ var init_static_patterns = __esm({
|
|
|
6840
7401
|
});
|
|
6841
7402
|
|
|
6842
7403
|
// src/store/request-store.ts
|
|
6843
|
-
function flattenHeaders(
|
|
7404
|
+
function flattenHeaders(headers2) {
|
|
6844
7405
|
const flat = {};
|
|
6845
|
-
for (const [key, value] of Object.entries(
|
|
7406
|
+
for (const [key, value] of Object.entries(headers2)) {
|
|
6846
7407
|
if (value === void 0) continue;
|
|
6847
7408
|
flat[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
6848
7409
|
}
|
|
@@ -6898,6 +7459,15 @@ var init_request_store = __esm({
|
|
|
6898
7459
|
}
|
|
6899
7460
|
return entry;
|
|
6900
7461
|
}
|
|
7462
|
+
add(entry) {
|
|
7463
|
+
this.requests.push(entry);
|
|
7464
|
+
if (this.requests.length > this.maxEntries) {
|
|
7465
|
+
this.requests.shift();
|
|
7466
|
+
}
|
|
7467
|
+
for (const fn of this.listeners) {
|
|
7468
|
+
fn(entry);
|
|
7469
|
+
}
|
|
7470
|
+
}
|
|
6901
7471
|
getAll() {
|
|
6902
7472
|
return this.requests;
|
|
6903
7473
|
}
|
|
@@ -6916,7 +7486,7 @@ var init_request_store = __esm({
|
|
|
6916
7486
|
});
|
|
6917
7487
|
|
|
6918
7488
|
// src/store/telemetry-store.ts
|
|
6919
|
-
import { randomUUID as
|
|
7489
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
6920
7490
|
var TelemetryStore;
|
|
6921
7491
|
var init_telemetry_store = __esm({
|
|
6922
7492
|
"src/store/telemetry-store.ts"() {
|
|
@@ -6929,7 +7499,7 @@ var init_telemetry_store = __esm({
|
|
|
6929
7499
|
entries = [];
|
|
6930
7500
|
listeners = [];
|
|
6931
7501
|
add(data) {
|
|
6932
|
-
const entry = { id:
|
|
7502
|
+
const entry = { id: randomUUID5(), ...data };
|
|
6933
7503
|
this.entries.push(entry);
|
|
6934
7504
|
if (this.entries.length > this.maxEntries) this.entries.shift();
|
|
6935
7505
|
for (const fn of this.listeners) fn(entry);
|
|
@@ -7013,7 +7583,7 @@ var init_math = __esm({
|
|
|
7013
7583
|
});
|
|
7014
7584
|
|
|
7015
7585
|
// src/store/metrics/metrics-store.ts
|
|
7016
|
-
import { randomUUID as
|
|
7586
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
7017
7587
|
function createAccumulator() {
|
|
7018
7588
|
return {
|
|
7019
7589
|
durations: [],
|
|
@@ -7037,19 +7607,23 @@ var init_metrics_store = __esm({
|
|
|
7037
7607
|
MetricsStore = class {
|
|
7038
7608
|
constructor(persistence) {
|
|
7039
7609
|
this.persistence = persistence;
|
|
7040
|
-
this.data =
|
|
7041
|
-
for (const ep of this.data.endpoints) {
|
|
7042
|
-
this.endpointIndex.set(ep.endpoint, ep);
|
|
7043
|
-
}
|
|
7610
|
+
this.data = { version: 1, endpoints: [] };
|
|
7044
7611
|
}
|
|
7045
7612
|
data;
|
|
7046
7613
|
endpointIndex = /* @__PURE__ */ new Map();
|
|
7047
|
-
sessionId =
|
|
7614
|
+
sessionId = randomUUID6();
|
|
7048
7615
|
sessionStart = Date.now();
|
|
7049
7616
|
flushTimer = null;
|
|
7050
7617
|
accumulators = /* @__PURE__ */ new Map();
|
|
7051
7618
|
pendingPoints = /* @__PURE__ */ new Map();
|
|
7052
7619
|
start() {
|
|
7620
|
+
this.persistence.loadAsync().then((data) => {
|
|
7621
|
+
this.data = data;
|
|
7622
|
+
for (const ep of this.data.endpoints) {
|
|
7623
|
+
this.endpointIndex.set(ep.endpoint, ep);
|
|
7624
|
+
}
|
|
7625
|
+
}).catch(() => {
|
|
7626
|
+
});
|
|
7053
7627
|
this.flushTimer = setInterval(
|
|
7054
7628
|
() => this.flush(),
|
|
7055
7629
|
METRICS_FLUSH_INTERVAL_MS
|
|
@@ -7155,7 +7729,7 @@ var init_metrics_store = __esm({
|
|
|
7155
7729
|
for (const [endpoint, acc] of this.accumulators) {
|
|
7156
7730
|
if (acc.durations.length === 0) continue;
|
|
7157
7731
|
const n = acc.totalRequestCount;
|
|
7158
|
-
const
|
|
7732
|
+
const session2 = {
|
|
7159
7733
|
sessionId: this.sessionId,
|
|
7160
7734
|
startedAt: this.sessionStart,
|
|
7161
7735
|
avgDurationMs: Math.round(acc.totalDurationSum / n),
|
|
@@ -7171,9 +7745,9 @@ var init_metrics_store = __esm({
|
|
|
7171
7745
|
(s) => s.sessionId === this.sessionId
|
|
7172
7746
|
);
|
|
7173
7747
|
if (existingIdx !== -1) {
|
|
7174
|
-
epMetrics.sessions[existingIdx] =
|
|
7748
|
+
epMetrics.sessions[existingIdx] = session2;
|
|
7175
7749
|
} else {
|
|
7176
|
-
epMetrics.sessions.push(
|
|
7750
|
+
epMetrics.sessions.push(session2);
|
|
7177
7751
|
}
|
|
7178
7752
|
if (epMetrics.sessions.length > METRICS_MAX_SESSIONS) {
|
|
7179
7753
|
epMetrics.sessions = epMetrics.sessions.slice(-METRICS_MAX_SESSIONS);
|
|
@@ -7209,7 +7783,8 @@ var init_metrics_store = __esm({
|
|
|
7209
7783
|
});
|
|
7210
7784
|
|
|
7211
7785
|
// src/store/metrics/persistence.ts
|
|
7212
|
-
import {
|
|
7786
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
7787
|
+
import { readFileSync as readFileSync4, existsSync as existsSync6, unlinkSync } from "fs";
|
|
7213
7788
|
import { resolve as resolve3 } from "path";
|
|
7214
7789
|
var FileMetricsPersistence;
|
|
7215
7790
|
var init_persistence = __esm({
|
|
@@ -7217,7 +7792,9 @@ var init_persistence = __esm({
|
|
|
7217
7792
|
"use strict";
|
|
7218
7793
|
init_constants();
|
|
7219
7794
|
init_atomic_writer();
|
|
7795
|
+
init_fs();
|
|
7220
7796
|
init_log();
|
|
7797
|
+
init_type_guards();
|
|
7221
7798
|
FileMetricsPersistence = class {
|
|
7222
7799
|
metricsPath;
|
|
7223
7800
|
writer;
|
|
@@ -7232,7 +7809,7 @@ var init_persistence = __esm({
|
|
|
7232
7809
|
}
|
|
7233
7810
|
load() {
|
|
7234
7811
|
try {
|
|
7235
|
-
if (
|
|
7812
|
+
if (existsSync6(this.metricsPath)) {
|
|
7236
7813
|
const raw = readFileSync4(this.metricsPath, "utf-8");
|
|
7237
7814
|
const parsed = JSON.parse(raw);
|
|
7238
7815
|
if (parsed?.version === 1 && Array.isArray(parsed.endpoints)) {
|
|
@@ -7240,7 +7817,21 @@ var init_persistence = __esm({
|
|
|
7240
7817
|
}
|
|
7241
7818
|
}
|
|
7242
7819
|
} catch (err) {
|
|
7243
|
-
brakitWarn(`failed to load metrics: ${err
|
|
7820
|
+
brakitWarn(`failed to load metrics: ${getErrorMessage(err)}`);
|
|
7821
|
+
}
|
|
7822
|
+
return { version: 1, endpoints: [] };
|
|
7823
|
+
}
|
|
7824
|
+
async loadAsync() {
|
|
7825
|
+
try {
|
|
7826
|
+
if (await fileExists(this.metricsPath)) {
|
|
7827
|
+
const raw = await readFile4(this.metricsPath, "utf-8");
|
|
7828
|
+
const parsed = JSON.parse(raw);
|
|
7829
|
+
if (parsed?.version === 1 && Array.isArray(parsed.endpoints)) {
|
|
7830
|
+
return parsed;
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
} catch (err) {
|
|
7834
|
+
brakitWarn(`failed to load metrics: ${getErrorMessage(err)}`);
|
|
7244
7835
|
}
|
|
7245
7836
|
return { version: 1, endpoints: [] };
|
|
7246
7837
|
}
|
|
@@ -7252,7 +7843,7 @@ var init_persistence = __esm({
|
|
|
7252
7843
|
}
|
|
7253
7844
|
remove() {
|
|
7254
7845
|
try {
|
|
7255
|
-
if (
|
|
7846
|
+
if (existsSync6(this.metricsPath)) {
|
|
7256
7847
|
unlinkSync(this.metricsPath);
|
|
7257
7848
|
}
|
|
7258
7849
|
} catch {
|
|
@@ -7379,17 +7970,28 @@ var init_health2 = __esm({
|
|
|
7379
7970
|
BrakitHealth = class {
|
|
7380
7971
|
errorCount = 0;
|
|
7381
7972
|
disabled = false;
|
|
7973
|
+
disabledAt = 0;
|
|
7382
7974
|
teardownFn = null;
|
|
7383
7975
|
reportError() {
|
|
7384
7976
|
this.errorCount++;
|
|
7385
7977
|
if (this.errorCount >= MAX_HEALTH_ERRORS && !this.disabled) {
|
|
7386
7978
|
this.disabled = true;
|
|
7387
|
-
|
|
7979
|
+
this.disabledAt = Date.now();
|
|
7980
|
+
try {
|
|
7981
|
+
process.stderr.write("brakit: too many errors, disabling temporarily.\n");
|
|
7982
|
+
} catch {
|
|
7983
|
+
}
|
|
7388
7984
|
this.teardownFn?.();
|
|
7389
7985
|
}
|
|
7390
7986
|
}
|
|
7391
7987
|
isActive() {
|
|
7392
|
-
|
|
7988
|
+
if (!this.disabled) return true;
|
|
7989
|
+
if (Date.now() - this.disabledAt > RECOVERY_WINDOW_MS) {
|
|
7990
|
+
this.disabled = false;
|
|
7991
|
+
this.errorCount = 0;
|
|
7992
|
+
return true;
|
|
7993
|
+
}
|
|
7994
|
+
return false;
|
|
7393
7995
|
}
|
|
7394
7996
|
setTeardown(fn) {
|
|
7395
7997
|
this.teardownFn = fn;
|
|
@@ -7430,10 +8032,10 @@ var init_guard = __esm({
|
|
|
7430
8032
|
});
|
|
7431
8033
|
|
|
7432
8034
|
// src/runtime/capture.ts
|
|
7433
|
-
import {
|
|
7434
|
-
function outgoingToIncoming(
|
|
8035
|
+
import { gunzip, brotliDecompress, inflate } from "zlib";
|
|
8036
|
+
function outgoingToIncoming(headers2) {
|
|
7435
8037
|
const result = {};
|
|
7436
|
-
for (const [key, value] of Object.entries(
|
|
8038
|
+
for (const [key, value] of Object.entries(headers2)) {
|
|
7437
8039
|
if (value === void 0) continue;
|
|
7438
8040
|
if (Array.isArray(value)) {
|
|
7439
8041
|
result[key] = value.map(String);
|
|
@@ -7443,15 +8045,19 @@ function outgoingToIncoming(headers) {
|
|
|
7443
8045
|
}
|
|
7444
8046
|
return result;
|
|
7445
8047
|
}
|
|
7446
|
-
function
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
8048
|
+
function decompressAsync(body, encoding) {
|
|
8049
|
+
const decompressor = encoding === CONTENT_ENCODING_GZIP ? gunzip : encoding === CONTENT_ENCODING_BR ? brotliDecompress : encoding === CONTENT_ENCODING_DEFLATE ? inflate : null;
|
|
8050
|
+
if (!decompressor) return Promise.resolve(body);
|
|
8051
|
+
return new Promise((resolve5) => {
|
|
8052
|
+
decompressor(body, (err, result) => {
|
|
8053
|
+
if (err) {
|
|
8054
|
+
brakitDebug(`decompress failed: ${err.message}`);
|
|
8055
|
+
resolve5(body);
|
|
8056
|
+
} else {
|
|
8057
|
+
resolve5(result);
|
|
8058
|
+
}
|
|
8059
|
+
});
|
|
8060
|
+
});
|
|
7455
8061
|
}
|
|
7456
8062
|
function toBuffer(chunk) {
|
|
7457
8063
|
if (Buffer.isBuffer(chunk)) return chunk;
|
|
@@ -7495,29 +8101,35 @@ function captureInProcess(req, res, requestId, requestStore) {
|
|
|
7495
8101
|
}
|
|
7496
8102
|
const result = originalEnd.apply(this, args);
|
|
7497
8103
|
const endTime = performance.now();
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
8104
|
+
const encoding = String(res.getHeader("content-encoding") ?? "").toLowerCase();
|
|
8105
|
+
const statusCode = res.statusCode;
|
|
8106
|
+
const responseHeaders = outgoingToIncoming(res.getHeaders());
|
|
8107
|
+
const responseContentType = String(res.getHeader("content-type") ?? "");
|
|
8108
|
+
const capturedChunks = resChunks.slice();
|
|
8109
|
+
void (async () => {
|
|
8110
|
+
try {
|
|
8111
|
+
let body = capturedChunks.length > 0 ? Buffer.concat(capturedChunks) : null;
|
|
8112
|
+
if (body && encoding) {
|
|
8113
|
+
body = await decompressAsync(body, encoding);
|
|
8114
|
+
}
|
|
8115
|
+
requestStore.capture({
|
|
8116
|
+
requestId,
|
|
8117
|
+
method,
|
|
8118
|
+
url: req.url ?? "/",
|
|
8119
|
+
requestHeaders: req.headers,
|
|
8120
|
+
requestBody: null,
|
|
8121
|
+
statusCode,
|
|
8122
|
+
responseHeaders,
|
|
8123
|
+
responseBody: body,
|
|
8124
|
+
responseContentType,
|
|
8125
|
+
startTime,
|
|
8126
|
+
endTime,
|
|
8127
|
+
config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
|
|
8128
|
+
});
|
|
8129
|
+
} catch (e) {
|
|
8130
|
+
brakitDebug(`capture store: ${e.message}`);
|
|
7503
8131
|
}
|
|
7504
|
-
|
|
7505
|
-
requestId,
|
|
7506
|
-
method,
|
|
7507
|
-
url: req.url ?? "/",
|
|
7508
|
-
requestHeaders: req.headers,
|
|
7509
|
-
requestBody: null,
|
|
7510
|
-
statusCode: res.statusCode,
|
|
7511
|
-
responseHeaders: outgoingToIncoming(res.getHeaders()),
|
|
7512
|
-
responseBody: body,
|
|
7513
|
-
responseContentType: String(res.getHeader("content-type") ?? ""),
|
|
7514
|
-
startTime,
|
|
7515
|
-
endTime,
|
|
7516
|
-
config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
|
|
7517
|
-
});
|
|
7518
|
-
} catch (e) {
|
|
7519
|
-
brakitDebug(`capture store: ${e.message}`);
|
|
7520
|
-
}
|
|
8132
|
+
})();
|
|
7521
8133
|
return result;
|
|
7522
8134
|
};
|
|
7523
8135
|
}
|
|
@@ -7531,7 +8143,7 @@ var init_capture = __esm({
|
|
|
7531
8143
|
|
|
7532
8144
|
// src/runtime/interceptor.ts
|
|
7533
8145
|
import http from "http";
|
|
7534
|
-
import { randomUUID as
|
|
8146
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
7535
8147
|
function installInterceptor(deps) {
|
|
7536
8148
|
originalEmit = http.Server.prototype.emit;
|
|
7537
8149
|
const saved = originalEmit;
|
|
@@ -7557,14 +8169,14 @@ function installInterceptor(deps) {
|
|
|
7557
8169
|
}
|
|
7558
8170
|
if (isDashboardRequest(url)) {
|
|
7559
8171
|
if (!isLocalRequest(req)) {
|
|
7560
|
-
res.writeHead(
|
|
8172
|
+
res.writeHead(HTTP_NOT_FOUND);
|
|
7561
8173
|
res.end("Not Found");
|
|
7562
8174
|
return true;
|
|
7563
8175
|
}
|
|
7564
8176
|
deps.handleDashboard(req, res, deps.config);
|
|
7565
8177
|
return true;
|
|
7566
8178
|
}
|
|
7567
|
-
const requestId =
|
|
8179
|
+
const requestId = randomUUID7();
|
|
7568
8180
|
const ctx = {
|
|
7569
8181
|
requestId,
|
|
7570
8182
|
url,
|
|
@@ -7593,6 +8205,7 @@ var init_interceptor = __esm({
|
|
|
7593
8205
|
init_safe_wrap();
|
|
7594
8206
|
init_guard();
|
|
7595
8207
|
init_capture();
|
|
8208
|
+
init_http();
|
|
7596
8209
|
originalEmit = null;
|
|
7597
8210
|
}
|
|
7598
8211
|
});
|
|
@@ -7602,11 +8215,15 @@ var setup_exports = {};
|
|
|
7602
8215
|
__export(setup_exports, {
|
|
7603
8216
|
setup: () => setup
|
|
7604
8217
|
});
|
|
7605
|
-
import {
|
|
8218
|
+
import { readFile as readFile5, mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
8219
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
7606
8220
|
import { resolve as resolve4 } from "path";
|
|
7607
8221
|
function setup() {
|
|
7608
|
-
if (
|
|
7609
|
-
|
|
8222
|
+
if (initPromise) return initPromise;
|
|
8223
|
+
initPromise = doSetup();
|
|
8224
|
+
return initPromise;
|
|
8225
|
+
}
|
|
8226
|
+
async function doSetup() {
|
|
7610
8227
|
const bus = new EventBus();
|
|
7611
8228
|
const registry = new ServiceRegistry();
|
|
7612
8229
|
const requestStore = new RequestStore();
|
|
@@ -7635,6 +8252,19 @@ function setup() {
|
|
|
7635
8252
|
const adapterRegistry = createDefaultRegistry();
|
|
7636
8253
|
adapterRegistry.patchAll(telemetryEmit);
|
|
7637
8254
|
const cwd = process.cwd();
|
|
8255
|
+
let framework = "unknown";
|
|
8256
|
+
try {
|
|
8257
|
+
const pkg = JSON.parse(await readFile5(resolve4(cwd, "package.json"), "utf-8"));
|
|
8258
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
8259
|
+
framework = detectFrameworkFromDeps(allDeps);
|
|
8260
|
+
} catch {
|
|
8261
|
+
}
|
|
8262
|
+
initSession(
|
|
8263
|
+
framework,
|
|
8264
|
+
detectPackageManagerSync(cwd),
|
|
8265
|
+
false,
|
|
8266
|
+
adapterRegistry.getActive().map((a) => a.name)
|
|
8267
|
+
);
|
|
7638
8268
|
const metricsStore = new MetricsStore(new FileMetricsPersistence(cwd));
|
|
7639
8269
|
metricsStore.start();
|
|
7640
8270
|
registry.register("metrics-store", metricsStore);
|
|
@@ -7667,22 +8297,42 @@ function setup() {
|
|
|
7667
8297
|
requestStore,
|
|
7668
8298
|
onFirstRequest(port) {
|
|
7669
8299
|
setBrakitPort(port);
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
8300
|
+
void (async () => {
|
|
8301
|
+
try {
|
|
8302
|
+
const dir = resolve4(cwd, METRICS_DIR);
|
|
8303
|
+
await mkdir2(dir, { recursive: true });
|
|
8304
|
+
const portPath = resolve4(cwd, PORT_FILE);
|
|
8305
|
+
try {
|
|
8306
|
+
const old = await readFile5(portPath, "utf-8");
|
|
8307
|
+
if (old.trim() && old.trim() !== String(port)) {
|
|
8308
|
+
brakitDebug(`Overwriting stale port file (was ${old.trim()}, now ${port})`);
|
|
8309
|
+
}
|
|
8310
|
+
} catch {
|
|
8311
|
+
}
|
|
8312
|
+
await writeFile3(portPath, String(port));
|
|
8313
|
+
} catch (err) {
|
|
8314
|
+
brakitDebug(`port file write failed: ${err.message}`);
|
|
7677
8315
|
}
|
|
7678
|
-
}
|
|
7679
|
-
writeFileSync4(portPath, String(port));
|
|
8316
|
+
})();
|
|
7680
8317
|
terminalDispose = startTerminalInsights(registry, port);
|
|
7681
8318
|
process.stdout.write(` brakit v${VERSION} \u2014 http://localhost:${port}${DASHBOARD_PREFIX}
|
|
7682
8319
|
`);
|
|
7683
8320
|
}
|
|
7684
8321
|
});
|
|
7685
|
-
|
|
8322
|
+
let telemetrySent = false;
|
|
8323
|
+
const sendTelemetry = () => {
|
|
8324
|
+
if (telemetrySent) return;
|
|
8325
|
+
telemetrySent = true;
|
|
8326
|
+
recordRequestCount(requestStore.getAll().length);
|
|
8327
|
+
recordInsightTypes(analysisEngine.getInsights().map((i) => i.type));
|
|
8328
|
+
recordRulesTriggered(analysisEngine.getFindings().map((f) => f.rule));
|
|
8329
|
+
trackSession(registry);
|
|
8330
|
+
};
|
|
8331
|
+
let teardownCalled = false;
|
|
8332
|
+
const runTeardown = () => {
|
|
8333
|
+
if (teardownCalled) return;
|
|
8334
|
+
teardownCalled = true;
|
|
8335
|
+
sendTelemetry();
|
|
7686
8336
|
uninstallInterceptor();
|
|
7687
8337
|
terminalDispose?.();
|
|
7688
8338
|
analysisEngine.stop();
|
|
@@ -7690,12 +8340,19 @@ function setup() {
|
|
|
7690
8340
|
metricsStore.stop();
|
|
7691
8341
|
try {
|
|
7692
8342
|
const portPath = resolve4(cwd, PORT_FILE);
|
|
7693
|
-
if (
|
|
8343
|
+
if (existsSync7(portPath)) unlinkSync2(portPath);
|
|
7694
8344
|
} catch {
|
|
7695
8345
|
}
|
|
8346
|
+
};
|
|
8347
|
+
health.setTeardown(runTeardown);
|
|
8348
|
+
process.on("beforeExit", () => {
|
|
8349
|
+
sendTelemetry();
|
|
8350
|
+
});
|
|
8351
|
+
process.on("exit", () => {
|
|
8352
|
+
runTeardown();
|
|
7696
8353
|
});
|
|
7697
8354
|
}
|
|
7698
|
-
var
|
|
8355
|
+
var initPromise;
|
|
7699
8356
|
var init_setup = __esm({
|
|
7700
8357
|
"src/runtime/setup.ts"() {
|
|
7701
8358
|
"use strict";
|
|
@@ -7720,7 +8377,9 @@ var init_setup = __esm({
|
|
|
7720
8377
|
init_health2();
|
|
7721
8378
|
init_interceptor();
|
|
7722
8379
|
init_log();
|
|
7723
|
-
|
|
8380
|
+
init_project();
|
|
8381
|
+
init_telemetry2();
|
|
8382
|
+
initPromise = null;
|
|
7724
8383
|
}
|
|
7725
8384
|
});
|
|
7726
8385
|
|
|
@@ -7739,7 +8398,7 @@ function shouldActivate() {
|
|
|
7739
8398
|
if (shouldActivate()) {
|
|
7740
8399
|
try {
|
|
7741
8400
|
const { setup: setup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
7742
|
-
setup2();
|
|
8401
|
+
await setup2();
|
|
7743
8402
|
} catch (err) {
|
|
7744
8403
|
console.warn("brakit: failed to start \u2014", err?.message);
|
|
7745
8404
|
}
|