brakit 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,38 @@
1
- # Brakit
2
-
3
- **See what your app is actually doing.**
4
-
5
- Every request, query, and security issue — before you ship.
6
-
7
- Open source · Local only · Zero config · 2 dependencies
8
-
9
- [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
10
- [![Node >= 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
11
- [![TypeScript](https://img.shields.io/badge/built%20with-TypeScript-3178c6.svg)](https://typescriptlang.org)
1
+ <h1 align="center"><img src="docs/images/icon.png" height="24" alt="" />&nbsp;&nbsp;Brakit</h1>
2
+
3
+ <p align="center">
4
+ <b>See what your app is actually doing.</b> <br />
5
+ Every request, query, and security issue — before you ship. <br />
6
+ <b>Open source · Local only · Zero config · 2 dependencies</b>
7
+ </p>
8
+
9
+ <h3 align="center">
10
+ <a href="docs/design/architecture.md">Architecture</a> &bull;
11
+ <a href="https://brakit.ai">Website</a> &bull;
12
+ <a href="CONTRIBUTING.md">Contributing</a>
13
+ </h3>
14
+
15
+ <h4 align="center">
16
+ <a href="LICENSE">
17
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" />
18
+ </a>
19
+ <a href="https://nodejs.org">
20
+ <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node >= 18" />
21
+ </a>
22
+ <a href="https://typescriptlang.org">
23
+ <img src="https://img.shields.io/badge/built%20with-TypeScript-3178c6.svg" alt="TypeScript" />
24
+ </a>
25
+ <a href="CONTRIBUTING.md">
26
+ <img src="https://img.shields.io/badge/PRs-Welcome-brightgreen" alt="PRs welcome!" />
27
+ </a>
28
+ </h4>
12
29
 
13
30
  ---
14
31
 
32
+ <p align="center">
33
+ <img width="700" src="docs/images/dashboard.png" alt="Brakit Dashboard" />
34
+ </p>
35
+
15
36
  ## Quick Start
16
37
 
17
38
  ```bash
@@ -28,18 +49,19 @@ Dashboard at `http://localhost:<port>/__brakit`. Insights in the terminal.
28
49
 
29
50
  > **Requirements:** Node.js >= 18 and a project with `package.json`.
30
51
 
31
- [Documentation](https://brakit.ai/docs) · [Website](https://brakit.ai)
32
-
33
52
  ---
34
53
 
35
54
  ## What You Get
36
55
 
37
- - **8 security rules** scanned against live traffic leaked secrets, PII in responses, missing auth, N+1 queries flagged automatically
56
+ - **Live dashboard** at `/__brakit`performance overview, request history, scatter charts, real-time via SSE
57
+ - **8 security rules** scanned against live traffic — leaked secrets, PII in responses, missing auth flags
58
+ - **Time breakdown** — every endpoint shows where time goes: DB, Fetch, or App code
59
+ - **N+1 query detection** — same query pattern repeated 5+ times in a single request
60
+ - **Regression detection** — p95 latency or query count increased vs. previous session
38
61
  - **Action-level visibility** — see "Sign Up" and "Load Dashboard", not 47 raw HTTP requests
39
62
  - **Duplicate detection** — same API called twice? Flagged with redundancy percentage
40
- - **N+1 query detection** — same query pattern repeated 5+ times in a single request? That's an N+1
41
63
  - **Full server tracing** — fetch calls, DB queries, console logs, errors — zero code changes
42
- - **Live dashboard** at `/__brakit` 9 tabs updating in real-time
64
+ - **Response overfetch** large JSON responses with many fields your client doesn't use
43
65
  - **Performance tracking** — health grades and p95 trends across dev sessions
44
66
 
45
67
  ---
@@ -56,16 +78,16 @@ Brakit watches every action your app takes — not raw HTTP noise, but what actu
56
78
 
57
79
  8 high-confidence rules that scan your live traffic and flag real issues — not theoretical ones:
58
80
 
59
- | | Rule | What it catches |
60
- | ------------ | ---------------- | ------------------------------------------------------------------------------- |
61
- | **Critical** | Exposed Secret | Response contains `password`, `api_key`, `client_secret` fields with real values |
62
- | **Critical** | Token in URL | Auth tokens in query parameters instead of headers |
63
- | **Critical** | Stack Trace Leak | Internal stack traces sent to the client |
64
- | **Critical** | Error Info Leak | DB connection strings, SQL queries, or secret values in error responses |
65
- | Warning | PII in Response | API echoes back emails, returns full user records with internal IDs |
66
- | Warning | Insecure Cookie | Missing `HttpOnly` or `SameSite` flags |
67
- | Warning | Sensitive Logs | Passwords, secrets, or token values in console output |
68
- | Warning | CORS + Credentials | `credentials: true` with wildcard origin |
81
+ | | Rule | What it catches |
82
+ | ------------ | ------------------ | -------------------------------------------------------------------------------- |
83
+ | **Critical** | Exposed Secret | Response contains `password`, `api_key`, `client_secret` fields with real values |
84
+ | **Critical** | Token in URL | Auth tokens in query parameters instead of headers |
85
+ | **Critical** | Stack Trace Leak | Internal stack traces sent to the client |
86
+ | **Critical** | Error Info Leak | DB connection strings, SQL queries, or secret values in error responses |
87
+ | Warning | PII in Response | API echoes back emails, returns full user records with internal IDs |
88
+ | Warning | Insecure Cookie | Missing `HttpOnly` or `SameSite` flags |
89
+ | Warning | Sensitive Logs | Passwords, secrets, or token values in console output |
90
+ | Warning | CORS + Credentials | `credentials: true` with wildcard origin |
69
91
 
70
92
  ---
71
93
 
@@ -93,27 +115,27 @@ Instrumentation hooks capture fetch calls, DB queries, console output, and error
93
115
 
94
116
  Brakit never runs in production. 7 independent layers ensure it:
95
117
 
96
- | # | Layer | How it blocks |
97
- |---|---|---|
98
- | 1 | `shouldActivate()` | Checks `NODE_ENV` + 15 cloud/CI env vars |
99
- | 2 | `instrumentation.ts` guard | Its own `NODE_ENV !== 'production'` check |
100
- | 3 | devDependency | Pruned in production builds |
101
- | 4 | `try/catch` on import | Missing module = silent no-op |
102
- | 5 | Localhost-only dashboard | Non-local IPs get 404 on `/__brakit` |
103
- | 6 | `safeWrap` + circuit breaker | 10 errors = brakit self-disables |
104
- | 7 | `BRAKIT_DISABLE=true` | Manual kill switch |
118
+ | # | Layer | How it blocks |
119
+ | --- | ---------------------------- | ----------------------------------------- |
120
+ | 1 | `shouldActivate()` | Checks `NODE_ENV` + 15 cloud/CI env vars |
121
+ | 2 | `instrumentation.ts` guard | Its own `NODE_ENV !== 'production'` check |
122
+ | 3 | devDependency | Pruned in production builds |
123
+ | 4 | `try/catch` on import | Missing module = silent no-op |
124
+ | 5 | Localhost-only dashboard | Non-local IPs get 404 on `/__brakit` |
125
+ | 6 | `safeWrap` + circuit breaker | 10 errors = brakit self-disables |
126
+ | 7 | `BRAKIT_DISABLE=true` | Manual kill switch |
105
127
 
106
128
  ### Supported Frameworks
107
129
 
108
- | Framework | Status |
109
- | ----------- | -------------------------- |
110
- | Next.js | Full support (auto-detect) |
111
- | Remix | Auto-detect |
112
- | Nuxt | Auto-detect |
113
- | Vite | Auto-detect |
114
- | Astro | Auto-detect |
115
- | Express | Auto-detect |
116
- | Fastify | Auto-detect |
130
+ | Framework | Status |
131
+ | --------- | -------------------------- |
132
+ | Next.js | Full support (auto-detect) |
133
+ | Remix | Auto-detect |
134
+ | Nuxt | Auto-detect |
135
+ | Vite | Auto-detect |
136
+ | Astro | Auto-detect |
137
+ | Express | Auto-detect |
138
+ | Fastify | Auto-detect |
117
139
 
118
140
  ### Supported Databases
119
141
 
@@ -157,34 +179,40 @@ Only 2 production dependencies: `citty` (CLI) and `picocolors` (terminal colors)
157
179
 
158
180
  ```
159
181
  src/
160
- runtime/ In-process entry point, server hooks, capture, safety
161
- analysis/ Security scanning, N+1 detection, insights engine
182
+ runtime/ In-process entry point, interceptor, capture, safety
183
+ analysis/ Insights engine and security scanner
184
+ insights/ InsightRule implementations (one file per rule)
162
185
  rules/ SecurityRule implementations (one file per rule)
163
186
  cli/ CLI commands (install, uninstall)
187
+ constants/ Shared thresholds, route paths, limits
164
188
  dashboard/
165
189
  api/ REST handlers — requests, flows, telemetry, metrics
166
190
  client/ Browser JS generated as template strings
167
191
  views/ Tab renderers (overview, flows, graph, etc.)
168
192
  styles/ CSS modules
169
193
  detect/ Framework auto-detection
170
- instrument/ Runtime hooks and database adapters
194
+ instrument/ Database adapters and instrumentation hooks
171
195
  adapters/ BrakitAdapter implementations (one file per library)
172
196
  hooks/ Core hooks (fetch, console, errors, context)
197
+ output/ Terminal insight listener
173
198
  store/ In-memory telemetry stores + persistent metrics
174
199
  types/ TypeScript definitions by domain
200
+ utils/ Shared utilities (collections, format, math, endpoint)
175
201
  ```
176
202
 
177
203
  ---
178
204
 
179
205
  ## Contributing
180
206
 
181
- Brakit is early and moving fast. The most common contributions — adding a new
182
- database adapter or a new security rule — each require exactly one file and one
183
- interface. See [CONTRIBUTING.md](CONTRIBUTING.md) for step-by-step guides.
207
+ Brakit is early and moving fast. The most common contributions — adding a
208
+ database adapter, a security rule, or an insight rule — each require exactly
209
+ one file and one interface. See [CONTRIBUTING.md](CONTRIBUTING.md) for
210
+ step-by-step guides.
184
211
 
185
212
  Some areas where help would be great:
186
213
 
187
214
  - **Database adapters** — Drizzle, Mongoose, SQLite, MongoDB
215
+ - **Insight rules** — New performance patterns, custom thresholds
188
216
  - **Security rules** — More patterns, configurable severity
189
217
  - **Dashboard** — Request diff, timeline view, HAR export
190
218
 
package/dist/api.js CHANGED
@@ -409,6 +409,58 @@ var corsCredentialsRule = {
409
409
  }
410
410
  };
411
411
 
412
+ // src/constants/thresholds.ts
413
+ var FLOW_GAP_MS = 5e3;
414
+ var SLOW_REQUEST_THRESHOLD_MS = 2e3;
415
+ var MIN_POLLING_SEQUENCE = 3;
416
+ var ENDPOINT_TRUNCATE_LENGTH = 12;
417
+ var N1_QUERY_THRESHOLD = 5;
418
+ var ERROR_RATE_THRESHOLD_PCT = 20;
419
+ var SLOW_ENDPOINT_THRESHOLD_MS = 1e3;
420
+ var MIN_REQUESTS_FOR_INSIGHT = 2;
421
+ var HIGH_QUERY_COUNT_PER_REQ = 5;
422
+ var CROSS_ENDPOINT_MIN_ENDPOINTS = 3;
423
+ var CROSS_ENDPOINT_PCT = 50;
424
+ var CROSS_ENDPOINT_MIN_OCCURRENCES = 5;
425
+ var REDUNDANT_QUERY_MIN_COUNT = 2;
426
+ var LARGE_RESPONSE_BYTES = 51200;
427
+ var HIGH_ROW_COUNT = 100;
428
+ var OVERFETCH_MIN_REQUESTS = 2;
429
+ var OVERFETCH_MIN_FIELDS = 8;
430
+ var OVERFETCH_MIN_INTERNAL_IDS = 2;
431
+ var OVERFETCH_NULL_RATIO = 0.3;
432
+ var REGRESSION_PCT_THRESHOLD = 50;
433
+ var REGRESSION_MIN_INCREASE_MS = 200;
434
+ var REGRESSION_MIN_REQUESTS = 5;
435
+ var QUERY_COUNT_REGRESSION_RATIO = 1.5;
436
+ var OVERFETCH_MANY_FIELDS = 12;
437
+ var OVERFETCH_UNWRAP_MIN_SIZE = 3;
438
+ var MAX_DUPLICATE_INSIGHTS = 3;
439
+
440
+ // src/utils/response.ts
441
+ function unwrapResponse(parsed) {
442
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
443
+ const obj = parsed;
444
+ const keys = Object.keys(obj);
445
+ if (keys.length > 3) return parsed;
446
+ let best = null;
447
+ let bestSize = 0;
448
+ for (const key of keys) {
449
+ const val = obj[key];
450
+ if (Array.isArray(val) && val.length > bestSize) {
451
+ best = val;
452
+ bestSize = val.length;
453
+ } else if (val && typeof val === "object" && !Array.isArray(val)) {
454
+ const size = Object.keys(val).length;
455
+ if (size > bestSize) {
456
+ best = val;
457
+ bestSize = size;
458
+ }
459
+ }
460
+ }
461
+ return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
462
+ }
463
+
412
464
  // src/analysis/rules/response-pii-leak.ts
413
465
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
414
466
  var FULL_RECORD_MIN_FIELDS = 5;
@@ -453,28 +505,6 @@ function hasInternalIds(obj) {
453
505
  }
454
506
  return false;
455
507
  }
456
- function unwrapResponse(parsed) {
457
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
458
- const obj = parsed;
459
- const keys = Object.keys(obj);
460
- if (keys.length > 3) return parsed;
461
- let best = null;
462
- let bestSize = 0;
463
- for (const key of keys) {
464
- const val = obj[key];
465
- if (Array.isArray(val) && val.length > bestSize) {
466
- best = val;
467
- bestSize = val.length;
468
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
469
- const size = Object.keys(val).length;
470
- if (size > bestSize) {
471
- best = val;
472
- bestSize = size;
473
- }
474
- }
475
- }
476
- return best && bestSize >= 3 ? best : parsed;
477
- }
478
508
  function detectPII(method, reqBody, resBody) {
479
509
  const target = unwrapResponse(resBody);
480
510
  if (WRITE_METHODS.has(method) && reqBody && typeof reqBody === "object") {
@@ -600,34 +630,6 @@ var DASHBOARD_PREFIX = "/__brakit";
600
630
  var MAX_REQUEST_ENTRIES = 1e3;
601
631
  var MAX_TELEMETRY_ENTRIES = 1e3;
602
632
 
603
- // src/constants/thresholds.ts
604
- var FLOW_GAP_MS = 5e3;
605
- var SLOW_REQUEST_THRESHOLD_MS = 2e3;
606
- var MIN_POLLING_SEQUENCE = 3;
607
- var ENDPOINT_TRUNCATE_LENGTH = 12;
608
- var N1_QUERY_THRESHOLD = 5;
609
- var ERROR_RATE_THRESHOLD_PCT = 20;
610
- var SLOW_ENDPOINT_THRESHOLD_MS = 1e3;
611
- var MIN_REQUESTS_FOR_INSIGHT = 2;
612
- var HIGH_QUERY_COUNT_PER_REQ = 5;
613
- var CROSS_ENDPOINT_MIN_ENDPOINTS = 3;
614
- var CROSS_ENDPOINT_PCT = 50;
615
- var CROSS_ENDPOINT_MIN_OCCURRENCES = 5;
616
- var REDUNDANT_QUERY_MIN_COUNT = 2;
617
- var LARGE_RESPONSE_BYTES = 51200;
618
- var HIGH_ROW_COUNT = 100;
619
- var OVERFETCH_MIN_REQUESTS = 2;
620
- var OVERFETCH_MIN_FIELDS = 8;
621
- var OVERFETCH_MIN_INTERNAL_IDS = 2;
622
- var OVERFETCH_NULL_RATIO = 0.3;
623
- var REGRESSION_PCT_THRESHOLD = 50;
624
- var REGRESSION_MIN_INCREASE_MS = 200;
625
- var REGRESSION_MIN_REQUESTS = 5;
626
- var QUERY_COUNT_REGRESSION_RATIO = 1.5;
627
- var OVERFETCH_MANY_FIELDS = 12;
628
- var OVERFETCH_UNWRAP_MIN_SIZE = 3;
629
- var MAX_DUPLICATE_INSIGHTS = 3;
630
-
631
633
  // src/utils/static-patterns.ts
632
634
  var STATIC_PATTERNS = [
633
635
  /^\/_next\//,
@@ -1642,30 +1644,6 @@ var highRowsRule = {
1642
1644
  }
1643
1645
  };
1644
1646
 
1645
- // src/utils/response.ts
1646
- function unwrapResponse2(parsed) {
1647
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
1648
- const obj = parsed;
1649
- const keys = Object.keys(obj);
1650
- if (keys.length > 3) return parsed;
1651
- let best = null;
1652
- let bestSize = 0;
1653
- for (const key of keys) {
1654
- const val = obj[key];
1655
- if (Array.isArray(val) && val.length > bestSize) {
1656
- best = val;
1657
- bestSize = val.length;
1658
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
1659
- const size = Object.keys(val).length;
1660
- if (size > bestSize) {
1661
- best = val;
1662
- bestSize = size;
1663
- }
1664
- }
1665
- }
1666
- return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
1667
- }
1668
-
1669
1647
  // src/analysis/insights/rules/response-overfetch.ts
1670
1648
  var responseOverfetchRule = {
1671
1649
  id: "response-overfetch",
@@ -1682,7 +1660,7 @@ var responseOverfetchRule = {
1682
1660
  } catch {
1683
1661
  continue;
1684
1662
  }
1685
- const target = unwrapResponse2(parsed);
1663
+ const target = unwrapResponse(parsed);
1686
1664
  const inspectObj = Array.isArray(target) && target.length > 0 ? target[0] : target;
1687
1665
  if (!inspectObj || typeof inspectObj !== "object" || Array.isArray(inspectObj)) continue;
1688
1666
  const fields = Object.keys(inspectObj);
@@ -1900,7 +1878,7 @@ var AnalysisEngine = class {
1900
1878
  };
1901
1879
 
1902
1880
  // src/index.ts
1903
- var VERSION = "0.7.5";
1881
+ var VERSION = "0.7.6";
1904
1882
  export {
1905
1883
  AdapterRegistry,
1906
1884
  AnalysisEngine,
@@ -386,6 +386,33 @@ var corsCredentialsRule = {
386
386
  }
387
387
  };
388
388
 
389
+ // src/constants/thresholds.ts
390
+ var OVERFETCH_UNWRAP_MIN_SIZE = 3;
391
+
392
+ // src/utils/response.ts
393
+ function unwrapResponse(parsed) {
394
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
395
+ const obj = parsed;
396
+ const keys = Object.keys(obj);
397
+ if (keys.length > 3) return parsed;
398
+ let best = null;
399
+ let bestSize = 0;
400
+ for (const key of keys) {
401
+ const val = obj[key];
402
+ if (Array.isArray(val) && val.length > bestSize) {
403
+ best = val;
404
+ bestSize = val.length;
405
+ } else if (val && typeof val === "object" && !Array.isArray(val)) {
406
+ const size = Object.keys(val).length;
407
+ if (size > bestSize) {
408
+ best = val;
409
+ bestSize = size;
410
+ }
411
+ }
412
+ }
413
+ return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
414
+ }
415
+
389
416
  // src/analysis/rules/response-pii-leak.ts
390
417
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
391
418
  var FULL_RECORD_MIN_FIELDS = 5;
@@ -430,28 +457,6 @@ function hasInternalIds(obj) {
430
457
  }
431
458
  return false;
432
459
  }
433
- function unwrapResponse(parsed) {
434
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
435
- const obj = parsed;
436
- const keys = Object.keys(obj);
437
- if (keys.length > 3) return parsed;
438
- let best = null;
439
- let bestSize = 0;
440
- for (const key of keys) {
441
- const val = obj[key];
442
- if (Array.isArray(val) && val.length > bestSize) {
443
- best = val;
444
- bestSize = val.length;
445
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
446
- const size = Object.keys(val).length;
447
- if (size > bestSize) {
448
- best = val;
449
- bestSize = size;
450
- }
451
- }
452
- }
453
- return best && bestSize >= 3 ? best : parsed;
454
- }
455
460
  function detectPII(method, reqBody, resBody) {
456
461
  const target = unwrapResponse(resBody);
457
462
  if (WRITE_METHODS.has(method) && reqBody && typeof reqBody === "object") {
@@ -695,7 +700,7 @@ import { resolve as resolve2 } from "path";
695
700
  import { randomUUID as randomUUID3 } from "crypto";
696
701
 
697
702
  // src/index.ts
698
- var VERSION = "0.7.5";
703
+ var VERSION = "0.7.6";
699
704
 
700
705
  // src/cli/commands/install.ts
701
706
  var IMPORT_LINE = `import "brakit";`;
@@ -3076,6 +3076,36 @@ var init_cors_credentials = __esm({
3076
3076
  }
3077
3077
  });
3078
3078
 
3079
+ // src/utils/response.ts
3080
+ function unwrapResponse(parsed) {
3081
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
3082
+ const obj = parsed;
3083
+ const keys = Object.keys(obj);
3084
+ if (keys.length > 3) return parsed;
3085
+ let best = null;
3086
+ let bestSize = 0;
3087
+ for (const key of keys) {
3088
+ const val = obj[key];
3089
+ if (Array.isArray(val) && val.length > bestSize) {
3090
+ best = val;
3091
+ bestSize = val.length;
3092
+ } else if (val && typeof val === "object" && !Array.isArray(val)) {
3093
+ const size = Object.keys(val).length;
3094
+ if (size > bestSize) {
3095
+ best = val;
3096
+ bestSize = size;
3097
+ }
3098
+ }
3099
+ }
3100
+ return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
3101
+ }
3102
+ var init_response = __esm({
3103
+ "src/utils/response.ts"() {
3104
+ "use strict";
3105
+ init_thresholds();
3106
+ }
3107
+ });
3108
+
3079
3109
  // src/analysis/rules/response-pii-leak.ts
3080
3110
  function tryParseJson2(body) {
3081
3111
  if (!body) return null;
@@ -3117,28 +3147,6 @@ function hasInternalIds(obj) {
3117
3147
  }
3118
3148
  return false;
3119
3149
  }
3120
- function unwrapResponse(parsed) {
3121
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
3122
- const obj = parsed;
3123
- const keys = Object.keys(obj);
3124
- if (keys.length > 3) return parsed;
3125
- let best = null;
3126
- let bestSize = 0;
3127
- for (const key of keys) {
3128
- const val = obj[key];
3129
- if (Array.isArray(val) && val.length > bestSize) {
3130
- best = val;
3131
- bestSize = val.length;
3132
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
3133
- const size = Object.keys(val).length;
3134
- if (size > bestSize) {
3135
- best = val;
3136
- bestSize = size;
3137
- }
3138
- }
3139
- }
3140
- return best && bestSize >= 3 ? best : parsed;
3141
- }
3142
3150
  function detectPII(method, reqBody, resBody) {
3143
3151
  const target = unwrapResponse(resBody);
3144
3152
  if (WRITE_METHODS.has(method) && reqBody && typeof reqBody === "object") {
@@ -3186,6 +3194,7 @@ var init_response_pii_leak = __esm({
3186
3194
  "src/analysis/rules/response-pii-leak.ts"() {
3187
3195
  "use strict";
3188
3196
  init_patterns();
3197
+ init_response();
3189
3198
  WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
3190
3199
  FULL_RECORD_MIN_FIELDS = 5;
3191
3200
  LIST_PII_MIN_ITEMS = 2;
@@ -3879,36 +3888,6 @@ var init_high_rows = __esm({
3879
3888
  }
3880
3889
  });
3881
3890
 
3882
- // src/utils/response.ts
3883
- function unwrapResponse2(parsed) {
3884
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
3885
- const obj = parsed;
3886
- const keys = Object.keys(obj);
3887
- if (keys.length > 3) return parsed;
3888
- let best = null;
3889
- let bestSize = 0;
3890
- for (const key of keys) {
3891
- const val = obj[key];
3892
- if (Array.isArray(val) && val.length > bestSize) {
3893
- best = val;
3894
- bestSize = val.length;
3895
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
3896
- const size = Object.keys(val).length;
3897
- if (size > bestSize) {
3898
- best = val;
3899
- bestSize = size;
3900
- }
3901
- }
3902
- }
3903
- return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
3904
- }
3905
- var init_response = __esm({
3906
- "src/utils/response.ts"() {
3907
- "use strict";
3908
- init_thresholds();
3909
- }
3910
- });
3911
-
3912
3891
  // src/analysis/insights/rules/response-overfetch.ts
3913
3892
  var responseOverfetchRule;
3914
3893
  var init_response_overfetch = __esm({
@@ -3933,7 +3912,7 @@ var init_response_overfetch = __esm({
3933
3912
  } catch {
3934
3913
  continue;
3935
3914
  }
3936
- const target = unwrapResponse2(parsed);
3915
+ const target = unwrapResponse(parsed);
3937
3916
  const inspectObj = Array.isArray(target) && target.length > 0 ? target[0] : target;
3938
3917
  if (!inspectObj || typeof inspectObj !== "object" || Array.isArray(inspectObj)) continue;
3939
3918
  const fields = Object.keys(inspectObj);
@@ -4226,7 +4205,7 @@ var init_src = __esm({
4226
4205
  init_engine();
4227
4206
  init_insights3();
4228
4207
  init_insights2();
4229
- VERSION = "0.7.5";
4208
+ VERSION = "0.7.6";
4230
4209
  }
4231
4210
  });
4232
4211
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brakit",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "See what your API is really doing. Security scanning, N+1 detection, duplicate calls, DB queries — one command, zero config.",
5
5
  "type": "module",
6
6
  "bin": {