brakit 0.8.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/dist/api.d.ts +83 -46
- package/dist/api.js +776 -767
- package/dist/bin/brakit.js +305 -432
- 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 +1566 -1700
- 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;
|
|
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,13 @@ 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"]);
|
|
25
32
|
}
|
|
26
33
|
});
|
|
27
34
|
|
|
@@ -40,17 +47,6 @@ var init_log = __esm({
|
|
|
40
47
|
}
|
|
41
48
|
});
|
|
42
49
|
|
|
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
50
|
// src/utils/type-guards.ts
|
|
55
51
|
function isNonEmptyString(val) {
|
|
56
52
|
return typeof val === "string" && val.trim().length > 0;
|
|
@@ -69,35 +65,14 @@ function isValidAiFixStatus(val) {
|
|
|
69
65
|
var init_type_guards = __esm({
|
|
70
66
|
"src/utils/type-guards.ts"() {
|
|
71
67
|
"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;
|
|
68
|
+
init_config();
|
|
94
69
|
}
|
|
95
70
|
});
|
|
96
71
|
|
|
97
|
-
// src/constants/
|
|
72
|
+
// src/constants/labels.ts
|
|
98
73
|
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/
|
|
74
|
+
var init_labels = __esm({
|
|
75
|
+
"src/constants/labels.ts"() {
|
|
101
76
|
"use strict";
|
|
102
77
|
DASHBOARD_PREFIX = "/__brakit";
|
|
103
78
|
DASHBOARD_API_REQUESTS = `${DASHBOARD_PREFIX}/api/requests`;
|
|
@@ -132,75 +107,10 @@ var init_routes = __esm({
|
|
|
132
107
|
}
|
|
133
108
|
});
|
|
134
109
|
|
|
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"() {
|
|
110
|
+
// src/constants/features.ts
|
|
111
|
+
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;
|
|
112
|
+
var init_features = __esm({
|
|
113
|
+
"src/constants/features.ts"() {
|
|
204
114
|
"use strict";
|
|
205
115
|
SUPPORTED_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
206
116
|
".ts",
|
|
@@ -212,20 +122,20 @@ var init_cli = __esm({
|
|
|
212
122
|
]);
|
|
213
123
|
BUILD_CACHE_DIRS = [".next", ".nuxt", ".output"];
|
|
214
124
|
FALLBACK_SCAN_DIRS = ["src", "."];
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
125
|
+
MCP_SERVER_NAME = "brakit";
|
|
126
|
+
INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
|
|
127
|
+
LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
128
|
+
CLIENT_FETCH_TIMEOUT_MS = 1e4;
|
|
129
|
+
HEALTH_CHECK_TIMEOUT_MS = 3e3;
|
|
130
|
+
DISCOVERY_POLL_INTERVAL_MS = 500;
|
|
131
|
+
MAX_DISCOVERY_DEPTH = 5;
|
|
132
|
+
MAX_TIMELINE_EVENTS = 20;
|
|
133
|
+
MAX_RESOLVED_DISPLAY = 5;
|
|
134
|
+
ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
135
|
+
MCP_SERVER_VERSION = "0.9.0";
|
|
136
|
+
RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
137
|
+
PORT_MIN = 1;
|
|
138
|
+
PORT_MAX = 65535;
|
|
229
139
|
}
|
|
230
140
|
});
|
|
231
141
|
|
|
@@ -233,21 +143,9 @@ var init_sdk_events = __esm({
|
|
|
233
143
|
var init_constants = __esm({
|
|
234
144
|
"src/constants/index.ts"() {
|
|
235
145
|
"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();
|
|
146
|
+
init_config();
|
|
147
|
+
init_labels();
|
|
148
|
+
init_features();
|
|
251
149
|
}
|
|
252
150
|
});
|
|
253
151
|
|
|
@@ -270,8 +168,8 @@ var BrakitClient;
|
|
|
270
168
|
var init_client = __esm({
|
|
271
169
|
"src/mcp/client.ts"() {
|
|
272
170
|
"use strict";
|
|
273
|
-
|
|
274
|
-
|
|
171
|
+
init_labels();
|
|
172
|
+
init_features();
|
|
275
173
|
BrakitClient = class {
|
|
276
174
|
constructor(baseUrl) {
|
|
277
175
|
this.baseUrl = baseUrl;
|
|
@@ -438,7 +336,7 @@ var init_discovery = __esm({
|
|
|
438
336
|
"use strict";
|
|
439
337
|
init_constants();
|
|
440
338
|
init_log();
|
|
441
|
-
|
|
339
|
+
init_features();
|
|
442
340
|
}
|
|
443
341
|
});
|
|
444
342
|
|
|
@@ -539,7 +437,7 @@ async function buildRequestDetail(client, req) {
|
|
|
539
437
|
var init_enrichment = __esm({
|
|
540
438
|
"src/mcp/enrichment.ts"() {
|
|
541
439
|
"use strict";
|
|
542
|
-
|
|
440
|
+
init_features();
|
|
543
441
|
init_endpoint();
|
|
544
442
|
}
|
|
545
443
|
});
|
|
@@ -550,7 +448,7 @@ var init_get_findings = __esm({
|
|
|
550
448
|
"src/mcp/tools/get-findings.ts"() {
|
|
551
449
|
"use strict";
|
|
552
450
|
init_enrichment();
|
|
553
|
-
|
|
451
|
+
init_config();
|
|
554
452
|
init_type_guards();
|
|
555
453
|
getFindings = {
|
|
556
454
|
name: "get_findings",
|
|
@@ -665,7 +563,7 @@ var getRequestDetail;
|
|
|
665
563
|
var init_get_request_detail = __esm({
|
|
666
564
|
"src/mcp/tools/get-request-detail.ts"() {
|
|
667
565
|
"use strict";
|
|
668
|
-
|
|
566
|
+
init_features();
|
|
669
567
|
init_enrichment();
|
|
670
568
|
getRequestDetail = {
|
|
671
569
|
name: "get_request_detail",
|
|
@@ -845,7 +743,7 @@ var getReport;
|
|
|
845
743
|
var init_get_report = __esm({
|
|
846
744
|
"src/mcp/tools/get-report.ts"() {
|
|
847
745
|
"use strict";
|
|
848
|
-
|
|
746
|
+
init_features();
|
|
849
747
|
getReport = {
|
|
850
748
|
name: "get_report",
|
|
851
749
|
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,7 +1040,7 @@ var init_server = __esm({
|
|
|
1142
1040
|
init_client();
|
|
1143
1041
|
init_discovery();
|
|
1144
1042
|
init_tools();
|
|
1145
|
-
|
|
1043
|
+
init_features();
|
|
1146
1044
|
init_prompts();
|
|
1147
1045
|
}
|
|
1148
1046
|
});
|
|
@@ -1164,7 +1062,7 @@ import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync }
|
|
|
1164
1062
|
import { resolve as resolve2 } from "path";
|
|
1165
1063
|
|
|
1166
1064
|
// src/utils/fs.ts
|
|
1167
|
-
|
|
1065
|
+
init_config();
|
|
1168
1066
|
init_log();
|
|
1169
1067
|
init_type_guards();
|
|
1170
1068
|
import { access, readFile, writeFile } from "fs/promises";
|
|
@@ -1187,10 +1085,7 @@ async function fileExists(path) {
|
|
|
1187
1085
|
}
|
|
1188
1086
|
|
|
1189
1087
|
// src/store/issue-store.ts
|
|
1190
|
-
|
|
1191
|
-
init_limits();
|
|
1192
|
-
init_thresholds();
|
|
1193
|
-
init_limits();
|
|
1088
|
+
init_config();
|
|
1194
1089
|
|
|
1195
1090
|
// src/utils/atomic-writer.ts
|
|
1196
1091
|
import {
|
|
@@ -1208,7 +1103,7 @@ init_log();
|
|
|
1208
1103
|
init_type_guards();
|
|
1209
1104
|
|
|
1210
1105
|
// src/utils/issue-id.ts
|
|
1211
|
-
|
|
1106
|
+
init_config();
|
|
1212
1107
|
import { createHash as createHash2 } from "crypto";
|
|
1213
1108
|
|
|
1214
1109
|
// src/detect/project.ts
|
|
@@ -1378,12 +1273,13 @@ async function detectInDir(dir, rootDir, projects) {
|
|
|
1378
1273
|
}
|
|
1379
1274
|
|
|
1380
1275
|
// src/utils/response.ts
|
|
1381
|
-
|
|
1276
|
+
init_config();
|
|
1277
|
+
var MAX_WRAPPER_KEYS = 3;
|
|
1382
1278
|
function unwrapResponse(parsed) {
|
|
1383
1279
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
|
|
1384
1280
|
const obj = parsed;
|
|
1385
1281
|
const keys = Object.keys(obj);
|
|
1386
|
-
if (keys.length >
|
|
1282
|
+
if (keys.length > MAX_WRAPPER_KEYS) return parsed;
|
|
1387
1283
|
let best = null;
|
|
1388
1284
|
let bestSize = 0;
|
|
1389
1285
|
for (const key of keys) {
|
|
@@ -1402,6 +1298,10 @@ function unwrapResponse(parsed) {
|
|
|
1402
1298
|
return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
|
|
1403
1299
|
}
|
|
1404
1300
|
|
|
1301
|
+
// src/analysis/rules/scanner.ts
|
|
1302
|
+
init_log();
|
|
1303
|
+
init_type_guards();
|
|
1304
|
+
|
|
1405
1305
|
// src/analysis/rules/patterns.ts
|
|
1406
1306
|
var SECRET_KEYS = /^(password|passwd|secret|api_key|apiKey|api_secret|apiSecret|private_key|privateKey|client_secret|clientSecret)$/;
|
|
1407
1307
|
var TOKEN_PARAMS = /^(token|api_key|apiKey|secret|password|access_token|session_id|sessionId)$/;
|
|
@@ -1428,8 +1328,8 @@ var RULE_HINTS = {
|
|
|
1428
1328
|
"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
1329
|
};
|
|
1430
1330
|
|
|
1431
|
-
// src/analysis/rules/
|
|
1432
|
-
|
|
1331
|
+
// src/analysis/rules/auth-rules.ts
|
|
1332
|
+
init_config();
|
|
1433
1333
|
|
|
1434
1334
|
// src/utils/http-status.ts
|
|
1435
1335
|
function isErrorStatus(code) {
|
|
@@ -1439,27 +1339,66 @@ function isRedirect(code) {
|
|
|
1439
1339
|
return code >= 300 && code < 400;
|
|
1440
1340
|
}
|
|
1441
1341
|
|
|
1442
|
-
// src/
|
|
1443
|
-
function
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1342
|
+
// src/utils/collections.ts
|
|
1343
|
+
function deduplicateFindings(items, extract) {
|
|
1344
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1345
|
+
const findings = [];
|
|
1346
|
+
for (const item of items) {
|
|
1347
|
+
const result = extract(item);
|
|
1348
|
+
if (!result) continue;
|
|
1349
|
+
const existing = seen.get(result.key);
|
|
1350
|
+
if (existing) {
|
|
1351
|
+
existing.count++;
|
|
1352
|
+
continue;
|
|
1450
1353
|
}
|
|
1451
|
-
|
|
1354
|
+
seen.set(result.key, result.finding);
|
|
1355
|
+
findings.push(result.finding);
|
|
1452
1356
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1357
|
+
return findings;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// src/utils/object-scan.ts
|
|
1361
|
+
init_config();
|
|
1362
|
+
var DEFAULTS = {
|
|
1363
|
+
maxDepth: MAX_OBJECT_SCAN_DEPTH,
|
|
1364
|
+
arrayLimit: SECRET_SCAN_ARRAY_LIMIT
|
|
1365
|
+
};
|
|
1366
|
+
function walkObject(obj, visitor, options) {
|
|
1367
|
+
const opts = { ...DEFAULTS, ...options };
|
|
1368
|
+
walk(obj, visitor, opts, 0);
|
|
1369
|
+
}
|
|
1370
|
+
function walk(obj, visitor, opts, depth) {
|
|
1371
|
+
if (depth >= opts.maxDepth) return;
|
|
1372
|
+
if (!obj || typeof obj !== "object") return;
|
|
1373
|
+
if (Array.isArray(obj)) {
|
|
1374
|
+
for (let i = 0; i < Math.min(obj.length, opts.arrayLimit); i++) {
|
|
1375
|
+
walk(obj[i], visitor, opts, depth + 1);
|
|
1457
1376
|
}
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
for (const key of Object.keys(obj)) {
|
|
1380
|
+
const val = obj[key];
|
|
1381
|
+
visitor(key, val, depth);
|
|
1458
1382
|
if (typeof val === "object" && val !== null) {
|
|
1459
|
-
|
|
1383
|
+
walk(val, visitor, opts, depth + 1);
|
|
1460
1384
|
}
|
|
1461
1385
|
}
|
|
1462
|
-
|
|
1386
|
+
}
|
|
1387
|
+
function collectFromObject(obj, match, options) {
|
|
1388
|
+
const results = [];
|
|
1389
|
+
walkObject(obj, (key, value) => {
|
|
1390
|
+
const result = match(key, value);
|
|
1391
|
+
if (result !== null) results.push(result);
|
|
1392
|
+
}, options);
|
|
1393
|
+
return results;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/analysis/rules/auth-rules.ts
|
|
1397
|
+
function findSecretKeys(obj) {
|
|
1398
|
+
return collectFromObject(
|
|
1399
|
+
obj,
|
|
1400
|
+
(key, val) => SECRET_KEYS.test(key) && typeof val === "string" && val.length >= MIN_SECRET_VALUE_LENGTH && !MASKED_RE.test(val) ? key : null
|
|
1401
|
+
);
|
|
1463
1402
|
}
|
|
1464
1403
|
var exposedSecretRule = {
|
|
1465
1404
|
id: "exposed-secret",
|
|
@@ -1467,50 +1406,39 @@ var exposedSecretRule = {
|
|
|
1467
1406
|
name: "Exposed Secret in Response",
|
|
1468
1407
|
hint: RULE_HINTS["exposed-secret"],
|
|
1469
1408
|
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
|
|
1409
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1410
|
+
if (isErrorStatus(request.statusCode)) return null;
|
|
1411
|
+
const parsed = ctx.parsedBodies.response.get(request.id);
|
|
1412
|
+
if (!parsed) return null;
|
|
1413
|
+
const keys = findSecretKeys(parsed);
|
|
1414
|
+
if (keys.length === 0) return null;
|
|
1415
|
+
const ep = `${request.method} ${request.path}`;
|
|
1416
|
+
return {
|
|
1417
|
+
key: `${ep}:${keys.sort().join(",")}`,
|
|
1418
|
+
finding: {
|
|
1419
|
+
severity: "critical",
|
|
1420
|
+
rule: "exposed-secret",
|
|
1421
|
+
title: "Exposed Secret in Response",
|
|
1422
|
+
desc: `${ep} \u2014 response contains ${keys.join(", ")} field${keys.length > 1 ? "s" : ""}`,
|
|
1423
|
+
hint: this.hint,
|
|
1424
|
+
detail: `Exposed fields: ${keys.join(", ")}. ${keys.length} unmasked secret value${keys.length !== 1 ? "s" : ""} in response body.`,
|
|
1425
|
+
endpoint: ep,
|
|
1426
|
+
count: 1
|
|
1427
|
+
}
|
|
1493
1428
|
};
|
|
1494
|
-
|
|
1495
|
-
findings.push(finding);
|
|
1496
|
-
}
|
|
1497
|
-
return findings;
|
|
1429
|
+
});
|
|
1498
1430
|
}
|
|
1499
1431
|
};
|
|
1500
|
-
|
|
1501
|
-
// src/analysis/rules/token-in-url.ts
|
|
1502
1432
|
var tokenInUrlRule = {
|
|
1503
1433
|
id: "token-in-url",
|
|
1504
1434
|
severity: "critical",
|
|
1505
1435
|
name: "Auth Token in URL",
|
|
1506
1436
|
hint: RULE_HINTS["token-in-url"],
|
|
1507
1437
|
check(ctx) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const
|
|
1512
|
-
if (qIdx === -1) continue;
|
|
1513
|
-
const params = r.url.substring(qIdx + 1).split("&");
|
|
1438
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1439
|
+
const qIdx = request.url.indexOf("?");
|
|
1440
|
+
if (qIdx === -1) return null;
|
|
1441
|
+
const params = request.url.substring(qIdx + 1).split("&");
|
|
1514
1442
|
const flagged = [];
|
|
1515
1443
|
for (const param of params) {
|
|
1516
1444
|
const [name, ...rest] = param.split("=");
|
|
@@ -1520,65 +1448,129 @@ var tokenInUrlRule = {
|
|
|
1520
1448
|
flagged.push(name);
|
|
1521
1449
|
}
|
|
1522
1450
|
}
|
|
1523
|
-
if (flagged.length === 0)
|
|
1524
|
-
const ep = `${
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1451
|
+
if (flagged.length === 0) return null;
|
|
1452
|
+
const ep = `${request.method} ${request.path}`;
|
|
1453
|
+
return {
|
|
1454
|
+
key: `${ep}:${flagged.sort().join(",")}`,
|
|
1455
|
+
finding: {
|
|
1456
|
+
severity: "critical",
|
|
1457
|
+
rule: "token-in-url",
|
|
1458
|
+
title: "Auth Token in URL",
|
|
1459
|
+
desc: `${ep} \u2014 ${flagged.join(", ")} exposed in query string`,
|
|
1460
|
+
hint: this.hint,
|
|
1461
|
+
detail: `Parameters in URL: ${flagged.join(", ")}. Auth tokens in URLs are logged by proxies, browsers, and CDNs.`,
|
|
1462
|
+
endpoint: ep,
|
|
1463
|
+
count: 1
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
function isFrameworkResponse(request) {
|
|
1470
|
+
if (isRedirect(request.statusCode)) return true;
|
|
1471
|
+
if (request.path?.startsWith("/__")) return true;
|
|
1472
|
+
if (request.responseHeaders?.["x-middleware-rewrite"]) return true;
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1475
|
+
var insecureCookieRule = {
|
|
1476
|
+
id: "insecure-cookie",
|
|
1477
|
+
severity: "warning",
|
|
1478
|
+
name: "Insecure Cookie",
|
|
1479
|
+
hint: RULE_HINTS["insecure-cookie"],
|
|
1480
|
+
check(ctx) {
|
|
1481
|
+
const cookieEntries = [];
|
|
1482
|
+
for (const request of ctx.requests) {
|
|
1483
|
+
if (!request.responseHeaders) continue;
|
|
1484
|
+
if (isFrameworkResponse(request)) continue;
|
|
1485
|
+
const setCookie = request.responseHeaders["set-cookie"];
|
|
1486
|
+
if (!setCookie) continue;
|
|
1487
|
+
const cookies = setCookie.split(/,(?=\s*[A-Za-z0-9_\-]+=)/);
|
|
1488
|
+
for (const cookie of cookies) {
|
|
1489
|
+
cookieEntries.push({ cookie });
|
|
1530
1490
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1491
|
+
}
|
|
1492
|
+
return deduplicateFindings(cookieEntries, ({ cookie }) => {
|
|
1493
|
+
const cookieName = cookie.trim().split("=")[0].trim();
|
|
1494
|
+
const lower = cookie.toLowerCase();
|
|
1495
|
+
const issues = [];
|
|
1496
|
+
if (!lower.includes("httponly")) issues.push("HttpOnly");
|
|
1497
|
+
if (!lower.includes("samesite")) issues.push("SameSite");
|
|
1498
|
+
if (issues.length === 0) return null;
|
|
1499
|
+
return {
|
|
1500
|
+
key: `${cookieName}:${issues.join(",")}`,
|
|
1501
|
+
finding: {
|
|
1502
|
+
severity: "warning",
|
|
1503
|
+
rule: "insecure-cookie",
|
|
1504
|
+
title: "Insecure Cookie",
|
|
1505
|
+
desc: `${cookieName} \u2014 missing ${issues.join(", ")} flag${issues.length > 1 ? "s" : ""}`,
|
|
1506
|
+
hint: this.hint,
|
|
1507
|
+
detail: `Missing: ${issues.join(", ")}. ${issues.includes("HttpOnly") ? "Cookie accessible via JavaScript (XSS risk). " : ""}${issues.includes("SameSite") ? "Cookie sent on cross-site requests (CSRF risk)." : ""}`,
|
|
1508
|
+
endpoint: cookieName,
|
|
1509
|
+
count: 1
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
var corsCredentialsRule = {
|
|
1516
|
+
id: "cors-credentials",
|
|
1517
|
+
severity: "warning",
|
|
1518
|
+
name: "CORS Credentials with Wildcard",
|
|
1519
|
+
hint: RULE_HINTS["cors-credentials"],
|
|
1520
|
+
check(ctx) {
|
|
1521
|
+
const findings = [];
|
|
1522
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1523
|
+
for (const request of ctx.requests) {
|
|
1524
|
+
if (!request.responseHeaders) continue;
|
|
1525
|
+
const origin = request.responseHeaders["access-control-allow-origin"];
|
|
1526
|
+
const creds = request.responseHeaders["access-control-allow-credentials"];
|
|
1527
|
+
if (origin !== "*" || creds !== "true") continue;
|
|
1528
|
+
const ep = `${request.method} ${request.path}`;
|
|
1529
|
+
if (seen.has(ep)) continue;
|
|
1530
|
+
seen.add(ep);
|
|
1531
|
+
findings.push({
|
|
1532
|
+
severity: "warning",
|
|
1533
|
+
rule: "cors-credentials",
|
|
1534
|
+
title: "CORS Credentials with Wildcard",
|
|
1535
|
+
desc: `${ep} \u2014 credentials:true with origin:* (browser will reject)`,
|
|
1536
1536
|
hint: this.hint,
|
|
1537
1537
|
endpoint: ep,
|
|
1538
1538
|
count: 1
|
|
1539
|
-
};
|
|
1540
|
-
seen.set(dedupKey, finding);
|
|
1541
|
-
findings.push(finding);
|
|
1539
|
+
});
|
|
1542
1540
|
}
|
|
1543
1541
|
return findings;
|
|
1544
1542
|
}
|
|
1545
1543
|
};
|
|
1546
1544
|
|
|
1547
|
-
// src/analysis/rules/
|
|
1545
|
+
// src/analysis/rules/data-rules.ts
|
|
1546
|
+
init_config();
|
|
1548
1547
|
var stackTraceLeakRule = {
|
|
1549
1548
|
id: "stack-trace-leak",
|
|
1550
1549
|
severity: "critical",
|
|
1551
1550
|
name: "Stack Trace Leaked to Client",
|
|
1552
1551
|
hint: RULE_HINTS["stack-trace-leak"],
|
|
1553
1552
|
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
|
|
1553
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1554
|
+
if (!request.responseBody) return null;
|
|
1555
|
+
if (!STACK_TRACE_RE.test(request.responseBody)) return null;
|
|
1556
|
+
const ep = `${request.method} ${request.path}`;
|
|
1557
|
+
const firstLine = request.responseBody.split("\n").find((l) => STACK_TRACE_RE.test(l))?.trim() ?? "";
|
|
1558
|
+
return {
|
|
1559
|
+
key: ep,
|
|
1560
|
+
finding: {
|
|
1561
|
+
severity: "critical",
|
|
1562
|
+
rule: "stack-trace-leak",
|
|
1563
|
+
title: "Stack Trace Leaked to Client",
|
|
1564
|
+
desc: `${ep} \u2014 response exposes internal stack trace`,
|
|
1565
|
+
hint: this.hint,
|
|
1566
|
+
detail: firstLine ? `Stack trace: ${firstLine.slice(0, 120)}` : void 0,
|
|
1567
|
+
endpoint: ep,
|
|
1568
|
+
count: 1
|
|
1569
|
+
}
|
|
1573
1570
|
};
|
|
1574
|
-
|
|
1575
|
-
findings.push(finding);
|
|
1576
|
-
}
|
|
1577
|
-
return findings;
|
|
1571
|
+
});
|
|
1578
1572
|
}
|
|
1579
1573
|
};
|
|
1580
|
-
|
|
1581
|
-
// src/analysis/rules/error-info-leak.ts
|
|
1582
1574
|
var CRITICAL_PATTERNS = [
|
|
1583
1575
|
{ re: DB_CONN_RE, label: "database connection string" },
|
|
1584
1576
|
{ re: SQL_FRAGMENT_RE, label: "SQL query fragment" },
|
|
@@ -1590,90 +1582,35 @@ var errorInfoLeakRule = {
|
|
|
1590
1582
|
name: "Sensitive Data in Error Response",
|
|
1591
1583
|
hint: RULE_HINTS["error-info-leak"],
|
|
1592
1584
|
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;
|
|
1585
|
+
const entries = [];
|
|
1586
|
+
for (const request of ctx.requests) {
|
|
1587
|
+
if (request.statusCode < 400) continue;
|
|
1588
|
+
if (!request.responseBody) continue;
|
|
1589
|
+
if (request.responseHeaders["x-nextjs-error"] || request.responseHeaders["x-nextjs-matched-path"]) continue;
|
|
1590
|
+
const ep = `${request.method} ${request.path}`;
|
|
1591
|
+
for (const pattern of CRITICAL_PATTERNS) {
|
|
1592
|
+
if (pattern.re.test(request.responseBody)) {
|
|
1593
|
+
entries.push({ ep, pattern, body: request.responseBody });
|
|
1607
1594
|
}
|
|
1608
|
-
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return deduplicateFindings(entries, ({ ep, pattern }) => {
|
|
1598
|
+
return {
|
|
1599
|
+
key: `${ep}:${pattern.label}`,
|
|
1600
|
+
finding: {
|
|
1609
1601
|
severity: "critical",
|
|
1610
1602
|
rule: "error-info-leak",
|
|
1611
1603
|
title: "Sensitive Data in Error Response",
|
|
1612
|
-
desc: `${ep} \u2014 error response exposes ${
|
|
1604
|
+
desc: `${ep} \u2014 error response exposes ${pattern.label}`,
|
|
1613
1605
|
hint: this.hint,
|
|
1606
|
+
detail: `Detected: ${pattern.label} in error response body`,
|
|
1614
1607
|
endpoint: ep,
|
|
1615
1608
|
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
1609
|
}
|
|
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;
|
|
1610
|
+
};
|
|
1611
|
+
});
|
|
1673
1612
|
}
|
|
1674
1613
|
};
|
|
1675
|
-
|
|
1676
|
-
// src/analysis/rules/sensitive-logs.ts
|
|
1677
1614
|
var sensitiveLogsRule = {
|
|
1678
1615
|
id: "sensitive-logs",
|
|
1679
1616
|
severity: "warning",
|
|
@@ -1698,59 +1635,13 @@ var sensitiveLogsRule = {
|
|
|
1698
1635
|
}];
|
|
1699
1636
|
}
|
|
1700
1637
|
};
|
|
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
1638
|
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;
|
|
1639
|
+
function findEmails(obj) {
|
|
1640
|
+
return collectFromObject(
|
|
1641
|
+
obj,
|
|
1642
|
+
(_key, val) => typeof val === "string" && EMAIL_RE.test(val) ? val : null,
|
|
1643
|
+
{ arrayLimit: PII_SCAN_ARRAY_LIMIT }
|
|
1644
|
+
);
|
|
1754
1645
|
}
|
|
1755
1646
|
function topLevelFieldCount(obj) {
|
|
1756
1647
|
if (Array.isArray(obj)) {
|
|
@@ -1835,101 +1726,83 @@ var responsePiiLeakRule = {
|
|
|
1835
1726
|
name: "PII Leak in Response",
|
|
1836
1727
|
hint: RULE_HINTS["response-pii-leak"],
|
|
1837
1728
|
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
|
-
|
|
1729
|
+
return deduplicateFindings(ctx.requests, (request) => {
|
|
1730
|
+
if (isErrorStatus(request.statusCode)) return null;
|
|
1731
|
+
if (SELF_SERVICE_PATH.test(request.path)) return null;
|
|
1732
|
+
const resJson = ctx.parsedBodies.response.get(request.id);
|
|
1733
|
+
if (!resJson) return null;
|
|
1734
|
+
const reqJson = ctx.parsedBodies.request.get(request.id) ?? null;
|
|
1735
|
+
const detection = detectPII(request.method, reqJson, resJson);
|
|
1736
|
+
if (!detection) return null;
|
|
1737
|
+
const ep = `${request.method} ${request.path}`;
|
|
1738
|
+
const fieldCount = topLevelFieldCount(resJson);
|
|
1739
|
+
const detailParts = [`Pattern: ${REASON_LABELS[detection.reason]}`];
|
|
1740
|
+
if (detection.emailCount > 0) detailParts.push(`${detection.emailCount} email${detection.emailCount !== 1 ? "s" : ""} detected`);
|
|
1741
|
+
if (fieldCount > 0) detailParts.push(`${fieldCount} fields per record`);
|
|
1742
|
+
return {
|
|
1743
|
+
key: ep,
|
|
1744
|
+
finding: {
|
|
1745
|
+
severity: "warning",
|
|
1746
|
+
rule: "response-pii-leak",
|
|
1747
|
+
title: "PII Leak in Response",
|
|
1748
|
+
desc: `${ep} \u2014 exposes PII in response`,
|
|
1749
|
+
hint: this.hint,
|
|
1750
|
+
detail: detailParts.join(". "),
|
|
1751
|
+
endpoint: ep,
|
|
1752
|
+
count: 1
|
|
1753
|
+
}
|
|
1862
1754
|
};
|
|
1863
|
-
|
|
1864
|
-
findings.push(finding);
|
|
1865
|
-
}
|
|
1866
|
-
return findings;
|
|
1755
|
+
});
|
|
1867
1756
|
}
|
|
1868
1757
|
};
|
|
1869
1758
|
|
|
1870
1759
|
// src/analysis/engine.ts
|
|
1871
|
-
|
|
1760
|
+
init_config();
|
|
1872
1761
|
|
|
1873
1762
|
// src/analysis/group.ts
|
|
1874
1763
|
init_constants();
|
|
1875
1764
|
import { randomUUID } from "crypto";
|
|
1765
|
+
init_endpoint();
|
|
1876
1766
|
|
|
1877
1767
|
// src/analysis/label.ts
|
|
1878
1768
|
init_constants();
|
|
1879
1769
|
|
|
1880
1770
|
// src/analysis/transforms.ts
|
|
1881
1771
|
init_constants();
|
|
1772
|
+
init_config();
|
|
1773
|
+
init_endpoint();
|
|
1882
1774
|
|
|
1883
1775
|
// src/analysis/insights/prepare.ts
|
|
1884
1776
|
init_endpoint();
|
|
1885
1777
|
init_constants();
|
|
1886
|
-
|
|
1778
|
+
init_config();
|
|
1887
1779
|
|
|
1888
|
-
// src/analysis/insights/
|
|
1889
|
-
|
|
1890
|
-
|
|
1780
|
+
// src/analysis/insights/runner.ts
|
|
1781
|
+
init_log();
|
|
1782
|
+
init_type_guards();
|
|
1891
1783
|
|
|
1892
|
-
// src/analysis/insights/rules/
|
|
1784
|
+
// src/analysis/insights/rules/query-rules.ts
|
|
1893
1785
|
init_endpoint();
|
|
1894
1786
|
init_constants();
|
|
1895
1787
|
|
|
1896
|
-
// src/analysis/insights/rules/
|
|
1788
|
+
// src/analysis/insights/rules/response-rules.ts
|
|
1897
1789
|
init_endpoint();
|
|
1790
|
+
init_log();
|
|
1791
|
+
init_type_guards();
|
|
1898
1792
|
init_constants();
|
|
1899
1793
|
|
|
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
|
|
1794
|
+
// src/analysis/insights/rules/reliability-rules.ts
|
|
1916
1795
|
init_constants();
|
|
1917
1796
|
|
|
1918
|
-
// src/analysis/insights/rules/
|
|
1797
|
+
// src/analysis/insights/rules/pattern-rules.ts
|
|
1919
1798
|
init_endpoint();
|
|
1920
1799
|
init_constants();
|
|
1921
1800
|
|
|
1922
|
-
// src/analysis/insights/rules/large-response.ts
|
|
1923
|
-
init_constants();
|
|
1924
|
-
|
|
1925
|
-
// src/analysis/insights/rules/regression.ts
|
|
1926
|
-
init_constants();
|
|
1927
|
-
|
|
1928
1801
|
// src/analysis/issue-mappers.ts
|
|
1929
1802
|
init_endpoint();
|
|
1930
1803
|
|
|
1931
1804
|
// src/index.ts
|
|
1932
|
-
var VERSION = "0.
|
|
1805
|
+
var VERSION = "0.9.0";
|
|
1933
1806
|
|
|
1934
1807
|
// src/cli/commands/install.ts
|
|
1935
1808
|
init_constants();
|