brakit 0.7.6 → 0.8.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 +10 -3
- package/dist/api.d.ts +47 -2
- package/dist/api.js +250 -47
- package/dist/bin/brakit.js +1119 -29
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +737 -0
- package/dist/runtime/index.js +270 -13
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<b>See what your app is actually doing.</b> <br />
|
|
5
5
|
Every request, query, and security issue — before you ship. <br />
|
|
6
|
-
<b>Open source · Local
|
|
6
|
+
<b>Open source · Local first · Zero config · 2 dependencies</b>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<h3 align="center">
|
|
@@ -63,6 +63,7 @@ Dashboard at `http://localhost:<port>/__brakit`. Insights in the terminal.
|
|
|
63
63
|
- **Full server tracing** — fetch calls, DB queries, console logs, errors — zero code changes
|
|
64
64
|
- **Response overfetch** — large JSON responses with many fields your client doesn't use
|
|
65
65
|
- **Performance tracking** — health grades and p95 trends across dev sessions
|
|
66
|
+
- **AI-native via MCP** — Claude, Cursor, and other AI tools can query findings, inspect endpoints, and verify fixes directly
|
|
66
67
|
|
|
67
68
|
---
|
|
68
69
|
|
|
@@ -93,7 +94,9 @@ Brakit watches every action your app takes — not raw HTTP noise, but what actu
|
|
|
93
94
|
|
|
94
95
|
## Who Is This For
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
- **AI-assisted developers** — Using Cursor, Copilot, or Claude Code to generate API code you don't fully review? Brakit catches the issues they introduce.
|
|
98
|
+
- **Console.log debuggers** — Wish you could just see every action your API executes without adding log statements everywhere? That's literally what brakit does.
|
|
99
|
+
- **Anyone shipping Node.js APIs** — If you want to catch security and performance issues before they reach production, brakit surfaces them as you develop.
|
|
97
100
|
|
|
98
101
|
---
|
|
99
102
|
|
|
@@ -105,6 +108,7 @@ import 'brakit' → hooks into http.Server → captures everything
|
|
|
105
108
|
+-- Dashboard UI (/__brakit)
|
|
106
109
|
+-- Live SSE stream (real-time updates)
|
|
107
110
|
+-- Terminal output (insights as you develop)
|
|
111
|
+
+-- MCP server (AI assistant integration)
|
|
108
112
|
```
|
|
109
113
|
|
|
110
114
|
`import 'brakit'` runs inside your process. It patches `http.Server.prototype.emit` to intercept all requests — capturing request/response pairs, grouping them into actions, and streaming everything to the dashboard at `/__brakit` on your existing port. No proxy, no second process, no different port.
|
|
@@ -156,7 +160,7 @@ Brakit never runs in production. 7 independent layers ensure it:
|
|
|
156
160
|
npx brakit uninstall
|
|
157
161
|
```
|
|
158
162
|
|
|
159
|
-
Removes the instrumentation file and devDependency. Your app is unchanged.
|
|
163
|
+
Removes the instrumentation file, MCP configuration, `.brakit` data directory, `.gitignore` entry, and devDependency. Your app is unchanged.
|
|
160
164
|
|
|
161
165
|
---
|
|
162
166
|
|
|
@@ -194,6 +198,8 @@ src/
|
|
|
194
198
|
instrument/ Database adapters and instrumentation hooks
|
|
195
199
|
adapters/ BrakitAdapter implementations (one file per library)
|
|
196
200
|
hooks/ Core hooks (fetch, console, errors, context)
|
|
201
|
+
mcp/ MCP server for AI tool integration (Claude, Cursor)
|
|
202
|
+
tools/ Tool implementations (one file per tool)
|
|
197
203
|
output/ Terminal insight listener
|
|
198
204
|
store/ In-memory telemetry stores + persistent metrics
|
|
199
205
|
types/ TypeScript definitions by domain
|
|
@@ -214,6 +220,7 @@ Some areas where help would be great:
|
|
|
214
220
|
- **Database adapters** — Drizzle, Mongoose, SQLite, MongoDB
|
|
215
221
|
- **Insight rules** — New performance patterns, custom thresholds
|
|
216
222
|
- **Security rules** — More patterns, configurable severity
|
|
223
|
+
- **MCP tools** — New AI-facing tools for the MCP server
|
|
217
224
|
- **Dashboard** — Request diff, timeline view, HAR export
|
|
218
225
|
|
|
219
226
|
Please open an issue first for larger changes so we can discuss the approach.
|
package/dist/api.d.ts
CHANGED
|
@@ -160,6 +160,50 @@ interface SecurityFinding {
|
|
|
160
160
|
count: number;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
type FindingState = "open" | "fixing" | "resolved";
|
|
164
|
+
type FindingSource = "passive";
|
|
165
|
+
interface StatefulFinding {
|
|
166
|
+
/** Stable ID derived from rule + endpoint + description hash */
|
|
167
|
+
findingId: string;
|
|
168
|
+
state: FindingState;
|
|
169
|
+
source: FindingSource;
|
|
170
|
+
finding: SecurityFinding;
|
|
171
|
+
firstSeenAt: number;
|
|
172
|
+
lastSeenAt: number;
|
|
173
|
+
resolvedAt: number | null;
|
|
174
|
+
occurrences: number;
|
|
175
|
+
}
|
|
176
|
+
interface FindingsData {
|
|
177
|
+
version: 1;
|
|
178
|
+
findings: StatefulFinding[];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
declare class FindingStore {
|
|
182
|
+
private rootDir;
|
|
183
|
+
private findings;
|
|
184
|
+
private flushTimer;
|
|
185
|
+
private dirty;
|
|
186
|
+
private writing;
|
|
187
|
+
private readonly findingsPath;
|
|
188
|
+
private readonly tmpPath;
|
|
189
|
+
private readonly metricsDir;
|
|
190
|
+
constructor(rootDir: string);
|
|
191
|
+
start(): void;
|
|
192
|
+
stop(): void;
|
|
193
|
+
upsert(finding: SecurityFinding, source: FindingSource): StatefulFinding;
|
|
194
|
+
transition(findingId: string, state: FindingState): boolean;
|
|
195
|
+
reconcilePassive(currentFindings: readonly SecurityFinding[]): void;
|
|
196
|
+
getAll(): readonly StatefulFinding[];
|
|
197
|
+
getByState(state: FindingState): readonly StatefulFinding[];
|
|
198
|
+
get(findingId: string): StatefulFinding | undefined;
|
|
199
|
+
clear(): void;
|
|
200
|
+
private load;
|
|
201
|
+
private flush;
|
|
202
|
+
private flushSync;
|
|
203
|
+
private writeAsync;
|
|
204
|
+
private ensureDir;
|
|
205
|
+
}
|
|
206
|
+
|
|
163
207
|
interface BrakitAdapter {
|
|
164
208
|
name: string;
|
|
165
209
|
detect(): boolean;
|
|
@@ -285,6 +329,7 @@ declare class MetricsStore {
|
|
|
285
329
|
type AnalysisListener = (insights: Insight[], findings: SecurityFinding[]) => void;
|
|
286
330
|
declare class AnalysisEngine {
|
|
287
331
|
private metricsStore;
|
|
332
|
+
private findingStore?;
|
|
288
333
|
private debounceMs;
|
|
289
334
|
private scanner;
|
|
290
335
|
private cachedInsights;
|
|
@@ -295,7 +340,7 @@ declare class AnalysisEngine {
|
|
|
295
340
|
private boundQueryListener;
|
|
296
341
|
private boundErrorListener;
|
|
297
342
|
private boundLogListener;
|
|
298
|
-
constructor(metricsStore: MetricsStore, debounceMs?: number);
|
|
343
|
+
constructor(metricsStore: MetricsStore, findingStore?: FindingStore | undefined, debounceMs?: number);
|
|
299
344
|
start(): void;
|
|
300
345
|
stop(): void;
|
|
301
346
|
onUpdate(fn: AnalysisListener): void;
|
|
@@ -308,4 +353,4 @@ declare class AnalysisEngine {
|
|
|
308
353
|
|
|
309
354
|
declare const VERSION: string;
|
|
310
355
|
|
|
311
|
-
export { AdapterRegistry, AnalysisEngine, type BrakitAdapter, type BrakitConfig, type DetectedProject, type FlatHeaders, type Framework, type HttpMethod, type Insight, type InsightContext, type InsightRule, InsightRunner, type NormalizedOp, type RequestCategory, type RequestListener, type SecurityContext, type SecurityFinding, type SecurityRule, SecurityScanner, type SecuritySeverity, type TracedRequest, VERSION, computeInsights, createDefaultInsightRunner, createDefaultScanner, detectProject };
|
|
356
|
+
export { AdapterRegistry, AnalysisEngine, type BrakitAdapter, type BrakitConfig, type DetectedProject, type FindingSource, type FindingState, FindingStore, type FindingsData, type FlatHeaders, type Framework, type HttpMethod, type Insight, type InsightContext, type InsightRule, InsightRunner, type NormalizedOp, type RequestCategory, type RequestListener, type SecurityContext, type SecurityFinding, type SecurityRule, SecurityScanner, type SecuritySeverity, type StatefulFinding, type TracedRequest, VERSION, computeInsights, createDefaultInsightRunner, createDefaultScanner, detectProject };
|
package/dist/api.js
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
// src/store/finding-store.ts
|
|
2
|
+
import {
|
|
3
|
+
readFileSync as readFileSync2,
|
|
4
|
+
writeFileSync as writeFileSync2,
|
|
5
|
+
existsSync as existsSync2,
|
|
6
|
+
mkdirSync as mkdirSync2,
|
|
7
|
+
renameSync
|
|
8
|
+
} from "fs";
|
|
9
|
+
import { writeFile as writeFile2, mkdir, rename } from "fs/promises";
|
|
10
|
+
import { resolve as resolve2 } from "path";
|
|
11
|
+
|
|
12
|
+
// src/constants/routes.ts
|
|
13
|
+
var DASHBOARD_PREFIX = "/__brakit";
|
|
14
|
+
|
|
15
|
+
// src/constants/limits.ts
|
|
16
|
+
var MAX_REQUEST_ENTRIES = 1e3;
|
|
17
|
+
var MAX_TELEMETRY_ENTRIES = 1e3;
|
|
18
|
+
|
|
19
|
+
// src/constants/thresholds.ts
|
|
20
|
+
var FLOW_GAP_MS = 5e3;
|
|
21
|
+
var SLOW_REQUEST_THRESHOLD_MS = 2e3;
|
|
22
|
+
var MIN_POLLING_SEQUENCE = 3;
|
|
23
|
+
var ENDPOINT_TRUNCATE_LENGTH = 12;
|
|
24
|
+
var N1_QUERY_THRESHOLD = 5;
|
|
25
|
+
var ERROR_RATE_THRESHOLD_PCT = 20;
|
|
26
|
+
var SLOW_ENDPOINT_THRESHOLD_MS = 1e3;
|
|
27
|
+
var MIN_REQUESTS_FOR_INSIGHT = 2;
|
|
28
|
+
var HIGH_QUERY_COUNT_PER_REQ = 5;
|
|
29
|
+
var CROSS_ENDPOINT_MIN_ENDPOINTS = 3;
|
|
30
|
+
var CROSS_ENDPOINT_PCT = 50;
|
|
31
|
+
var CROSS_ENDPOINT_MIN_OCCURRENCES = 5;
|
|
32
|
+
var REDUNDANT_QUERY_MIN_COUNT = 2;
|
|
33
|
+
var LARGE_RESPONSE_BYTES = 51200;
|
|
34
|
+
var HIGH_ROW_COUNT = 100;
|
|
35
|
+
var OVERFETCH_MIN_REQUESTS = 2;
|
|
36
|
+
var OVERFETCH_MIN_FIELDS = 8;
|
|
37
|
+
var OVERFETCH_MIN_INTERNAL_IDS = 2;
|
|
38
|
+
var OVERFETCH_NULL_RATIO = 0.3;
|
|
39
|
+
var REGRESSION_PCT_THRESHOLD = 50;
|
|
40
|
+
var REGRESSION_MIN_INCREASE_MS = 200;
|
|
41
|
+
var REGRESSION_MIN_REQUESTS = 5;
|
|
42
|
+
var QUERY_COUNT_REGRESSION_RATIO = 1.5;
|
|
43
|
+
var OVERFETCH_MANY_FIELDS = 12;
|
|
44
|
+
var OVERFETCH_UNWRAP_MIN_SIZE = 3;
|
|
45
|
+
var MAX_DUPLICATE_INSIGHTS = 3;
|
|
46
|
+
|
|
47
|
+
// src/constants/metrics.ts
|
|
48
|
+
var METRICS_DIR = ".brakit";
|
|
49
|
+
var FINDINGS_FILE = ".brakit/findings.json";
|
|
50
|
+
var FINDINGS_FLUSH_INTERVAL_MS = 1e4;
|
|
4
51
|
|
|
5
52
|
// src/utils/fs.ts
|
|
6
53
|
import { access } from "fs/promises";
|
|
@@ -14,8 +61,191 @@ async function fileExists(path) {
|
|
|
14
61
|
return false;
|
|
15
62
|
}
|
|
16
63
|
}
|
|
64
|
+
function ensureGitignore(dir, entry) {
|
|
65
|
+
try {
|
|
66
|
+
const gitignorePath = resolve(dir, "../.gitignore");
|
|
67
|
+
if (existsSync(gitignorePath)) {
|
|
68
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
69
|
+
if (content.split("\n").some((l) => l.trim() === entry)) return;
|
|
70
|
+
writeFileSync(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
|
|
71
|
+
} else {
|
|
72
|
+
writeFileSync(gitignorePath, entry + "\n");
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/store/finding-id.ts
|
|
79
|
+
import { createHash } from "crypto";
|
|
80
|
+
function computeFindingId(finding) {
|
|
81
|
+
const key = `${finding.rule}:${finding.endpoint}:${finding.desc}`;
|
|
82
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/store/finding-store.ts
|
|
86
|
+
var FindingStore = class {
|
|
87
|
+
constructor(rootDir) {
|
|
88
|
+
this.rootDir = rootDir;
|
|
89
|
+
this.metricsDir = resolve2(rootDir, METRICS_DIR);
|
|
90
|
+
this.findingsPath = resolve2(rootDir, FINDINGS_FILE);
|
|
91
|
+
this.tmpPath = this.findingsPath + ".tmp";
|
|
92
|
+
this.load();
|
|
93
|
+
}
|
|
94
|
+
findings = /* @__PURE__ */ new Map();
|
|
95
|
+
flushTimer = null;
|
|
96
|
+
dirty = false;
|
|
97
|
+
writing = false;
|
|
98
|
+
findingsPath;
|
|
99
|
+
tmpPath;
|
|
100
|
+
metricsDir;
|
|
101
|
+
start() {
|
|
102
|
+
this.flushTimer = setInterval(
|
|
103
|
+
() => this.flush(),
|
|
104
|
+
FINDINGS_FLUSH_INTERVAL_MS
|
|
105
|
+
);
|
|
106
|
+
this.flushTimer.unref();
|
|
107
|
+
}
|
|
108
|
+
stop() {
|
|
109
|
+
if (this.flushTimer) {
|
|
110
|
+
clearInterval(this.flushTimer);
|
|
111
|
+
this.flushTimer = null;
|
|
112
|
+
}
|
|
113
|
+
this.flushSync();
|
|
114
|
+
}
|
|
115
|
+
upsert(finding, source) {
|
|
116
|
+
const id = computeFindingId(finding);
|
|
117
|
+
const existing = this.findings.get(id);
|
|
118
|
+
const now = Date.now();
|
|
119
|
+
if (existing) {
|
|
120
|
+
existing.lastSeenAt = now;
|
|
121
|
+
existing.occurrences++;
|
|
122
|
+
existing.finding = finding;
|
|
123
|
+
if (existing.state === "resolved") {
|
|
124
|
+
existing.state = "open";
|
|
125
|
+
existing.resolvedAt = null;
|
|
126
|
+
}
|
|
127
|
+
this.dirty = true;
|
|
128
|
+
return existing;
|
|
129
|
+
}
|
|
130
|
+
const stateful = {
|
|
131
|
+
findingId: id,
|
|
132
|
+
state: "open",
|
|
133
|
+
source,
|
|
134
|
+
finding,
|
|
135
|
+
firstSeenAt: now,
|
|
136
|
+
lastSeenAt: now,
|
|
137
|
+
resolvedAt: null,
|
|
138
|
+
occurrences: 1
|
|
139
|
+
};
|
|
140
|
+
this.findings.set(id, stateful);
|
|
141
|
+
this.dirty = true;
|
|
142
|
+
return stateful;
|
|
143
|
+
}
|
|
144
|
+
transition(findingId, state) {
|
|
145
|
+
const finding = this.findings.get(findingId);
|
|
146
|
+
if (!finding) return false;
|
|
147
|
+
finding.state = state;
|
|
148
|
+
if (state === "resolved") {
|
|
149
|
+
finding.resolvedAt = Date.now();
|
|
150
|
+
}
|
|
151
|
+
this.dirty = true;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
reconcilePassive(currentFindings) {
|
|
155
|
+
const currentIds = new Set(currentFindings.map(computeFindingId));
|
|
156
|
+
for (const [id, stateful] of this.findings) {
|
|
157
|
+
if (stateful.source === "passive" && stateful.state === "open" && !currentIds.has(id)) {
|
|
158
|
+
stateful.state = "resolved";
|
|
159
|
+
stateful.resolvedAt = Date.now();
|
|
160
|
+
this.dirty = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
getAll() {
|
|
165
|
+
return [...this.findings.values()];
|
|
166
|
+
}
|
|
167
|
+
getByState(state) {
|
|
168
|
+
return [...this.findings.values()].filter((f) => f.state === state);
|
|
169
|
+
}
|
|
170
|
+
get(findingId) {
|
|
171
|
+
return this.findings.get(findingId);
|
|
172
|
+
}
|
|
173
|
+
clear() {
|
|
174
|
+
this.findings.clear();
|
|
175
|
+
this.dirty = true;
|
|
176
|
+
}
|
|
177
|
+
load() {
|
|
178
|
+
try {
|
|
179
|
+
if (existsSync2(this.findingsPath)) {
|
|
180
|
+
const raw = readFileSync2(this.findingsPath, "utf-8");
|
|
181
|
+
const parsed = JSON.parse(raw);
|
|
182
|
+
if (parsed?.version === 1 && Array.isArray(parsed.findings)) {
|
|
183
|
+
for (const f of parsed.findings) {
|
|
184
|
+
this.findings.set(f.findingId, f);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
flush() {
|
|
192
|
+
if (!this.dirty) return;
|
|
193
|
+
this.writeAsync();
|
|
194
|
+
}
|
|
195
|
+
flushSync() {
|
|
196
|
+
if (!this.dirty) return;
|
|
197
|
+
try {
|
|
198
|
+
this.ensureDir();
|
|
199
|
+
const data = {
|
|
200
|
+
version: 1,
|
|
201
|
+
findings: [...this.findings.values()]
|
|
202
|
+
};
|
|
203
|
+
writeFileSync2(this.tmpPath, JSON.stringify(data));
|
|
204
|
+
renameSync(this.tmpPath, this.findingsPath);
|
|
205
|
+
this.dirty = false;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
process.stderr.write(
|
|
208
|
+
`[brakit] failed to save findings: ${err.message}
|
|
209
|
+
`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async writeAsync() {
|
|
214
|
+
if (this.writing) return;
|
|
215
|
+
this.writing = true;
|
|
216
|
+
try {
|
|
217
|
+
if (!existsSync2(this.metricsDir)) {
|
|
218
|
+
await mkdir(this.metricsDir, { recursive: true });
|
|
219
|
+
ensureGitignore(this.metricsDir, METRICS_DIR);
|
|
220
|
+
}
|
|
221
|
+
const data = {
|
|
222
|
+
version: 1,
|
|
223
|
+
findings: [...this.findings.values()]
|
|
224
|
+
};
|
|
225
|
+
await writeFile2(this.tmpPath, JSON.stringify(data));
|
|
226
|
+
await rename(this.tmpPath, this.findingsPath);
|
|
227
|
+
this.dirty = false;
|
|
228
|
+
} catch (err) {
|
|
229
|
+
process.stderr.write(
|
|
230
|
+
`[brakit] failed to save findings: ${err.message}
|
|
231
|
+
`
|
|
232
|
+
);
|
|
233
|
+
} finally {
|
|
234
|
+
this.writing = false;
|
|
235
|
+
if (this.dirty) this.writeAsync();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
ensureDir() {
|
|
239
|
+
if (!existsSync2(this.metricsDir)) {
|
|
240
|
+
mkdirSync2(this.metricsDir, { recursive: true });
|
|
241
|
+
ensureGitignore(this.metricsDir, METRICS_DIR);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
17
245
|
|
|
18
246
|
// src/detect/project.ts
|
|
247
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
248
|
+
import { join } from "path";
|
|
19
249
|
var FRAMEWORKS = [
|
|
20
250
|
{ name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
|
|
21
251
|
{ name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
|
|
@@ -409,34 +639,6 @@ var corsCredentialsRule = {
|
|
|
409
639
|
}
|
|
410
640
|
};
|
|
411
641
|
|
|
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
642
|
// src/utils/response.ts
|
|
441
643
|
function unwrapResponse(parsed) {
|
|
442
644
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
|
|
@@ -623,13 +825,6 @@ function createDefaultScanner() {
|
|
|
623
825
|
return scanner;
|
|
624
826
|
}
|
|
625
827
|
|
|
626
|
-
// src/constants/routes.ts
|
|
627
|
-
var DASHBOARD_PREFIX = "/__brakit";
|
|
628
|
-
|
|
629
|
-
// src/constants/limits.ts
|
|
630
|
-
var MAX_REQUEST_ENTRIES = 1e3;
|
|
631
|
-
var MAX_TELEMETRY_ENTRIES = 1e3;
|
|
632
|
-
|
|
633
828
|
// src/utils/static-patterns.ts
|
|
634
829
|
var STATIC_PATTERNS = [
|
|
635
830
|
/^\/_next\//,
|
|
@@ -778,15 +973,15 @@ function getEndpointKey(method, path) {
|
|
|
778
973
|
|
|
779
974
|
// src/store/metrics/persistence.ts
|
|
780
975
|
import {
|
|
781
|
-
readFileSync as
|
|
782
|
-
writeFileSync as
|
|
783
|
-
mkdirSync as
|
|
784
|
-
existsSync as
|
|
976
|
+
readFileSync as readFileSync3,
|
|
977
|
+
writeFileSync as writeFileSync3,
|
|
978
|
+
mkdirSync as mkdirSync3,
|
|
979
|
+
existsSync as existsSync3,
|
|
785
980
|
unlinkSync,
|
|
786
|
-
renameSync
|
|
981
|
+
renameSync as renameSync2
|
|
787
982
|
} from "fs";
|
|
788
|
-
import { writeFile as
|
|
789
|
-
import { resolve as
|
|
983
|
+
import { writeFile as writeFile3, mkdir as mkdir2, rename as rename2 } from "fs/promises";
|
|
984
|
+
import { resolve as resolve3 } from "path";
|
|
790
985
|
|
|
791
986
|
// src/analysis/group.ts
|
|
792
987
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -1797,8 +1992,9 @@ function computeInsights(ctx) {
|
|
|
1797
1992
|
|
|
1798
1993
|
// src/analysis/engine.ts
|
|
1799
1994
|
var AnalysisEngine = class {
|
|
1800
|
-
constructor(metricsStore, debounceMs = 300) {
|
|
1995
|
+
constructor(metricsStore, findingStore, debounceMs = 300) {
|
|
1801
1996
|
this.metricsStore = metricsStore;
|
|
1997
|
+
this.findingStore = findingStore;
|
|
1802
1998
|
this.debounceMs = debounceMs;
|
|
1803
1999
|
this.scanner = createDefaultScanner();
|
|
1804
2000
|
this.boundRequestListener = () => this.scheduleRecompute();
|
|
@@ -1859,6 +2055,12 @@ var AnalysisEngine = class {
|
|
|
1859
2055
|
const fetches = defaultFetchStore.getAll();
|
|
1860
2056
|
const flows = groupRequestsIntoFlows(requests);
|
|
1861
2057
|
this.cachedFindings = this.scanner.scan({ requests, logs });
|
|
2058
|
+
if (this.findingStore) {
|
|
2059
|
+
for (const finding of this.cachedFindings) {
|
|
2060
|
+
this.findingStore.upsert(finding, "passive");
|
|
2061
|
+
}
|
|
2062
|
+
this.findingStore.reconcilePassive(this.cachedFindings);
|
|
2063
|
+
}
|
|
1862
2064
|
this.cachedInsights = computeInsights({
|
|
1863
2065
|
requests,
|
|
1864
2066
|
queries,
|
|
@@ -1878,10 +2080,11 @@ var AnalysisEngine = class {
|
|
|
1878
2080
|
};
|
|
1879
2081
|
|
|
1880
2082
|
// src/index.ts
|
|
1881
|
-
var VERSION = "0.
|
|
2083
|
+
var VERSION = "0.8.0";
|
|
1882
2084
|
export {
|
|
1883
2085
|
AdapterRegistry,
|
|
1884
2086
|
AnalysisEngine,
|
|
2087
|
+
FindingStore,
|
|
1885
2088
|
InsightRunner,
|
|
1886
2089
|
SecurityScanner,
|
|
1887
2090
|
VERSION,
|