brakit 0.8.7 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/dist/api.d.ts +85 -46
- package/dist/api.js +782 -767
- package/dist/bin/brakit.js +456 -450
- package/dist/dashboard-client.global.js +465 -267
- package/dist/dashboard.html +584 -310
- package/dist/mcp/server.js +7 -15
- package/dist/runtime/index.js +1747 -1770
- package/package.json +1 -1
package/dist/bin/brakit.js
CHANGED
|
@@ -9,10 +9,10 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/constants/
|
|
13
|
-
var PROJECT_HASH_LENGTH, SECRET_SCAN_ARRAY_LIMIT, PII_SCAN_ARRAY_LIMIT, MIN_SECRET_VALUE_LENGTH, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, MAX_OBJECT_SCAN_DEPTH, ISSUE_PRUNE_TTL_MS;
|
|
14
|
-
var
|
|
15
|
-
"src/constants/
|
|
12
|
+
// src/constants/config.ts
|
|
13
|
+
var PROJECT_HASH_LENGTH, SECRET_SCAN_ARRAY_LIMIT, PII_SCAN_ARRAY_LIMIT, MIN_SECRET_VALUE_LENGTH, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, MAX_OBJECT_SCAN_DEPTH, ISSUE_PRUNE_TTL_MS, OVERFETCH_UNWRAP_MIN_SIZE, STALE_ISSUE_TTL_MS, METRICS_DIR, PORT_FILE, VALID_ISSUE_STATES, VALID_AI_FIX_STATUSES, VALID_SECURITY_SEVERITIES, TELEMETRY_EVENT_CLI_INVOKED, TELEMETRY_EVENT_CLI_UNINSTALL, DETAIL_PREVIEW_LENGTH;
|
|
14
|
+
var init_config = __esm({
|
|
15
|
+
"src/constants/config.ts"() {
|
|
16
16
|
"use strict";
|
|
17
17
|
PROJECT_HASH_LENGTH = 8;
|
|
18
18
|
SECRET_SCAN_ARRAY_LIMIT = 5;
|
|
@@ -22,6 +22,16 @@ var init_limits = __esm({
|
|
|
22
22
|
LIST_PII_MIN_ITEMS = 2;
|
|
23
23
|
MAX_OBJECT_SCAN_DEPTH = 5;
|
|
24
24
|
ISSUE_PRUNE_TTL_MS = 10 * 60 * 1e3;
|
|
25
|
+
OVERFETCH_UNWRAP_MIN_SIZE = 3;
|
|
26
|
+
STALE_ISSUE_TTL_MS = 30 * 60 * 1e3;
|
|
27
|
+
METRICS_DIR = ".brakit";
|
|
28
|
+
PORT_FILE = ".brakit/port";
|
|
29
|
+
VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
30
|
+
VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
31
|
+
VALID_SECURITY_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning"]);
|
|
32
|
+
TELEMETRY_EVENT_CLI_INVOKED = "cli_invoked";
|
|
33
|
+
TELEMETRY_EVENT_CLI_UNINSTALL = "cli_uninstall";
|
|
34
|
+
DETAIL_PREVIEW_LENGTH = 120;
|
|
25
35
|
}
|
|
26
36
|
});
|
|
27
37
|
|
|
@@ -40,17 +50,6 @@ var init_log = __esm({
|
|
|
40
50
|
}
|
|
41
51
|
});
|
|
42
52
|
|
|
43
|
-
// src/constants/lifecycle.ts
|
|
44
|
-
var VALID_ISSUE_STATES, VALID_AI_FIX_STATUSES, VALID_SECURITY_SEVERITIES;
|
|
45
|
-
var init_lifecycle = __esm({
|
|
46
|
-
"src/constants/lifecycle.ts"() {
|
|
47
|
-
"use strict";
|
|
48
|
-
VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
49
|
-
VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
50
|
-
VALID_SECURITY_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning"]);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
53
|
// src/utils/type-guards.ts
|
|
55
54
|
function isNonEmptyString(val) {
|
|
56
55
|
return typeof val === "string" && val.trim().length > 0;
|
|
@@ -69,35 +68,14 @@ function isValidAiFixStatus(val) {
|
|
|
69
68
|
var init_type_guards = __esm({
|
|
70
69
|
"src/utils/type-guards.ts"() {
|
|
71
70
|
"use strict";
|
|
72
|
-
|
|
73
|
-
init_limits();
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// src/constants/metrics.ts
|
|
78
|
-
var METRICS_DIR, PORT_FILE;
|
|
79
|
-
var init_metrics = __esm({
|
|
80
|
-
"src/constants/metrics.ts"() {
|
|
81
|
-
"use strict";
|
|
82
|
-
METRICS_DIR = ".brakit";
|
|
83
|
-
PORT_FILE = ".brakit/port";
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// src/constants/thresholds.ts
|
|
88
|
-
var OVERFETCH_UNWRAP_MIN_SIZE, STALE_ISSUE_TTL_MS;
|
|
89
|
-
var init_thresholds = __esm({
|
|
90
|
-
"src/constants/thresholds.ts"() {
|
|
91
|
-
"use strict";
|
|
92
|
-
OVERFETCH_UNWRAP_MIN_SIZE = 3;
|
|
93
|
-
STALE_ISSUE_TTL_MS = 30 * 60 * 1e3;
|
|
71
|
+
init_config();
|
|
94
72
|
}
|
|
95
73
|
});
|
|
96
74
|
|
|
97
|
-
// src/constants/
|
|
98
|
-
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;
|
|
99
|
-
var
|
|
100
|
-
"src/constants/
|
|
75
|
+
// src/constants/labels.ts
|
|
76
|
+
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, POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS;
|
|
77
|
+
var init_labels = __esm({
|
|
78
|
+
"src/constants/labels.ts"() {
|
|
101
79
|
"use strict";
|
|
102
80
|
DASHBOARD_PREFIX = "/__brakit";
|
|
103
81
|
DASHBOARD_API_REQUESTS = `${DASHBOARD_PREFIX}/api/requests`;
|
|
@@ -129,78 +107,16 @@ var init_routes = __esm({
|
|
|
129
107
|
"security"
|
|
130
108
|
];
|
|
131
109
|
VALID_TABS = new Set(VALID_TABS_TUPLE);
|
|
110
|
+
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
111
|
+
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
112
|
+
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
132
113
|
}
|
|
133
114
|
});
|
|
134
115
|
|
|
135
|
-
// src/constants/
|
|
136
|
-
var
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// src/constants/headers.ts
|
|
143
|
-
var init_headers = __esm({
|
|
144
|
-
"src/constants/headers.ts"() {
|
|
145
|
-
"use strict";
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// src/constants/network.ts
|
|
150
|
-
var RECOVERY_WINDOW_MS, PORT_MIN, PORT_MAX;
|
|
151
|
-
var init_network = __esm({
|
|
152
|
-
"src/constants/network.ts"() {
|
|
153
|
-
"use strict";
|
|
154
|
-
RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
155
|
-
PORT_MIN = 1;
|
|
156
|
-
PORT_MAX = 65535;
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// src/constants/mcp.ts
|
|
161
|
-
var MCP_SERVER_NAME, INITIAL_DISCOVERY_TIMEOUT_MS, LAZY_DISCOVERY_TIMEOUT_MS, CLIENT_FETCH_TIMEOUT_MS, HEALTH_CHECK_TIMEOUT_MS, DISCOVERY_POLL_INTERVAL_MS, MAX_DISCOVERY_DEPTH, MAX_TIMELINE_EVENTS, MAX_RESOLVED_DISPLAY, ENRICHMENT_SEVERITY_FILTER, MCP_SERVER_VERSION;
|
|
162
|
-
var init_mcp = __esm({
|
|
163
|
-
"src/constants/mcp.ts"() {
|
|
164
|
-
"use strict";
|
|
165
|
-
MCP_SERVER_NAME = "brakit";
|
|
166
|
-
INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
|
|
167
|
-
LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
168
|
-
CLIENT_FETCH_TIMEOUT_MS = 1e4;
|
|
169
|
-
HEALTH_CHECK_TIMEOUT_MS = 3e3;
|
|
170
|
-
DISCOVERY_POLL_INTERVAL_MS = 500;
|
|
171
|
-
MAX_DISCOVERY_DEPTH = 5;
|
|
172
|
-
MAX_TIMELINE_EVENTS = 20;
|
|
173
|
-
MAX_RESOLVED_DISPLAY = 5;
|
|
174
|
-
ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
175
|
-
MCP_SERVER_VERSION = "0.8.7";
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// src/constants/encoding.ts
|
|
180
|
-
var init_encoding = __esm({
|
|
181
|
-
"src/constants/encoding.ts"() {
|
|
182
|
-
"use strict";
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// src/constants/severity.ts
|
|
187
|
-
var init_severity = __esm({
|
|
188
|
-
"src/constants/severity.ts"() {
|
|
189
|
-
"use strict";
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// src/constants/telemetry.ts
|
|
194
|
-
var init_telemetry = __esm({
|
|
195
|
-
"src/constants/telemetry.ts"() {
|
|
196
|
-
"use strict";
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// src/constants/cli.ts
|
|
201
|
-
var SUPPORTED_SOURCE_EXTENSIONS, BUILD_CACHE_DIRS, FALLBACK_SCAN_DIRS;
|
|
202
|
-
var init_cli = __esm({
|
|
203
|
-
"src/constants/cli.ts"() {
|
|
116
|
+
// src/constants/features.ts
|
|
117
|
+
var SUPPORTED_SOURCE_EXTENSIONS, BUILD_CACHE_DIRS, FALLBACK_SCAN_DIRS, MCP_SERVER_NAME, INITIAL_DISCOVERY_TIMEOUT_MS, LAZY_DISCOVERY_TIMEOUT_MS, CLIENT_FETCH_TIMEOUT_MS, HEALTH_CHECK_TIMEOUT_MS, DISCOVERY_POLL_INTERVAL_MS, MAX_DISCOVERY_DEPTH, MAX_TIMELINE_EVENTS, MAX_RESOLVED_DISPLAY, ENRICHMENT_SEVERITY_FILTER, MCP_SERVER_VERSION, RECOVERY_WINDOW_MS, PORT_MIN, PORT_MAX, DIR_MODE_OWNER_ONLY, FILE_MODE_OWNER_ONLY;
|
|
118
|
+
var init_features = __esm({
|
|
119
|
+
"src/constants/features.ts"() {
|
|
204
120
|
"use strict";
|
|
205
121
|
SUPPORTED_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
206
122
|
".ts",
|
|
@@ -212,20 +128,22 @@ var init_cli = __esm({
|
|
|
212
128
|
]);
|
|
213
129
|
BUILD_CACHE_DIRS = [".next", ".nuxt", ".output"];
|
|
214
130
|
FALLBACK_SCAN_DIRS = ["src", "."];
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
131
|
+
MCP_SERVER_NAME = "brakit";
|
|
132
|
+
INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
|
|
133
|
+
LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
134
|
+
CLIENT_FETCH_TIMEOUT_MS = 1e4;
|
|
135
|
+
HEALTH_CHECK_TIMEOUT_MS = 3e3;
|
|
136
|
+
DISCOVERY_POLL_INTERVAL_MS = 500;
|
|
137
|
+
MAX_DISCOVERY_DEPTH = 5;
|
|
138
|
+
MAX_TIMELINE_EVENTS = 20;
|
|
139
|
+
MAX_RESOLVED_DISPLAY = 5;
|
|
140
|
+
ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
141
|
+
MCP_SERVER_VERSION = "0.9.1";
|
|
142
|
+
RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
143
|
+
PORT_MIN = 1;
|
|
144
|
+
PORT_MAX = 65535;
|
|
145
|
+
DIR_MODE_OWNER_ONLY = 448;
|
|
146
|
+
FILE_MODE_OWNER_ONLY = 384;
|
|
229
147
|
}
|
|
230
148
|
});
|
|
231
149
|
|
|
@@ -233,21 +151,9 @@ var init_sdk_events = __esm({
|
|
|
233
151
|
var init_constants = __esm({
|
|
234
152
|
"src/constants/index.ts"() {
|
|
235
153
|
"use strict";
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
init_transport();
|
|
240
|
-
init_metrics();
|
|
241
|
-
init_headers();
|
|
242
|
-
init_network();
|
|
243
|
-
init_mcp();
|
|
244
|
-
init_encoding();
|
|
245
|
-
init_severity();
|
|
246
|
-
init_telemetry();
|
|
247
|
-
init_lifecycle();
|
|
248
|
-
init_cli();
|
|
249
|
-
init_timeline();
|
|
250
|
-
init_sdk_events();
|
|
154
|
+
init_config();
|
|
155
|
+
init_labels();
|
|
156
|
+
init_features();
|
|
251
157
|
}
|
|
252
158
|
});
|
|
253
159
|
|
|
@@ -270,8 +176,8 @@ var BrakitClient;
|
|
|
270
176
|
var init_client = __esm({
|
|
271
177
|
"src/mcp/client.ts"() {
|
|
272
178
|
"use strict";
|
|
273
|
-
|
|
274
|
-
|
|
179
|
+
init_labels();
|
|
180
|
+
init_features();
|
|
275
181
|
BrakitClient = class {
|
|
276
182
|
constructor(baseUrl) {
|
|
277
183
|
this.baseUrl = baseUrl;
|
|
@@ -409,8 +315,8 @@ async function searchForPort(startDir) {
|
|
|
409
315
|
}
|
|
410
316
|
return null;
|
|
411
317
|
}
|
|
412
|
-
async function discoverBrakitPort(
|
|
413
|
-
const port = await searchForPort(
|
|
318
|
+
async function discoverBrakitPort(cwd2) {
|
|
319
|
+
const port = await searchForPort(cwd2 ?? process.cwd());
|
|
414
320
|
if (!port) {
|
|
415
321
|
throw new Error(
|
|
416
322
|
"Brakit is not running. Start your app with brakit enabled first."
|
|
@@ -418,11 +324,11 @@ async function discoverBrakitPort(cwd) {
|
|
|
418
324
|
}
|
|
419
325
|
return { port, baseUrl: `http://localhost:${port}` };
|
|
420
326
|
}
|
|
421
|
-
async function waitForBrakit(
|
|
327
|
+
async function waitForBrakit(cwd2, timeoutMs = 1e4, pollMs = DISCOVERY_POLL_INTERVAL_MS) {
|
|
422
328
|
const deadline = Date.now() + timeoutMs;
|
|
423
329
|
while (Date.now() < deadline) {
|
|
424
330
|
try {
|
|
425
|
-
const result = await discoverBrakitPort(
|
|
331
|
+
const result = await discoverBrakitPort(cwd2);
|
|
426
332
|
const res = await fetch(`${result.baseUrl}${DASHBOARD_API_REQUESTS}?limit=1`);
|
|
427
333
|
if (res.ok) return result;
|
|
428
334
|
} catch {
|
|
@@ -438,7 +344,7 @@ var init_discovery = __esm({
|
|
|
438
344
|
"use strict";
|
|
439
345
|
init_constants();
|
|
440
346
|
init_log();
|
|
441
|
-
|
|
347
|
+
init_features();
|
|
442
348
|
}
|
|
443
349
|
});
|
|
444
350
|
|
|
@@ -539,7 +445,7 @@ async function buildRequestDetail(client, req) {
|
|
|
539
445
|
var init_enrichment = __esm({
|
|
540
446
|
"src/mcp/enrichment.ts"() {
|
|
541
447
|
"use strict";
|
|
542
|
-
|
|
448
|
+
init_features();
|
|
543
449
|
init_endpoint();
|
|
544
450
|
}
|
|
545
451
|
});
|
|
@@ -550,7 +456,7 @@ var init_get_findings = __esm({
|
|
|
550
456
|
"src/mcp/tools/get-findings.ts"() {
|
|
551
457
|
"use strict";
|
|
552
458
|
init_enrichment();
|
|
553
|
-
|
|
459
|
+
init_config();
|
|
554
460
|
init_type_guards();
|
|
555
461
|
getFindings = {
|
|
556
462
|
name: "get_findings",
|
|
@@ -665,7 +571,7 @@ var getRequestDetail;
|
|
|
665
571
|
var init_get_request_detail = __esm({
|
|
666
572
|
"src/mcp/tools/get-request-detail.ts"() {
|
|
667
573
|
"use strict";
|
|
668
|
-
|
|
574
|
+
init_features();
|
|
669
575
|
init_enrichment();
|
|
670
576
|
getRequestDetail = {
|
|
671
577
|
name: "get_request_detail",
|
|
@@ -845,7 +751,7 @@ var getReport;
|
|
|
845
751
|
var init_get_report = __esm({
|
|
846
752
|
"src/mcp/tools/get-report.ts"() {
|
|
847
753
|
"use strict";
|
|
848
|
-
|
|
754
|
+
init_features();
|
|
849
755
|
getReport = {
|
|
850
756
|
name: "get_report",
|
|
851
757
|
description: "Generate a summary report of all findings: total found, open, resolved. Use this to get a high-level overview of the application's health.",
|
|
@@ -1142,13 +1048,15 @@ var init_server = __esm({
|
|
|
1142
1048
|
init_client();
|
|
1143
1049
|
init_discovery();
|
|
1144
1050
|
init_tools();
|
|
1145
|
-
|
|
1051
|
+
init_features();
|
|
1146
1052
|
init_prompts();
|
|
1147
1053
|
}
|
|
1148
1054
|
});
|
|
1149
1055
|
|
|
1150
1056
|
// bin/brakit.ts
|
|
1151
1057
|
import { runMain } from "citty";
|
|
1058
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1059
|
+
import { resolve as resolve6 } from "path";
|
|
1152
1060
|
|
|
1153
1061
|
// src/cli/commands/install.ts
|
|
1154
1062
|
import { defineCommand } from "citty";
|
|
@@ -1164,7 +1072,7 @@ import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync }
|
|
|
1164
1072
|
import { resolve as resolve2 } from "path";
|
|
1165
1073
|
|
|
1166
1074
|
// src/utils/fs.ts
|
|
1167
|
-
|
|
1075
|
+
init_config();
|
|
1168
1076
|
init_log();
|
|
1169
1077
|
init_type_guards();
|
|
1170
1078
|
import { access, readFile, writeFile } from "fs/promises";
|
|
@@ -1187,10 +1095,7 @@ async function fileExists(path) {
|
|
|
1187
1095
|
}
|
|
1188
1096
|
|
|
1189
1097
|
// src/store/issue-store.ts
|
|
1190
|
-
|
|
1191
|
-
init_limits();
|
|
1192
|
-
init_thresholds();
|
|
1193
|
-
init_limits();
|
|
1098
|
+
init_config();
|
|
1194
1099
|
|
|
1195
1100
|
// src/utils/atomic-writer.ts
|
|
1196
1101
|
import {
|
|
@@ -1208,7 +1113,7 @@ init_log();
|
|
|
1208
1113
|
init_type_guards();
|
|
1209
1114
|
|
|
1210
1115
|
// src/utils/issue-id.ts
|
|
1211
|
-
|
|
1116
|
+
init_config();
|
|
1212
1117
|
import { createHash as createHash2 } from "crypto";
|
|
1213
1118
|
|
|
1214
1119
|
// src/detect/project.ts
|
|
@@ -1378,12 +1283,13 @@ async function detectInDir(dir, rootDir, projects) {
|
|
|
1378
1283
|
}
|
|
1379
1284
|
|
|
1380
1285
|
// src/utils/response.ts
|
|
1381
|
-
|
|
1286
|
+
init_config();
|
|
1287
|
+
var MAX_WRAPPER_KEYS = 3;
|
|
1382
1288
|
function unwrapResponse(parsed) {
|
|
1383
1289
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
|
|
1384
1290
|
const obj = parsed;
|
|
1385
1291
|
const keys = Object.keys(obj);
|
|
1386
|
-
if (keys.length >
|
|
1292
|
+
if (keys.length > MAX_WRAPPER_KEYS) return parsed;
|
|
1387
1293
|
let best = null;
|
|
1388
1294
|
let bestSize = 0;
|
|
1389
1295
|
for (const key of keys) {
|
|
@@ -1402,6 +1308,10 @@ function unwrapResponse(parsed) {
|
|
|
1402
1308
|
return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
|
|
1403
1309
|
}
|
|
1404
1310
|
|
|
1311
|
+
// src/analysis/rules/scanner.ts
|
|
1312
|
+
init_log();
|
|
1313
|
+
init_type_guards();
|
|
1314
|
+
|
|
1405
1315
|
// src/analysis/rules/patterns.ts
|
|
1406
1316
|
var SECRET_KEYS = /^(password|passwd|secret|api_key|apiKey|api_secret|apiSecret|private_key|privateKey|client_secret|clientSecret)$/;
|
|
1407
1317
|
var TOKEN_PARAMS = /^(token|api_key|apiKey|secret|password|access_token|session_id|sessionId)$/;
|
|
@@ -1428,8 +1338,8 @@ var RULE_HINTS = {
|
|
|
1428
1338
|
"response-pii-leak": "API responses should return minimal data. Don't echo back full user records \u2014 select only the fields the client needs."
|
|
1429
1339
|
};
|
|
1430
1340
|
|
|
1431
|
-
// src/analysis/rules/
|
|
1432
|
-
|
|
1341
|
+
// src/analysis/rules/auth-rules.ts
|
|
1342
|
+
init_config();
|
|
1433
1343
|
|
|
1434
1344
|
// src/utils/http-status.ts
|
|
1435
1345
|
function isErrorStatus(code) {
|
|
@@ -1439,27 +1349,66 @@ function isRedirect(code) {
|
|
|
1439
1349
|
return code >= 300 && code < 400;
|
|
1440
1350
|
}
|
|
1441
1351
|
|
|
1442
|
-
// src/
|
|
1443
|
-
function
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1352
|
+
// src/utils/collections.ts
|
|
1353
|
+
function deduplicateFindings(items, extract) {
|
|
1354
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1355
|
+
const findings = [];
|
|
1356
|
+
for (const item of items) {
|
|
1357
|
+
const result = extract(item);
|
|
1358
|
+
if (!result) continue;
|
|
1359
|
+
const existing = seen.get(result.key);
|
|
1360
|
+
if (existing) {
|
|
1361
|
+
existing.count++;
|
|
1362
|
+
continue;
|
|
1450
1363
|
}
|
|
1451
|
-
|
|
1364
|
+
seen.set(result.key, result.finding);
|
|
1365
|
+
findings.push(result.finding);
|
|
1452
1366
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1367
|
+
return findings;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// src/utils/object-scan.ts
|
|
1371
|
+
init_config();
|
|
1372
|
+
var DEFAULTS = {
|
|
1373
|
+
maxDepth: MAX_OBJECT_SCAN_DEPTH,
|
|
1374
|
+
arrayLimit: SECRET_SCAN_ARRAY_LIMIT
|
|
1375
|
+
};
|
|
1376
|
+
function walkObject(obj, visitor, options) {
|
|
1377
|
+
const opts = { ...DEFAULTS, ...options };
|
|
1378
|
+
walk(obj, visitor, opts, 0);
|
|
1379
|
+
}
|
|
1380
|
+
function walk(obj, visitor, opts, depth) {
|
|
1381
|
+
if (depth >= opts.maxDepth) return;
|
|
1382
|
+
if (!obj || typeof obj !== "object") return;
|
|
1383
|
+
if (Array.isArray(obj)) {
|
|
1384
|
+
for (let i = 0; i < Math.min(obj.length, opts.arrayLimit); i++) {
|
|
1385
|
+
walk(obj[i], visitor, opts, depth + 1);
|
|
1457
1386
|
}
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
for (const key of Object.keys(obj)) {
|
|
1390
|
+
const val = obj[key];
|
|
1391
|
+
visitor(key, val, depth);
|
|
1458
1392
|
if (typeof val === "object" && val !== null) {
|
|
1459
|
-
|
|
1393
|
+
walk(val, visitor, opts, depth + 1);
|
|
1460
1394
|
}
|
|
1461
1395
|
}
|
|
1462
|
-
|
|
1396
|
+
}
|
|
1397
|
+
function collectFromObject(obj, match, options) {
|
|
1398
|
+
const results = [];
|
|
1399
|
+
walkObject(obj, (key, value) => {
|
|
1400
|
+
const result = match(key, value);
|
|
1401
|
+
if (result !== null) results.push(result);
|
|
1402
|
+
}, options);
|
|
1403
|
+
return results;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/analysis/rules/auth-rules.ts
|
|
1407
|
+
function findSecretKeys(obj) {
|
|
1408
|
+
return collectFromObject(
|
|
1409
|
+
obj,
|
|
1410
|
+
(key, val) => SECRET_KEYS.test(key) && typeof val === "string" && val.length >= MIN_SECRET_VALUE_LENGTH && !MASKED_RE.test(val) ? key : null
|
|
1411
|
+
);
|
|
1463
1412
|
}
|
|
1464
1413
|
var exposedSecretRule = {
|
|
1465
1414
|
id: "exposed-secret",
|
|
@@ -1467,50 +1416,39 @@ var exposedSecretRule = {
|
|
|
1467
1416
|
name: "Exposed Secret in Response",
|
|
1468
1417
|
hint: RULE_HINTS["exposed-secret"],
|
|
1469
1418
|
check(ctx) {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
if (
|
|
1474
|
-
const
|
|
1475
|
-
if (
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
desc: `${ep} \u2014 response contains ${keys.join(", ")} field${keys.length > 1 ? "s" : ""}`,
|
|
1490
|
-
hint: this.hint,
|
|
1491
|
-
endpoint: ep,
|
|
1492
|
-
count: 1
|
|
1419
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1420
|
+
if (isErrorStatus(request.statusCode)) return null;
|
|
1421
|
+
const parsed = ctx.parsedBodies.response.get(request.id);
|
|
1422
|
+
if (!parsed) return null;
|
|
1423
|
+
const keys = findSecretKeys(parsed);
|
|
1424
|
+
if (keys.length === 0) return null;
|
|
1425
|
+
const ep = `${request.method} ${request.path}`;
|
|
1426
|
+
return {
|
|
1427
|
+
key: `${ep}:${keys.sort().join(",")}`,
|
|
1428
|
+
finding: {
|
|
1429
|
+
severity: "critical",
|
|
1430
|
+
rule: "exposed-secret",
|
|
1431
|
+
title: "Exposed Secret in Response",
|
|
1432
|
+
desc: `${ep} \u2014 response contains ${keys.join(", ")} field${keys.length > 1 ? "s" : ""}`,
|
|
1433
|
+
hint: this.hint,
|
|
1434
|
+
detail: `Exposed fields: ${keys.join(", ")}. ${keys.length} unmasked secret value${keys.length !== 1 ? "s" : ""} in response body.`,
|
|
1435
|
+
endpoint: ep,
|
|
1436
|
+
count: 1
|
|
1437
|
+
}
|
|
1493
1438
|
};
|
|
1494
|
-
|
|
1495
|
-
findings.push(finding);
|
|
1496
|
-
}
|
|
1497
|
-
return findings;
|
|
1439
|
+
});
|
|
1498
1440
|
}
|
|
1499
1441
|
};
|
|
1500
|
-
|
|
1501
|
-
// src/analysis/rules/token-in-url.ts
|
|
1502
1442
|
var tokenInUrlRule = {
|
|
1503
1443
|
id: "token-in-url",
|
|
1504
1444
|
severity: "critical",
|
|
1505
1445
|
name: "Auth Token in URL",
|
|
1506
1446
|
hint: RULE_HINTS["token-in-url"],
|
|
1507
1447
|
check(ctx) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const
|
|
1512
|
-
if (qIdx === -1) continue;
|
|
1513
|
-
const params = r.url.substring(qIdx + 1).split("&");
|
|
1448
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1449
|
+
const qIdx = request.url.indexOf("?");
|
|
1450
|
+
if (qIdx === -1) return null;
|
|
1451
|
+
const params = request.url.substring(qIdx + 1).split("&");
|
|
1514
1452
|
const flagged = [];
|
|
1515
1453
|
for (const param of params) {
|
|
1516
1454
|
const [name, ...rest] = param.split("=");
|
|
@@ -1520,65 +1458,129 @@ var tokenInUrlRule = {
|
|
|
1520
1458
|
flagged.push(name);
|
|
1521
1459
|
}
|
|
1522
1460
|
}
|
|
1523
|
-
if (flagged.length === 0)
|
|
1524
|
-
const ep = `${
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1461
|
+
if (flagged.length === 0) return null;
|
|
1462
|
+
const ep = `${request.method} ${request.path}`;
|
|
1463
|
+
return {
|
|
1464
|
+
key: `${ep}:${flagged.sort().join(",")}`,
|
|
1465
|
+
finding: {
|
|
1466
|
+
severity: "critical",
|
|
1467
|
+
rule: "token-in-url",
|
|
1468
|
+
title: "Auth Token in URL",
|
|
1469
|
+
desc: `${ep} \u2014 ${flagged.join(", ")} exposed in query string`,
|
|
1470
|
+
hint: this.hint,
|
|
1471
|
+
detail: `Parameters in URL: ${flagged.join(", ")}. Auth tokens in URLs are logged by proxies, browsers, and CDNs.`,
|
|
1472
|
+
endpoint: ep,
|
|
1473
|
+
count: 1
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
function isFrameworkResponse(request) {
|
|
1480
|
+
if (isRedirect(request.statusCode)) return true;
|
|
1481
|
+
if (request.path?.startsWith("/__")) return true;
|
|
1482
|
+
if (request.responseHeaders?.["x-middleware-rewrite"]) return true;
|
|
1483
|
+
return false;
|
|
1484
|
+
}
|
|
1485
|
+
var insecureCookieRule = {
|
|
1486
|
+
id: "insecure-cookie",
|
|
1487
|
+
severity: "warning",
|
|
1488
|
+
name: "Insecure Cookie",
|
|
1489
|
+
hint: RULE_HINTS["insecure-cookie"],
|
|
1490
|
+
check(ctx) {
|
|
1491
|
+
const cookieEntries = [];
|
|
1492
|
+
for (const request of ctx.requests) {
|
|
1493
|
+
if (!request.responseHeaders) continue;
|
|
1494
|
+
if (isFrameworkResponse(request)) continue;
|
|
1495
|
+
const setCookie = request.responseHeaders["set-cookie"];
|
|
1496
|
+
if (!setCookie) continue;
|
|
1497
|
+
const cookies = setCookie.split(/,(?=\s*[A-Za-z0-9_\-]+=)/);
|
|
1498
|
+
for (const cookie of cookies) {
|
|
1499
|
+
cookieEntries.push({ cookie });
|
|
1530
1500
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1501
|
+
}
|
|
1502
|
+
return deduplicateFindings(cookieEntries, ({ cookie }) => {
|
|
1503
|
+
const cookieName = cookie.trim().split("=")[0].trim();
|
|
1504
|
+
const lower = cookie.toLowerCase();
|
|
1505
|
+
const issues = [];
|
|
1506
|
+
if (!lower.includes("httponly")) issues.push("HttpOnly");
|
|
1507
|
+
if (!lower.includes("samesite")) issues.push("SameSite");
|
|
1508
|
+
if (issues.length === 0) return null;
|
|
1509
|
+
return {
|
|
1510
|
+
key: `${cookieName}:${issues.join(",")}`,
|
|
1511
|
+
finding: {
|
|
1512
|
+
severity: "warning",
|
|
1513
|
+
rule: "insecure-cookie",
|
|
1514
|
+
title: "Insecure Cookie",
|
|
1515
|
+
desc: `${cookieName} \u2014 missing ${issues.join(", ")} flag${issues.length > 1 ? "s" : ""}`,
|
|
1516
|
+
hint: this.hint,
|
|
1517
|
+
detail: `Missing: ${issues.join(", ")}. ${issues.includes("HttpOnly") ? "Cookie accessible via JavaScript (XSS risk). " : ""}${issues.includes("SameSite") ? "Cookie sent on cross-site requests (CSRF risk)." : ""}`,
|
|
1518
|
+
endpoint: cookieName,
|
|
1519
|
+
count: 1
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
var corsCredentialsRule = {
|
|
1526
|
+
id: "cors-credentials",
|
|
1527
|
+
severity: "warning",
|
|
1528
|
+
name: "CORS Credentials with Wildcard",
|
|
1529
|
+
hint: RULE_HINTS["cors-credentials"],
|
|
1530
|
+
check(ctx) {
|
|
1531
|
+
const findings = [];
|
|
1532
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1533
|
+
for (const request of ctx.requests) {
|
|
1534
|
+
if (!request.responseHeaders) continue;
|
|
1535
|
+
const origin = request.responseHeaders["access-control-allow-origin"];
|
|
1536
|
+
const creds = request.responseHeaders["access-control-allow-credentials"];
|
|
1537
|
+
if (origin !== "*" || creds !== "true") continue;
|
|
1538
|
+
const ep = `${request.method} ${request.path}`;
|
|
1539
|
+
if (seen.has(ep)) continue;
|
|
1540
|
+
seen.add(ep);
|
|
1541
|
+
findings.push({
|
|
1542
|
+
severity: "warning",
|
|
1543
|
+
rule: "cors-credentials",
|
|
1544
|
+
title: "CORS Credentials with Wildcard",
|
|
1545
|
+
desc: `${ep} \u2014 credentials:true with origin:* (browser will reject)`,
|
|
1536
1546
|
hint: this.hint,
|
|
1537
1547
|
endpoint: ep,
|
|
1538
1548
|
count: 1
|
|
1539
|
-
};
|
|
1540
|
-
seen.set(dedupKey, finding);
|
|
1541
|
-
findings.push(finding);
|
|
1549
|
+
});
|
|
1542
1550
|
}
|
|
1543
1551
|
return findings;
|
|
1544
1552
|
}
|
|
1545
1553
|
};
|
|
1546
1554
|
|
|
1547
|
-
// src/analysis/rules/
|
|
1555
|
+
// src/analysis/rules/data-rules.ts
|
|
1556
|
+
init_config();
|
|
1548
1557
|
var stackTraceLeakRule = {
|
|
1549
1558
|
id: "stack-trace-leak",
|
|
1550
1559
|
severity: "critical",
|
|
1551
1560
|
name: "Stack Trace Leaked to Client",
|
|
1552
1561
|
hint: RULE_HINTS["stack-trace-leak"],
|
|
1553
1562
|
check(ctx) {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
endpoint: ep,
|
|
1572
|
-
count: 1
|
|
1563
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1564
|
+
if (!request.responseBody) return null;
|
|
1565
|
+
if (!STACK_TRACE_RE.test(request.responseBody)) return null;
|
|
1566
|
+
const ep = `${request.method} ${request.path}`;
|
|
1567
|
+
const firstLine = request.responseBody.split("\n").find((l) => STACK_TRACE_RE.test(l))?.trim() ?? "";
|
|
1568
|
+
return {
|
|
1569
|
+
key: ep,
|
|
1570
|
+
finding: {
|
|
1571
|
+
severity: "critical",
|
|
1572
|
+
rule: "stack-trace-leak",
|
|
1573
|
+
title: "Stack Trace Leaked to Client",
|
|
1574
|
+
desc: `${ep} \u2014 response exposes internal stack trace`,
|
|
1575
|
+
hint: this.hint,
|
|
1576
|
+
detail: firstLine ? `Stack trace: ${firstLine.slice(0, DETAIL_PREVIEW_LENGTH)}` : void 0,
|
|
1577
|
+
endpoint: ep,
|
|
1578
|
+
count: 1
|
|
1579
|
+
}
|
|
1573
1580
|
};
|
|
1574
|
-
|
|
1575
|
-
findings.push(finding);
|
|
1576
|
-
}
|
|
1577
|
-
return findings;
|
|
1581
|
+
});
|
|
1578
1582
|
}
|
|
1579
1583
|
};
|
|
1580
|
-
|
|
1581
|
-
// src/analysis/rules/error-info-leak.ts
|
|
1582
1584
|
var CRITICAL_PATTERNS = [
|
|
1583
1585
|
{ re: DB_CONN_RE, label: "database connection string" },
|
|
1584
1586
|
{ re: SQL_FRAGMENT_RE, label: "SQL query fragment" },
|
|
@@ -1590,90 +1592,35 @@ var errorInfoLeakRule = {
|
|
|
1590
1592
|
name: "Sensitive Data in Error Response",
|
|
1591
1593
|
hint: RULE_HINTS["error-info-leak"],
|
|
1592
1594
|
check(ctx) {
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
if (
|
|
1597
|
-
if (
|
|
1598
|
-
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
const dedupKey = `${ep}:${p.label}`;
|
|
1603
|
-
const existing = seen.get(dedupKey);
|
|
1604
|
-
if (existing) {
|
|
1605
|
-
existing.count++;
|
|
1606
|
-
continue;
|
|
1595
|
+
const entries = [];
|
|
1596
|
+
for (const request of ctx.requests) {
|
|
1597
|
+
if (request.statusCode < 400) continue;
|
|
1598
|
+
if (!request.responseBody) continue;
|
|
1599
|
+
if (request.responseHeaders["x-nextjs-error"] || request.responseHeaders["x-nextjs-matched-path"]) continue;
|
|
1600
|
+
const ep = `${request.method} ${request.path}`;
|
|
1601
|
+
for (const pattern of CRITICAL_PATTERNS) {
|
|
1602
|
+
if (pattern.re.test(request.responseBody)) {
|
|
1603
|
+
entries.push({ ep, pattern, body: request.responseBody });
|
|
1607
1604
|
}
|
|
1608
|
-
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return deduplicateFindings(entries, ({ ep, pattern }) => {
|
|
1608
|
+
return {
|
|
1609
|
+
key: `${ep}:${pattern.label}`,
|
|
1610
|
+
finding: {
|
|
1609
1611
|
severity: "critical",
|
|
1610
1612
|
rule: "error-info-leak",
|
|
1611
1613
|
title: "Sensitive Data in Error Response",
|
|
1612
|
-
desc: `${ep} \u2014 error response exposes ${
|
|
1614
|
+
desc: `${ep} \u2014 error response exposes ${pattern.label}`,
|
|
1613
1615
|
hint: this.hint,
|
|
1616
|
+
detail: `Detected: ${pattern.label} in error response body`,
|
|
1614
1617
|
endpoint: ep,
|
|
1615
1618
|
count: 1
|
|
1616
|
-
};
|
|
1617
|
-
seen.set(dedupKey, finding);
|
|
1618
|
-
findings.push(finding);
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
return findings;
|
|
1622
|
-
}
|
|
1623
|
-
};
|
|
1624
|
-
|
|
1625
|
-
// src/analysis/rules/insecure-cookie.ts
|
|
1626
|
-
function isFrameworkResponse(r) {
|
|
1627
|
-
if (isRedirect(r.statusCode)) return true;
|
|
1628
|
-
if (r.path?.startsWith("/__")) return true;
|
|
1629
|
-
if (r.responseHeaders?.["x-middleware-rewrite"]) return true;
|
|
1630
|
-
return false;
|
|
1631
|
-
}
|
|
1632
|
-
var insecureCookieRule = {
|
|
1633
|
-
id: "insecure-cookie",
|
|
1634
|
-
severity: "warning",
|
|
1635
|
-
name: "Insecure Cookie",
|
|
1636
|
-
hint: RULE_HINTS["insecure-cookie"],
|
|
1637
|
-
check(ctx) {
|
|
1638
|
-
const findings = [];
|
|
1639
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1640
|
-
for (const r of ctx.requests) {
|
|
1641
|
-
if (!r.responseHeaders) continue;
|
|
1642
|
-
if (isFrameworkResponse(r)) continue;
|
|
1643
|
-
const setCookie = r.responseHeaders["set-cookie"];
|
|
1644
|
-
if (!setCookie) continue;
|
|
1645
|
-
const cookies = setCookie.split(/,(?=\s*[A-Za-z0-9_\-]+=)/);
|
|
1646
|
-
for (const cookie of cookies) {
|
|
1647
|
-
const cookieName = cookie.trim().split("=")[0].trim();
|
|
1648
|
-
const lower = cookie.toLowerCase();
|
|
1649
|
-
const issues = [];
|
|
1650
|
-
if (!lower.includes("httponly")) issues.push("HttpOnly");
|
|
1651
|
-
if (!lower.includes("samesite")) issues.push("SameSite");
|
|
1652
|
-
if (issues.length === 0) continue;
|
|
1653
|
-
const dedupKey = `${cookieName}:${issues.join(",")}`;
|
|
1654
|
-
const existing = seen.get(dedupKey);
|
|
1655
|
-
if (existing) {
|
|
1656
|
-
existing.count++;
|
|
1657
|
-
continue;
|
|
1658
1619
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
rule: "insecure-cookie",
|
|
1662
|
-
title: "Insecure Cookie",
|
|
1663
|
-
desc: `${cookieName} \u2014 missing ${issues.join(", ")} flag${issues.length > 1 ? "s" : ""}`,
|
|
1664
|
-
hint: this.hint,
|
|
1665
|
-
endpoint: cookieName,
|
|
1666
|
-
count: 1
|
|
1667
|
-
};
|
|
1668
|
-
seen.set(dedupKey, finding);
|
|
1669
|
-
findings.push(finding);
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
return findings;
|
|
1620
|
+
};
|
|
1621
|
+
});
|
|
1673
1622
|
}
|
|
1674
1623
|
};
|
|
1675
|
-
|
|
1676
|
-
// src/analysis/rules/sensitive-logs.ts
|
|
1677
1624
|
var sensitiveLogsRule = {
|
|
1678
1625
|
id: "sensitive-logs",
|
|
1679
1626
|
severity: "warning",
|
|
@@ -1698,59 +1645,13 @@ var sensitiveLogsRule = {
|
|
|
1698
1645
|
}];
|
|
1699
1646
|
}
|
|
1700
1647
|
};
|
|
1701
|
-
|
|
1702
|
-
// src/analysis/rules/cors-credentials.ts
|
|
1703
|
-
var corsCredentialsRule = {
|
|
1704
|
-
id: "cors-credentials",
|
|
1705
|
-
severity: "warning",
|
|
1706
|
-
name: "CORS Credentials with Wildcard",
|
|
1707
|
-
hint: RULE_HINTS["cors-credentials"],
|
|
1708
|
-
check(ctx) {
|
|
1709
|
-
const findings = [];
|
|
1710
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1711
|
-
for (const r of ctx.requests) {
|
|
1712
|
-
if (!r.responseHeaders) continue;
|
|
1713
|
-
const origin = r.responseHeaders["access-control-allow-origin"];
|
|
1714
|
-
const creds = r.responseHeaders["access-control-allow-credentials"];
|
|
1715
|
-
if (origin !== "*" || creds !== "true") continue;
|
|
1716
|
-
const ep = `${r.method} ${r.path}`;
|
|
1717
|
-
if (seen.has(ep)) continue;
|
|
1718
|
-
seen.add(ep);
|
|
1719
|
-
findings.push({
|
|
1720
|
-
severity: "warning",
|
|
1721
|
-
rule: "cors-credentials",
|
|
1722
|
-
title: "CORS Credentials with Wildcard",
|
|
1723
|
-
desc: `${ep} \u2014 credentials:true with origin:* (browser will reject)`,
|
|
1724
|
-
hint: this.hint,
|
|
1725
|
-
endpoint: ep,
|
|
1726
|
-
count: 1
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
return findings;
|
|
1730
|
-
}
|
|
1731
|
-
};
|
|
1732
|
-
|
|
1733
|
-
// src/analysis/rules/response-pii-leak.ts
|
|
1734
|
-
init_limits();
|
|
1735
1648
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
1736
|
-
function findEmails(obj
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
emails.push(...findEmails(obj[i], depth + 1));
|
|
1743
|
-
}
|
|
1744
|
-
return emails;
|
|
1745
|
-
}
|
|
1746
|
-
for (const v of Object.values(obj)) {
|
|
1747
|
-
if (typeof v === "string" && EMAIL_RE.test(v)) {
|
|
1748
|
-
emails.push(v);
|
|
1749
|
-
} else if (typeof v === "object" && v !== null) {
|
|
1750
|
-
emails.push(...findEmails(v, depth + 1));
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
return emails;
|
|
1649
|
+
function findEmails(obj) {
|
|
1650
|
+
return collectFromObject(
|
|
1651
|
+
obj,
|
|
1652
|
+
(_key, val) => typeof val === "string" && EMAIL_RE.test(val) ? val : null,
|
|
1653
|
+
{ arrayLimit: PII_SCAN_ARRAY_LIMIT }
|
|
1654
|
+
);
|
|
1754
1655
|
}
|
|
1755
1656
|
function topLevelFieldCount(obj) {
|
|
1756
1657
|
if (Array.isArray(obj)) {
|
|
@@ -1835,101 +1736,83 @@ var responsePiiLeakRule = {
|
|
|
1835
1736
|
name: "PII Leak in Response",
|
|
1836
1737
|
hint: RULE_HINTS["response-pii-leak"],
|
|
1837
1738
|
check(ctx) {
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
if (
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
if (
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1739
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1740
|
+
if (isErrorStatus(request.statusCode)) return null;
|
|
1741
|
+
if (SELF_SERVICE_PATH.test(request.path)) return null;
|
|
1742
|
+
const resJson = ctx.parsedBodies.response.get(request.id);
|
|
1743
|
+
if (!resJson) return null;
|
|
1744
|
+
const reqJson = ctx.parsedBodies.request.get(request.id) ?? null;
|
|
1745
|
+
const detection = detectPII(request.method, reqJson, resJson);
|
|
1746
|
+
if (!detection) return null;
|
|
1747
|
+
const ep = `${request.method} ${request.path}`;
|
|
1748
|
+
const fieldCount = topLevelFieldCount(resJson);
|
|
1749
|
+
const detailParts = [`Pattern: ${REASON_LABELS[detection.reason]}`];
|
|
1750
|
+
if (detection.emailCount > 0) detailParts.push(`${detection.emailCount} email${detection.emailCount !== 1 ? "s" : ""} detected`);
|
|
1751
|
+
if (fieldCount > 0) detailParts.push(`${fieldCount} fields per record`);
|
|
1752
|
+
return {
|
|
1753
|
+
key: ep,
|
|
1754
|
+
finding: {
|
|
1755
|
+
severity: "warning",
|
|
1756
|
+
rule: "response-pii-leak",
|
|
1757
|
+
title: "PII Leak in Response",
|
|
1758
|
+
desc: `${ep} \u2014 exposes PII in response`,
|
|
1759
|
+
hint: this.hint,
|
|
1760
|
+
detail: detailParts.join(". "),
|
|
1761
|
+
endpoint: ep,
|
|
1762
|
+
count: 1
|
|
1763
|
+
}
|
|
1862
1764
|
};
|
|
1863
|
-
|
|
1864
|
-
findings.push(finding);
|
|
1865
|
-
}
|
|
1866
|
-
return findings;
|
|
1765
|
+
});
|
|
1867
1766
|
}
|
|
1868
1767
|
};
|
|
1869
1768
|
|
|
1870
1769
|
// src/analysis/engine.ts
|
|
1871
|
-
|
|
1770
|
+
init_config();
|
|
1872
1771
|
|
|
1873
1772
|
// src/analysis/group.ts
|
|
1874
1773
|
init_constants();
|
|
1875
1774
|
import { randomUUID } from "crypto";
|
|
1775
|
+
init_endpoint();
|
|
1876
1776
|
|
|
1877
1777
|
// src/analysis/label.ts
|
|
1878
1778
|
init_constants();
|
|
1879
1779
|
|
|
1880
1780
|
// src/analysis/transforms.ts
|
|
1881
1781
|
init_constants();
|
|
1782
|
+
init_config();
|
|
1783
|
+
init_endpoint();
|
|
1882
1784
|
|
|
1883
1785
|
// src/analysis/insights/prepare.ts
|
|
1884
1786
|
init_endpoint();
|
|
1885
1787
|
init_constants();
|
|
1886
|
-
|
|
1788
|
+
init_config();
|
|
1887
1789
|
|
|
1888
|
-
// src/analysis/insights/
|
|
1889
|
-
|
|
1890
|
-
|
|
1790
|
+
// src/analysis/insights/runner.ts
|
|
1791
|
+
init_log();
|
|
1792
|
+
init_type_guards();
|
|
1891
1793
|
|
|
1892
|
-
// src/analysis/insights/rules/
|
|
1794
|
+
// src/analysis/insights/rules/query-rules.ts
|
|
1893
1795
|
init_endpoint();
|
|
1894
1796
|
init_constants();
|
|
1895
1797
|
|
|
1896
|
-
// src/analysis/insights/rules/
|
|
1798
|
+
// src/analysis/insights/rules/response-rules.ts
|
|
1897
1799
|
init_endpoint();
|
|
1800
|
+
init_log();
|
|
1801
|
+
init_type_guards();
|
|
1898
1802
|
init_constants();
|
|
1899
1803
|
|
|
1900
|
-
// src/analysis/insights/rules/
|
|
1901
|
-
init_constants();
|
|
1902
|
-
|
|
1903
|
-
// src/analysis/insights/rules/duplicate.ts
|
|
1904
|
-
init_constants();
|
|
1905
|
-
|
|
1906
|
-
// src/analysis/insights/rules/slow.ts
|
|
1907
|
-
init_constants();
|
|
1908
|
-
|
|
1909
|
-
// src/analysis/insights/rules/query-heavy.ts
|
|
1910
|
-
init_constants();
|
|
1911
|
-
|
|
1912
|
-
// src/analysis/insights/rules/select-star.ts
|
|
1913
|
-
init_constants();
|
|
1914
|
-
|
|
1915
|
-
// src/analysis/insights/rules/high-rows.ts
|
|
1804
|
+
// src/analysis/insights/rules/reliability-rules.ts
|
|
1916
1805
|
init_constants();
|
|
1917
1806
|
|
|
1918
|
-
// src/analysis/insights/rules/
|
|
1807
|
+
// src/analysis/insights/rules/pattern-rules.ts
|
|
1919
1808
|
init_endpoint();
|
|
1920
1809
|
init_constants();
|
|
1921
1810
|
|
|
1922
|
-
// src/analysis/insights/rules/large-response.ts
|
|
1923
|
-
init_constants();
|
|
1924
|
-
|
|
1925
|
-
// src/analysis/insights/rules/regression.ts
|
|
1926
|
-
init_constants();
|
|
1927
|
-
|
|
1928
1811
|
// src/analysis/issue-mappers.ts
|
|
1929
1812
|
init_endpoint();
|
|
1930
1813
|
|
|
1931
1814
|
// src/index.ts
|
|
1932
|
-
var VERSION = "0.
|
|
1815
|
+
var VERSION = "0.9.1";
|
|
1933
1816
|
|
|
1934
1817
|
// src/cli/commands/install.ts
|
|
1935
1818
|
init_constants();
|
|
@@ -2141,8 +2024,8 @@ async function setupNuxt(rootDir) {
|
|
|
2141
2024
|
}
|
|
2142
2025
|
const content = BRAKIT_TEMPLATES.nuxt + "\n";
|
|
2143
2026
|
const dir = join3(rootDir, "server/plugins");
|
|
2144
|
-
const { mkdirSync:
|
|
2145
|
-
|
|
2027
|
+
const { mkdirSync: mkdirSync4 } = await import("fs");
|
|
2028
|
+
mkdirSync4(dir, { recursive: true });
|
|
2146
2029
|
await writeFile3(absPath, content);
|
|
2147
2030
|
return { action: "created", file: relPath, content };
|
|
2148
2031
|
}
|
|
@@ -2241,13 +2124,118 @@ function printManualInstructions(framework) {
|
|
|
2241
2124
|
|
|
2242
2125
|
// src/cli/commands/uninstall.ts
|
|
2243
2126
|
import { defineCommand as defineCommand2 } from "citty";
|
|
2244
|
-
import { resolve as resolve4, join as
|
|
2127
|
+
import { resolve as resolve4, join as join5, relative as relative2 } from "path";
|
|
2245
2128
|
import { readFile as readFile5, writeFile as writeFile4, unlink, rm, readdir as readdir2 } from "fs/promises";
|
|
2246
2129
|
import { execSync as execSync2 } from "child_process";
|
|
2247
2130
|
import pc2 from "picocolors";
|
|
2248
2131
|
init_constants();
|
|
2249
2132
|
init_log();
|
|
2250
2133
|
init_type_guards();
|
|
2134
|
+
|
|
2135
|
+
// src/telemetry/index.ts
|
|
2136
|
+
import { platform as platform2, release, arch } from "os";
|
|
2137
|
+
import { spawn } from "child_process";
|
|
2138
|
+
|
|
2139
|
+
// src/telemetry/config.ts
|
|
2140
|
+
init_features();
|
|
2141
|
+
import { homedir as homedir2, platform } from "os";
|
|
2142
|
+
import { join as join4 } from "path";
|
|
2143
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
2144
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2145
|
+
var IS_WINDOWS = platform() === "win32";
|
|
2146
|
+
var CONFIG_DIR = join4(homedir2(), ".brakit");
|
|
2147
|
+
var CONFIG_PATH = join4(CONFIG_DIR, "config.json");
|
|
2148
|
+
function readConfig() {
|
|
2149
|
+
try {
|
|
2150
|
+
if (!existsSync6(CONFIG_PATH)) return null;
|
|
2151
|
+
return JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
|
|
2152
|
+
} catch {
|
|
2153
|
+
return null;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
function writeConfig(config) {
|
|
2157
|
+
try {
|
|
2158
|
+
if (!existsSync6(CONFIG_DIR))
|
|
2159
|
+
mkdirSync3(CONFIG_DIR, { recursive: true, ...IS_WINDOWS ? {} : { mode: DIR_MODE_OWNER_ONLY } });
|
|
2160
|
+
writeFileSync3(
|
|
2161
|
+
CONFIG_PATH,
|
|
2162
|
+
JSON.stringify(config, null, 2) + "\n",
|
|
2163
|
+
IS_WINDOWS ? {} : { mode: FILE_MODE_OWNER_ONLY }
|
|
2164
|
+
);
|
|
2165
|
+
} catch (err) {
|
|
2166
|
+
if (process.env.BRAKIT_DEBUG) {
|
|
2167
|
+
process.stderr.write(`[brakit] config write failed: ${err?.message ?? err}
|
|
2168
|
+
`);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
function getOrCreateConfig() {
|
|
2173
|
+
const existing = readConfig();
|
|
2174
|
+
if (existing && typeof existing.telemetry === "boolean" && existing.anonymousId) {
|
|
2175
|
+
return existing;
|
|
2176
|
+
}
|
|
2177
|
+
const config = { telemetry: true, anonymousId: randomUUID2() };
|
|
2178
|
+
writeConfig(config);
|
|
2179
|
+
return config;
|
|
2180
|
+
}
|
|
2181
|
+
var cachedEnabled = null;
|
|
2182
|
+
function isTelemetryEnabled() {
|
|
2183
|
+
if (cachedEnabled !== null) return cachedEnabled;
|
|
2184
|
+
const env = process.env.BRAKIT_TELEMETRY;
|
|
2185
|
+
if (env !== void 0) {
|
|
2186
|
+
cachedEnabled = env !== "false" && env !== "0" && env !== "off";
|
|
2187
|
+
return cachedEnabled;
|
|
2188
|
+
}
|
|
2189
|
+
cachedEnabled = readConfig()?.telemetry ?? true;
|
|
2190
|
+
return cachedEnabled;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
// src/telemetry/index.ts
|
|
2194
|
+
init_labels();
|
|
2195
|
+
init_config();
|
|
2196
|
+
var POSTHOG_KEY = "phc_E9TwydCGnSfPLIUhNxChpeg32TSowjk31KiPhnLPP0x";
|
|
2197
|
+
function commonProperties() {
|
|
2198
|
+
return {
|
|
2199
|
+
brakit_version: VERSION,
|
|
2200
|
+
node_version: process.version,
|
|
2201
|
+
os: `${platform2()}-${release()}`,
|
|
2202
|
+
arch: arch(),
|
|
2203
|
+
$lib: "brakit",
|
|
2204
|
+
$process_person_profile: false,
|
|
2205
|
+
$geoip_disable: true
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
function sendToPosthog(event, properties) {
|
|
2209
|
+
if (!isTelemetryEnabled()) return;
|
|
2210
|
+
const config = getOrCreateConfig();
|
|
2211
|
+
const payload = {
|
|
2212
|
+
api_key: POSTHOG_KEY,
|
|
2213
|
+
event,
|
|
2214
|
+
distinct_id: config.anonymousId,
|
|
2215
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2216
|
+
properties: { ...commonProperties(), ...properties }
|
|
2217
|
+
};
|
|
2218
|
+
try {
|
|
2219
|
+
const body = JSON.stringify(payload);
|
|
2220
|
+
const url = `${POSTHOG_HOST}${POSTHOG_CAPTURE_PATH}`;
|
|
2221
|
+
const child = spawn(
|
|
2222
|
+
process.execPath,
|
|
2223
|
+
[
|
|
2224
|
+
"-e",
|
|
2225
|
+
`fetch(${JSON.stringify(url)},{method:"POST",headers:{"content-type":"application/json"},body:${JSON.stringify(body)},signal:AbortSignal.timeout(${POSTHOG_REQUEST_TIMEOUT_MS})}).catch(()=>{})`
|
|
2226
|
+
],
|
|
2227
|
+
{ detached: true, stdio: "ignore" }
|
|
2228
|
+
);
|
|
2229
|
+
child.unref();
|
|
2230
|
+
} catch {
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
function trackEvent(event, properties) {
|
|
2234
|
+
sendToPosthog(event, { sdk: "node", ...properties });
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/cli/commands/uninstall.ts
|
|
2238
|
+
init_config();
|
|
2251
2239
|
var PREPENDED_FILES = [
|
|
2252
2240
|
"app/entry.server.tsx",
|
|
2253
2241
|
"app/entry.server.ts",
|
|
@@ -2282,16 +2270,20 @@ var uninstall_default = defineCommand2({
|
|
|
2282
2270
|
console.log();
|
|
2283
2271
|
console.log(pc2.bold(" \u25C6 brakit uninstall"));
|
|
2284
2272
|
console.log();
|
|
2273
|
+
let anyInstrumentationRemoved = false;
|
|
2274
|
+
let anyPackageRemoved = false;
|
|
2285
2275
|
for (const project of projects) {
|
|
2286
2276
|
const suffix = projects.length > 1 ? ` in ${relative2(rootDir, project.dir) || "."}` : "";
|
|
2287
2277
|
const removed = await removeInstrumentation(project.dir);
|
|
2288
2278
|
if (removed) {
|
|
2279
|
+
anyInstrumentationRemoved = true;
|
|
2289
2280
|
console.log(pc2.green(` \u2713 ${removed}${suffix}`));
|
|
2290
2281
|
} else {
|
|
2291
2282
|
console.log(pc2.dim(` No brakit instrumentation files found${suffix}.`));
|
|
2292
2283
|
}
|
|
2293
2284
|
const uninstalled = await uninstallPackage(project.dir, project.pm);
|
|
2294
2285
|
if (uninstalled === true) {
|
|
2286
|
+
anyPackageRemoved = true;
|
|
2295
2287
|
console.log(pc2.green(` \u2713 Removed brakit from devDependencies${suffix}`));
|
|
2296
2288
|
} else if (uninstalled === "failed") {
|
|
2297
2289
|
}
|
|
@@ -2312,6 +2304,12 @@ var uninstall_default = defineCommand2({
|
|
|
2312
2304
|
if (cacheCleared) {
|
|
2313
2305
|
console.log(pc2.green(" \u2713 Cleared build cache"));
|
|
2314
2306
|
}
|
|
2307
|
+
trackEvent(TELEMETRY_EVENT_CLI_UNINSTALL, {
|
|
2308
|
+
instrumentation_removed: anyInstrumentationRemoved,
|
|
2309
|
+
package_removed: anyPackageRemoved,
|
|
2310
|
+
mcp_removed: mcpRemoved,
|
|
2311
|
+
data_removed: dataRemoved
|
|
2312
|
+
});
|
|
2315
2313
|
console.log();
|
|
2316
2314
|
}
|
|
2317
2315
|
});
|
|
@@ -2322,7 +2320,7 @@ async function removeInstrumentation(projectDir) {
|
|
|
2322
2320
|
}
|
|
2323
2321
|
const candidates = [...PREPENDED_FILES];
|
|
2324
2322
|
try {
|
|
2325
|
-
const pkgRaw = await readFile5(
|
|
2323
|
+
const pkgRaw = await readFile5(join5(projectDir, "package.json"), "utf-8");
|
|
2326
2324
|
const pkg = JSON.parse(pkgRaw);
|
|
2327
2325
|
if (pkg.main) candidates.unshift(pkg.main);
|
|
2328
2326
|
} catch (err) {
|
|
@@ -2337,7 +2335,7 @@ async function removeInstrumentation(projectDir) {
|
|
|
2337
2335
|
return null;
|
|
2338
2336
|
}
|
|
2339
2337
|
async function tryRemoveBrakitFromFile(projectDir, relPath) {
|
|
2340
|
-
const absPath =
|
|
2338
|
+
const absPath = join5(projectDir, relPath);
|
|
2341
2339
|
if (!await fileExists(absPath)) return null;
|
|
2342
2340
|
const content = await readFile5(absPath, "utf-8");
|
|
2343
2341
|
if (!content.includes("brakit")) return null;
|
|
@@ -2354,7 +2352,7 @@ async function tryRemoveBrakitFromFile(projectDir, relPath) {
|
|
|
2354
2352
|
return null;
|
|
2355
2353
|
}
|
|
2356
2354
|
async function tryRemoveImportLine(projectDir, relPath) {
|
|
2357
|
-
const absPath =
|
|
2355
|
+
const absPath = join5(projectDir, relPath);
|
|
2358
2356
|
if (!await fileExists(absPath)) return null;
|
|
2359
2357
|
const content = await readFile5(absPath, "utf-8");
|
|
2360
2358
|
if (!content.includes(IMPORT_LINE)) return null;
|
|
@@ -2365,7 +2363,7 @@ async function tryRemoveImportLine(projectDir, relPath) {
|
|
|
2365
2363
|
async function fallbackSearchAndRemove(projectDir) {
|
|
2366
2364
|
const dirsToScan = FALLBACK_SCAN_DIRS;
|
|
2367
2365
|
for (const dir of dirsToScan) {
|
|
2368
|
-
const absDir =
|
|
2366
|
+
const absDir = join5(projectDir, dir);
|
|
2369
2367
|
if (!await fileExists(absDir)) continue;
|
|
2370
2368
|
let entries;
|
|
2371
2369
|
try {
|
|
@@ -2378,7 +2376,7 @@ async function fallbackSearchAndRemove(projectDir) {
|
|
|
2378
2376
|
const ext = entry.slice(entry.lastIndexOf("."));
|
|
2379
2377
|
if (!SUPPORTED_SOURCE_EXTENSIONS.has(ext)) continue;
|
|
2380
2378
|
const relPath = dir === "." ? entry : `${dir}/${entry}`;
|
|
2381
|
-
const absPath =
|
|
2379
|
+
const absPath = join5(projectDir, relPath);
|
|
2382
2380
|
try {
|
|
2383
2381
|
const content = await readFile5(absPath, "utf-8");
|
|
2384
2382
|
if (!containsBrakitImport(content)) continue;
|
|
@@ -2401,7 +2399,7 @@ async function fallbackSearchAndRemove(projectDir) {
|
|
|
2401
2399
|
return null;
|
|
2402
2400
|
}
|
|
2403
2401
|
async function removeMcpConfig(rootDir) {
|
|
2404
|
-
const mcpPath =
|
|
2402
|
+
const mcpPath = join5(rootDir, ".mcp.json");
|
|
2405
2403
|
if (!await fileExists(mcpPath)) return false;
|
|
2406
2404
|
try {
|
|
2407
2405
|
const raw = await readFile5(mcpPath, "utf-8");
|
|
@@ -2421,7 +2419,7 @@ async function removeMcpConfig(rootDir) {
|
|
|
2421
2419
|
}
|
|
2422
2420
|
async function uninstallPackage(rootDir, pm) {
|
|
2423
2421
|
try {
|
|
2424
|
-
const pkgRaw = await readFile5(
|
|
2422
|
+
const pkgRaw = await readFile5(join5(rootDir, "package.json"), "utf-8");
|
|
2425
2423
|
const pkg = JSON.parse(pkgRaw);
|
|
2426
2424
|
if (!pkg.devDependencies?.brakit && !pkg.dependencies?.brakit) return false;
|
|
2427
2425
|
} catch (err) {
|
|
@@ -2445,7 +2443,7 @@ async function uninstallPackage(rootDir, pm) {
|
|
|
2445
2443
|
}
|
|
2446
2444
|
async function removeBrakitData(rootDir) {
|
|
2447
2445
|
let removed = false;
|
|
2448
|
-
const projectDir =
|
|
2446
|
+
const projectDir = join5(rootDir, METRICS_DIR);
|
|
2449
2447
|
if (await fileExists(projectDir)) {
|
|
2450
2448
|
try {
|
|
2451
2449
|
await rm(projectDir, { recursive: true, force: true });
|
|
@@ -2466,7 +2464,7 @@ async function removeBrakitData(rootDir) {
|
|
|
2466
2464
|
return removed;
|
|
2467
2465
|
}
|
|
2468
2466
|
async function cleanGitignore(rootDir) {
|
|
2469
|
-
const gitignorePath =
|
|
2467
|
+
const gitignorePath = join5(rootDir, ".gitignore");
|
|
2470
2468
|
if (!await fileExists(gitignorePath)) return false;
|
|
2471
2469
|
try {
|
|
2472
2470
|
const content = await readFile5(gitignorePath, "utf-8");
|
|
@@ -2483,7 +2481,7 @@ async function cleanGitignore(rootDir) {
|
|
|
2483
2481
|
async function clearBuildCaches(rootDir) {
|
|
2484
2482
|
let cleared = false;
|
|
2485
2483
|
for (const dir of BUILD_CACHE_DIRS) {
|
|
2486
|
-
const absDir =
|
|
2484
|
+
const absDir = join5(rootDir, dir);
|
|
2487
2485
|
if (!await fileExists(absDir)) continue;
|
|
2488
2486
|
try {
|
|
2489
2487
|
await rm(absDir, { recursive: true, force: true });
|
|
@@ -2496,7 +2494,15 @@ async function clearBuildCaches(rootDir) {
|
|
|
2496
2494
|
}
|
|
2497
2495
|
|
|
2498
2496
|
// bin/brakit.ts
|
|
2497
|
+
init_config();
|
|
2499
2498
|
var sub = process.argv[2];
|
|
2499
|
+
var command = sub === "uninstall" ? "uninstall" : sub === "mcp" ? "mcp" : "install";
|
|
2500
|
+
var cwd = process.cwd();
|
|
2501
|
+
trackEvent(TELEMETRY_EVENT_CLI_INVOKED, {
|
|
2502
|
+
command,
|
|
2503
|
+
has_package_json: existsSync7(resolve6(cwd, "package.json")),
|
|
2504
|
+
cwd_has_node_modules: existsSync7(resolve6(cwd, "node_modules"))
|
|
2505
|
+
});
|
|
2500
2506
|
if (sub === "uninstall") {
|
|
2501
2507
|
process.argv.splice(2, 1);
|
|
2502
2508
|
runMain(uninstall_default);
|