brakit 0.8.1 → 0.8.3
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 +5 -5
- package/dist/api.d.ts +103 -44
- package/dist/api.js +153 -266
- package/dist/bin/brakit.js +130 -219
- package/dist/mcp/server.js +65 -23
- package/dist/runtime/index.js +1623 -1539
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
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";
|
|
2
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
10
3
|
import { resolve as resolve2 } from "path";
|
|
11
4
|
|
|
12
5
|
// src/constants/routes.ts
|
|
13
6
|
var DASHBOARD_PREFIX = "/__brakit";
|
|
14
7
|
|
|
15
8
|
// src/constants/limits.ts
|
|
16
|
-
var MAX_REQUEST_ENTRIES = 1e3;
|
|
17
|
-
var MAX_TELEMETRY_ENTRIES = 1e3;
|
|
18
9
|
var MAX_INGEST_BYTES = 10 * 1024 * 1024;
|
|
19
10
|
|
|
20
11
|
// src/constants/thresholds.ts
|
|
@@ -53,6 +44,25 @@ var METRICS_DIR = ".brakit";
|
|
|
53
44
|
var FINDINGS_FILE = ".brakit/findings.json";
|
|
54
45
|
var FINDINGS_FLUSH_INTERVAL_MS = 1e4;
|
|
55
46
|
|
|
47
|
+
// src/constants/severity.ts
|
|
48
|
+
var SEVERITY_CRITICAL = "critical";
|
|
49
|
+
var SEVERITY_WARNING = "warning";
|
|
50
|
+
var SEVERITY_INFO = "info";
|
|
51
|
+
var SEVERITY_ICON_MAP = {
|
|
52
|
+
[SEVERITY_CRITICAL]: { icon: "\u2717", cls: "critical" },
|
|
53
|
+
[SEVERITY_WARNING]: { icon: "\u26A0", cls: "warning" },
|
|
54
|
+
[SEVERITY_INFO]: { icon: "\u2139", cls: "info" }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/utils/atomic-writer.ts
|
|
58
|
+
import {
|
|
59
|
+
writeFileSync as writeFileSync2,
|
|
60
|
+
existsSync as existsSync2,
|
|
61
|
+
mkdirSync as mkdirSync2,
|
|
62
|
+
renameSync
|
|
63
|
+
} from "fs";
|
|
64
|
+
import { writeFile as writeFile2, mkdir, rename } from "fs/promises";
|
|
65
|
+
|
|
56
66
|
// src/utils/fs.ts
|
|
57
67
|
import { access } from "fs/promises";
|
|
58
68
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -79,6 +89,70 @@ function ensureGitignore(dir, entry) {
|
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
// src/utils/log.ts
|
|
93
|
+
var PREFIX = "[brakit]";
|
|
94
|
+
function brakitWarn(message) {
|
|
95
|
+
process.stderr.write(`${PREFIX} ${message}
|
|
96
|
+
`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/utils/atomic-writer.ts
|
|
100
|
+
var AtomicWriter = class {
|
|
101
|
+
constructor(opts) {
|
|
102
|
+
this.opts = opts;
|
|
103
|
+
this.tmpPath = opts.filePath + ".tmp";
|
|
104
|
+
}
|
|
105
|
+
tmpPath;
|
|
106
|
+
writing = false;
|
|
107
|
+
pendingContent = null;
|
|
108
|
+
writeSync(content) {
|
|
109
|
+
try {
|
|
110
|
+
this.ensureDir();
|
|
111
|
+
writeFileSync2(this.tmpPath, content);
|
|
112
|
+
renameSync(this.tmpPath, this.opts.filePath);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
brakitWarn(`failed to save ${this.opts.label}: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async writeAsync(content) {
|
|
118
|
+
if (this.writing) {
|
|
119
|
+
this.pendingContent = content;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.writing = true;
|
|
123
|
+
try {
|
|
124
|
+
await this.ensureDirAsync();
|
|
125
|
+
await writeFile2(this.tmpPath, content);
|
|
126
|
+
await rename(this.tmpPath, this.opts.filePath);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
brakitWarn(`failed to save ${this.opts.label}: ${err.message}`);
|
|
129
|
+
} finally {
|
|
130
|
+
this.writing = false;
|
|
131
|
+
if (this.pendingContent !== null) {
|
|
132
|
+
const next = this.pendingContent;
|
|
133
|
+
this.pendingContent = null;
|
|
134
|
+
this.writeAsync(next);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
ensureDir() {
|
|
139
|
+
if (!existsSync2(this.opts.dir)) {
|
|
140
|
+
mkdirSync2(this.opts.dir, { recursive: true });
|
|
141
|
+
if (this.opts.gitignoreEntry) {
|
|
142
|
+
ensureGitignore(this.opts.dir, this.opts.gitignoreEntry);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async ensureDirAsync() {
|
|
147
|
+
if (!existsSync2(this.opts.dir)) {
|
|
148
|
+
await mkdir(this.opts.dir, { recursive: true });
|
|
149
|
+
if (this.opts.gitignoreEntry) {
|
|
150
|
+
ensureGitignore(this.opts.dir, this.opts.gitignoreEntry);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
82
156
|
// src/store/finding-id.ts
|
|
83
157
|
import { createHash } from "crypto";
|
|
84
158
|
function computeFindingId(finding) {
|
|
@@ -90,18 +164,21 @@ function computeFindingId(finding) {
|
|
|
90
164
|
var FindingStore = class {
|
|
91
165
|
constructor(rootDir) {
|
|
92
166
|
this.rootDir = rootDir;
|
|
93
|
-
|
|
167
|
+
const metricsDir = resolve2(rootDir, METRICS_DIR);
|
|
94
168
|
this.findingsPath = resolve2(rootDir, FINDINGS_FILE);
|
|
95
|
-
this.
|
|
169
|
+
this.writer = new AtomicWriter({
|
|
170
|
+
dir: metricsDir,
|
|
171
|
+
filePath: this.findingsPath,
|
|
172
|
+
gitignoreEntry: METRICS_DIR,
|
|
173
|
+
label: "findings"
|
|
174
|
+
});
|
|
96
175
|
this.load();
|
|
97
176
|
}
|
|
98
177
|
findings = /* @__PURE__ */ new Map();
|
|
99
178
|
flushTimer = null;
|
|
100
179
|
dirty = false;
|
|
101
|
-
|
|
180
|
+
writer;
|
|
102
181
|
findingsPath;
|
|
103
|
-
tmpPath;
|
|
104
|
-
metricsDir;
|
|
105
182
|
start() {
|
|
106
183
|
this.flushTimer = setInterval(
|
|
107
184
|
() => this.flush(),
|
|
@@ -155,6 +232,15 @@ var FindingStore = class {
|
|
|
155
232
|
this.dirty = true;
|
|
156
233
|
return true;
|
|
157
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Reconcile passive findings against the current analysis results.
|
|
237
|
+
*
|
|
238
|
+
* Passive findings are detected by continuous scanning (not user-triggered).
|
|
239
|
+
* When a previously-seen finding is absent from the current results, it means
|
|
240
|
+
* the issue has been fixed — transition it to "resolved" automatically.
|
|
241
|
+
* Active findings (from MCP verify-fix) are not auto-resolved because they
|
|
242
|
+
* require explicit verification.
|
|
243
|
+
*/
|
|
158
244
|
reconcilePassive(currentFindings) {
|
|
159
245
|
const currentIds = new Set(currentFindings.map(computeFindingId));
|
|
160
246
|
for (const [id, stateful] of this.findings) {
|
|
@@ -180,7 +266,7 @@ var FindingStore = class {
|
|
|
180
266
|
}
|
|
181
267
|
load() {
|
|
182
268
|
try {
|
|
183
|
-
if (
|
|
269
|
+
if (existsSync3(this.findingsPath)) {
|
|
184
270
|
const raw = readFileSync2(this.findingsPath, "utf-8");
|
|
185
271
|
const parsed = JSON.parse(raw);
|
|
186
272
|
if (parsed?.version === 1 && Array.isArray(parsed.findings)) {
|
|
@@ -194,56 +280,20 @@ var FindingStore = class {
|
|
|
194
280
|
}
|
|
195
281
|
flush() {
|
|
196
282
|
if (!this.dirty) return;
|
|
197
|
-
this.writeAsync();
|
|
283
|
+
this.writer.writeAsync(this.serialize());
|
|
284
|
+
this.dirty = false;
|
|
198
285
|
}
|
|
199
286
|
flushSync() {
|
|
200
287
|
if (!this.dirty) return;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const data = {
|
|
204
|
-
version: 1,
|
|
205
|
-
findings: [...this.findings.values()]
|
|
206
|
-
};
|
|
207
|
-
writeFileSync2(this.tmpPath, JSON.stringify(data));
|
|
208
|
-
renameSync(this.tmpPath, this.findingsPath);
|
|
209
|
-
this.dirty = false;
|
|
210
|
-
} catch (err) {
|
|
211
|
-
process.stderr.write(
|
|
212
|
-
`[brakit] failed to save findings: ${err.message}
|
|
213
|
-
`
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
async writeAsync() {
|
|
218
|
-
if (this.writing) return;
|
|
219
|
-
this.writing = true;
|
|
220
|
-
try {
|
|
221
|
-
if (!existsSync2(this.metricsDir)) {
|
|
222
|
-
await mkdir(this.metricsDir, { recursive: true });
|
|
223
|
-
ensureGitignore(this.metricsDir, METRICS_DIR);
|
|
224
|
-
}
|
|
225
|
-
const data = {
|
|
226
|
-
version: 1,
|
|
227
|
-
findings: [...this.findings.values()]
|
|
228
|
-
};
|
|
229
|
-
await writeFile2(this.tmpPath, JSON.stringify(data));
|
|
230
|
-
await rename(this.tmpPath, this.findingsPath);
|
|
231
|
-
this.dirty = false;
|
|
232
|
-
} catch (err) {
|
|
233
|
-
process.stderr.write(
|
|
234
|
-
`[brakit] failed to save findings: ${err.message}
|
|
235
|
-
`
|
|
236
|
-
);
|
|
237
|
-
} finally {
|
|
238
|
-
this.writing = false;
|
|
239
|
-
if (this.dirty) this.writeAsync();
|
|
240
|
-
}
|
|
288
|
+
this.writer.writeSync(this.serialize());
|
|
289
|
+
this.dirty = false;
|
|
241
290
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
291
|
+
serialize() {
|
|
292
|
+
const data = {
|
|
293
|
+
version: 1,
|
|
294
|
+
findings: [...this.findings.values()]
|
|
295
|
+
};
|
|
296
|
+
return JSON.stringify(data);
|
|
247
297
|
}
|
|
248
298
|
};
|
|
249
299
|
|
|
@@ -829,170 +879,20 @@ function createDefaultScanner() {
|
|
|
829
879
|
return scanner;
|
|
830
880
|
}
|
|
831
881
|
|
|
832
|
-
// src/
|
|
833
|
-
var
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
/^\/__nextjs/
|
|
838
|
-
];
|
|
839
|
-
function isStaticPath(urlPath) {
|
|
840
|
-
return STATIC_PATTERNS.some((p) => p.test(urlPath));
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// src/store/request-store.ts
|
|
844
|
-
function flattenHeaders(headers) {
|
|
845
|
-
const flat = {};
|
|
846
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
847
|
-
if (value === void 0) continue;
|
|
848
|
-
flat[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
849
|
-
}
|
|
850
|
-
return flat;
|
|
851
|
-
}
|
|
852
|
-
var RequestStore = class {
|
|
853
|
-
constructor(maxEntries = MAX_REQUEST_ENTRIES) {
|
|
854
|
-
this.maxEntries = maxEntries;
|
|
855
|
-
}
|
|
856
|
-
requests = [];
|
|
857
|
-
listeners = [];
|
|
858
|
-
capture(input) {
|
|
859
|
-
const url = input.url;
|
|
860
|
-
const path = url.split("?")[0];
|
|
861
|
-
let requestBodyStr = null;
|
|
862
|
-
if (input.requestBody && input.requestBody.length > 0) {
|
|
863
|
-
requestBodyStr = input.requestBody.subarray(0, input.config.maxBodyCapture).toString("utf-8");
|
|
864
|
-
}
|
|
865
|
-
let responseBodyStr = null;
|
|
866
|
-
if (input.responseBody && input.responseBody.length > 0) {
|
|
867
|
-
const ct = input.responseContentType;
|
|
868
|
-
if (ct.includes("json") || ct.includes("text") || ct.includes("html")) {
|
|
869
|
-
responseBodyStr = input.responseBody.subarray(0, input.config.maxBodyCapture).toString("utf-8");
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
const entry = {
|
|
873
|
-
id: input.requestId,
|
|
874
|
-
method: input.method,
|
|
875
|
-
url,
|
|
876
|
-
path,
|
|
877
|
-
headers: flattenHeaders(input.requestHeaders),
|
|
878
|
-
requestBody: requestBodyStr,
|
|
879
|
-
statusCode: input.statusCode,
|
|
880
|
-
responseHeaders: flattenHeaders(input.responseHeaders),
|
|
881
|
-
responseBody: responseBodyStr,
|
|
882
|
-
startedAt: input.startTime,
|
|
883
|
-
durationMs: Math.round((input.endTime ?? performance.now()) - input.startTime),
|
|
884
|
-
responseSize: input.responseBody?.length ?? 0,
|
|
885
|
-
isStatic: isStaticPath(path)
|
|
886
|
-
};
|
|
887
|
-
this.requests.push(entry);
|
|
888
|
-
if (this.requests.length > this.maxEntries) {
|
|
889
|
-
this.requests.shift();
|
|
890
|
-
}
|
|
891
|
-
for (const fn of this.listeners) {
|
|
892
|
-
fn(entry);
|
|
893
|
-
}
|
|
894
|
-
return entry;
|
|
895
|
-
}
|
|
896
|
-
getAll() {
|
|
897
|
-
return this.requests;
|
|
882
|
+
// src/core/disposable.ts
|
|
883
|
+
var SubscriptionBag = class {
|
|
884
|
+
items = [];
|
|
885
|
+
add(teardown) {
|
|
886
|
+
this.items.push(typeof teardown === "function" ? { dispose: teardown } : teardown);
|
|
898
887
|
}
|
|
899
|
-
|
|
900
|
-
this.
|
|
901
|
-
|
|
902
|
-
onRequest(fn) {
|
|
903
|
-
this.listeners.push(fn);
|
|
904
|
-
}
|
|
905
|
-
offRequest(fn) {
|
|
906
|
-
const idx = this.listeners.indexOf(fn);
|
|
907
|
-
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
888
|
+
dispose() {
|
|
889
|
+
for (const d of this.items) d.dispose();
|
|
890
|
+
this.items.length = 0;
|
|
908
891
|
}
|
|
909
892
|
};
|
|
910
893
|
|
|
911
|
-
// src/store/request-log.ts
|
|
912
|
-
var defaultStore = new RequestStore();
|
|
913
|
-
var getRequests = () => defaultStore.getAll();
|
|
914
|
-
var onRequest = (fn) => defaultStore.onRequest(fn);
|
|
915
|
-
var offRequest = (fn) => defaultStore.offRequest(fn);
|
|
916
|
-
|
|
917
|
-
// src/store/telemetry-store.ts
|
|
918
|
-
import { randomUUID } from "crypto";
|
|
919
|
-
var TelemetryStore = class {
|
|
920
|
-
constructor(maxEntries = MAX_TELEMETRY_ENTRIES) {
|
|
921
|
-
this.maxEntries = maxEntries;
|
|
922
|
-
}
|
|
923
|
-
entries = [];
|
|
924
|
-
listeners = [];
|
|
925
|
-
add(data) {
|
|
926
|
-
const entry = { id: randomUUID(), ...data };
|
|
927
|
-
this.entries.push(entry);
|
|
928
|
-
if (this.entries.length > this.maxEntries) this.entries.shift();
|
|
929
|
-
for (const fn of this.listeners) fn(entry);
|
|
930
|
-
return entry;
|
|
931
|
-
}
|
|
932
|
-
getAll() {
|
|
933
|
-
return this.entries;
|
|
934
|
-
}
|
|
935
|
-
getByRequest(requestId) {
|
|
936
|
-
return this.entries.filter((e) => e.parentRequestId === requestId);
|
|
937
|
-
}
|
|
938
|
-
clear() {
|
|
939
|
-
this.entries.length = 0;
|
|
940
|
-
}
|
|
941
|
-
onEntry(fn) {
|
|
942
|
-
this.listeners.push(fn);
|
|
943
|
-
}
|
|
944
|
-
offEntry(fn) {
|
|
945
|
-
const idx = this.listeners.indexOf(fn);
|
|
946
|
-
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
947
|
-
}
|
|
948
|
-
};
|
|
949
|
-
|
|
950
|
-
// src/store/fetch-store.ts
|
|
951
|
-
var FetchStore = class extends TelemetryStore {
|
|
952
|
-
};
|
|
953
|
-
var defaultFetchStore = new FetchStore();
|
|
954
|
-
|
|
955
|
-
// src/store/log-store.ts
|
|
956
|
-
var LogStore = class extends TelemetryStore {
|
|
957
|
-
};
|
|
958
|
-
var defaultLogStore = new LogStore();
|
|
959
|
-
|
|
960
|
-
// src/store/error-store.ts
|
|
961
|
-
var ErrorStore = class extends TelemetryStore {
|
|
962
|
-
};
|
|
963
|
-
var defaultErrorStore = new ErrorStore();
|
|
964
|
-
|
|
965
|
-
// src/store/query-store.ts
|
|
966
|
-
var QueryStore = class extends TelemetryStore {
|
|
967
|
-
};
|
|
968
|
-
var defaultQueryStore = new QueryStore();
|
|
969
|
-
|
|
970
|
-
// src/store/metrics/metrics-store.ts
|
|
971
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
972
|
-
|
|
973
|
-
// src/utils/endpoint.ts
|
|
974
|
-
function getEndpointKey(method, path) {
|
|
975
|
-
return `${method} ${path}`;
|
|
976
|
-
}
|
|
977
|
-
var ENDPOINT_PREFIX_RE = /^(\S+\s+\S+)/;
|
|
978
|
-
function extractEndpointFromDesc(desc) {
|
|
979
|
-
return desc.match(ENDPOINT_PREFIX_RE)?.[1] ?? null;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// src/store/metrics/persistence.ts
|
|
983
|
-
import {
|
|
984
|
-
readFileSync as readFileSync3,
|
|
985
|
-
writeFileSync as writeFileSync3,
|
|
986
|
-
mkdirSync as mkdirSync3,
|
|
987
|
-
existsSync as existsSync3,
|
|
988
|
-
unlinkSync,
|
|
989
|
-
renameSync as renameSync2
|
|
990
|
-
} from "fs";
|
|
991
|
-
import { writeFile as writeFile3, mkdir as mkdir2, rename as rename2 } from "fs/promises";
|
|
992
|
-
import { resolve as resolve3 } from "path";
|
|
993
|
-
|
|
994
894
|
// src/analysis/group.ts
|
|
995
|
-
import { randomUUID
|
|
895
|
+
import { randomUUID } from "crypto";
|
|
996
896
|
|
|
997
897
|
// src/analysis/categorize.ts
|
|
998
898
|
function detectCategory(req) {
|
|
@@ -1288,7 +1188,7 @@ function buildFlow(rawRequests) {
|
|
|
1288
1188
|
const redundancyPct = nonStaticCount > 0 ? Math.round(duplicateCount / nonStaticCount * 100) : 0;
|
|
1289
1189
|
const sourcePage = getDominantSourcePage(rawRequests);
|
|
1290
1190
|
return {
|
|
1291
|
-
id:
|
|
1191
|
+
id: randomUUID(),
|
|
1292
1192
|
label: deriveFlowLabel(requests, sourcePage),
|
|
1293
1193
|
requests,
|
|
1294
1194
|
startTime,
|
|
@@ -1360,6 +1260,15 @@ function groupBy(items, keyFn) {
|
|
|
1360
1260
|
return map;
|
|
1361
1261
|
}
|
|
1362
1262
|
|
|
1263
|
+
// src/utils/endpoint.ts
|
|
1264
|
+
function getEndpointKey(method, path) {
|
|
1265
|
+
return `${method} ${path}`;
|
|
1266
|
+
}
|
|
1267
|
+
var ENDPOINT_PREFIX_RE = /^(\S+\s+\S+)/;
|
|
1268
|
+
function extractEndpointFromDesc(desc) {
|
|
1269
|
+
return desc.match(ENDPOINT_PREFIX_RE)?.[1] ?? null;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1363
1272
|
// src/instrument/adapters/normalize.ts
|
|
1364
1273
|
function normalizeSQL(sql) {
|
|
1365
1274
|
if (!sql) return { op: "OTHER", table: "" };
|
|
@@ -2073,15 +1982,10 @@ var InsightTracker = class {
|
|
|
2073
1982
|
|
|
2074
1983
|
// src/analysis/engine.ts
|
|
2075
1984
|
var AnalysisEngine = class {
|
|
2076
|
-
constructor(
|
|
2077
|
-
this.
|
|
2078
|
-
this.findingStore = findingStore;
|
|
1985
|
+
constructor(registry, debounceMs = 300) {
|
|
1986
|
+
this.registry = registry;
|
|
2079
1987
|
this.debounceMs = debounceMs;
|
|
2080
1988
|
this.scanner = createDefaultScanner();
|
|
2081
|
-
this.boundRequestListener = () => this.scheduleRecompute();
|
|
2082
|
-
this.boundQueryListener = () => this.scheduleRecompute();
|
|
2083
|
-
this.boundErrorListener = () => this.scheduleRecompute();
|
|
2084
|
-
this.boundLogListener = () => this.scheduleRecompute();
|
|
2085
1989
|
}
|
|
2086
1990
|
scanner;
|
|
2087
1991
|
insightTracker = new InsightTracker();
|
|
@@ -2089,34 +1993,21 @@ var AnalysisEngine = class {
|
|
|
2089
1993
|
cachedFindings = [];
|
|
2090
1994
|
cachedStatefulInsights = [];
|
|
2091
1995
|
debounceTimer = null;
|
|
2092
|
-
|
|
2093
|
-
boundRequestListener;
|
|
2094
|
-
boundQueryListener;
|
|
2095
|
-
boundErrorListener;
|
|
2096
|
-
boundLogListener;
|
|
1996
|
+
subs = new SubscriptionBag();
|
|
2097
1997
|
start() {
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
1998
|
+
const bus = this.registry.get("event-bus");
|
|
1999
|
+
this.subs.add(bus.on("request:completed", () => this.scheduleRecompute()));
|
|
2000
|
+
this.subs.add(bus.on("telemetry:query", () => this.scheduleRecompute()));
|
|
2001
|
+
this.subs.add(bus.on("telemetry:error", () => this.scheduleRecompute()));
|
|
2002
|
+
this.subs.add(bus.on("telemetry:log", () => this.scheduleRecompute()));
|
|
2102
2003
|
}
|
|
2103
2004
|
stop() {
|
|
2104
|
-
|
|
2105
|
-
defaultQueryStore.offEntry(this.boundQueryListener);
|
|
2106
|
-
defaultErrorStore.offEntry(this.boundErrorListener);
|
|
2107
|
-
defaultLogStore.offEntry(this.boundLogListener);
|
|
2005
|
+
this.subs.dispose();
|
|
2108
2006
|
if (this.debounceTimer) {
|
|
2109
2007
|
clearTimeout(this.debounceTimer);
|
|
2110
2008
|
this.debounceTimer = null;
|
|
2111
2009
|
}
|
|
2112
2010
|
}
|
|
2113
|
-
onUpdate(fn) {
|
|
2114
|
-
this.listeners.push(fn);
|
|
2115
|
-
}
|
|
2116
|
-
offUpdate(fn) {
|
|
2117
|
-
const idx = this.listeners.indexOf(fn);
|
|
2118
|
-
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
2119
|
-
}
|
|
2120
2011
|
getInsights() {
|
|
2121
2012
|
return this.cachedInsights;
|
|
2122
2013
|
}
|
|
@@ -2124,7 +2015,7 @@ var AnalysisEngine = class {
|
|
|
2124
2015
|
return this.cachedFindings;
|
|
2125
2016
|
}
|
|
2126
2017
|
getStatefulFindings() {
|
|
2127
|
-
return this.
|
|
2018
|
+
return this.registry.has("finding-store") ? this.registry.get("finding-store").getAll() : [];
|
|
2128
2019
|
}
|
|
2129
2020
|
getStatefulInsights() {
|
|
2130
2021
|
return this.cachedStatefulInsights;
|
|
@@ -2137,18 +2028,19 @@ var AnalysisEngine = class {
|
|
|
2137
2028
|
}, this.debounceMs);
|
|
2138
2029
|
}
|
|
2139
2030
|
recompute() {
|
|
2140
|
-
const requests =
|
|
2141
|
-
const queries =
|
|
2142
|
-
const errors =
|
|
2143
|
-
const logs =
|
|
2144
|
-
const fetches =
|
|
2031
|
+
const requests = this.registry.get("request-store").getAll();
|
|
2032
|
+
const queries = this.registry.get("query-store").getAll();
|
|
2033
|
+
const errors = this.registry.get("error-store").getAll();
|
|
2034
|
+
const logs = this.registry.get("log-store").getAll();
|
|
2035
|
+
const fetches = this.registry.get("fetch-store").getAll();
|
|
2145
2036
|
const flows = groupRequestsIntoFlows(requests);
|
|
2146
2037
|
this.cachedFindings = this.scanner.scan({ requests, logs });
|
|
2147
|
-
if (this.
|
|
2038
|
+
if (this.registry.has("finding-store")) {
|
|
2039
|
+
const findingStore = this.registry.get("finding-store");
|
|
2148
2040
|
for (const finding of this.cachedFindings) {
|
|
2149
|
-
|
|
2041
|
+
findingStore.upsert(finding, "passive");
|
|
2150
2042
|
}
|
|
2151
|
-
|
|
2043
|
+
findingStore.reconcilePassive(this.cachedFindings);
|
|
2152
2044
|
}
|
|
2153
2045
|
this.cachedInsights = computeInsights({
|
|
2154
2046
|
requests,
|
|
@@ -2156,7 +2048,7 @@ var AnalysisEngine = class {
|
|
|
2156
2048
|
errors,
|
|
2157
2049
|
flows,
|
|
2158
2050
|
fetches,
|
|
2159
|
-
previousMetrics: this.
|
|
2051
|
+
previousMetrics: this.registry.get("metrics-store").getAll(),
|
|
2160
2052
|
securityFindings: this.cachedFindings
|
|
2161
2053
|
});
|
|
2162
2054
|
this.cachedStatefulInsights = this.insightTracker.reconcile(this.cachedInsights);
|
|
@@ -2166,17 +2058,12 @@ var AnalysisEngine = class {
|
|
|
2166
2058
|
statefulFindings: this.getStatefulFindings(),
|
|
2167
2059
|
statefulInsights: this.cachedStatefulInsights
|
|
2168
2060
|
};
|
|
2169
|
-
|
|
2170
|
-
try {
|
|
2171
|
-
fn(update);
|
|
2172
|
-
} catch {
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2061
|
+
this.registry.get("event-bus").emit("analysis:updated", update);
|
|
2175
2062
|
}
|
|
2176
2063
|
};
|
|
2177
2064
|
|
|
2178
2065
|
// src/index.ts
|
|
2179
|
-
var VERSION = "0.8.
|
|
2066
|
+
var VERSION = "0.8.3";
|
|
2180
2067
|
export {
|
|
2181
2068
|
AdapterRegistry,
|
|
2182
2069
|
AnalysisEngine,
|