bashbros 0.1.1 → 0.1.2
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/dist/{chunk-A535VV7N.js → chunk-BW6XCOJH.js} +4 -3
- package/dist/chunk-BW6XCOJH.js.map +1 -0
- package/dist/chunk-FRMAIRQ2.js +89 -0
- package/dist/chunk-FRMAIRQ2.js.map +1 -0
- package/dist/{chunk-GD5VNHIN.js → chunk-QWZGB4V3.js} +4 -85
- package/dist/chunk-QWZGB4V3.js.map +1 -0
- package/dist/{chunk-WPJJZLT6.js → chunk-SQCP6IYB.js} +2 -2
- package/dist/{chunk-VVSCAH2B.js → chunk-XCZMQRSX.js} +125 -10
- package/dist/chunk-XCZMQRSX.js.map +1 -0
- package/dist/chunk-YUMNBQAY.js +766 -0
- package/dist/chunk-YUMNBQAY.js.map +1 -0
- package/dist/cli.js +577 -30
- package/dist/cli.js.map +1 -1
- package/dist/{config-43SK6SFI.js → config-JLLOTFLI.js} +2 -2
- package/dist/{db-EHQDB5OL.js → db-OBKEXRTP.js} +2 -2
- package/dist/{display-HFIFXOOL.js → display-6LZ2HBCU.js} +3 -3
- package/dist/engine-EGPAS2EX.js +10 -0
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/session-Y4MICATZ.js +15 -0
- package/dist/session-Y4MICATZ.js.map +1 -0
- package/dist/static/index.html +1873 -276
- package/package.json +1 -1
- package/dist/chunk-A535VV7N.js.map +0 -1
- package/dist/chunk-CSRPOGHY.js +0 -354
- package/dist/chunk-CSRPOGHY.js.map +0 -1
- package/dist/chunk-GD5VNHIN.js.map +0 -1
- package/dist/chunk-VVSCAH2B.js.map +0 -1
- package/dist/engine-PKLXW6OF.js +0 -9
- /package/dist/{chunk-WPJJZLT6.js.map → chunk-SQCP6IYB.js.map} +0 -0
- /package/dist/{config-43SK6SFI.js.map → config-JLLOTFLI.js.map} +0 -0
- /package/dist/{db-EHQDB5OL.js.map → db-OBKEXRTP.js.map} +0 -0
- /package/dist/{display-HFIFXOOL.js.map → display-6LZ2HBCU.js.map} +0 -0
- /package/dist/{engine-PKLXW6OF.js.map → engine-EGPAS2EX.js.map} +0 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/dashboard/db.ts
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
var DashboardDB = class {
|
|
7
|
+
db;
|
|
8
|
+
constructor(dbPath = ".bashbros.db") {
|
|
9
|
+
this.db = new Database(dbPath);
|
|
10
|
+
this.db.pragma("journal_mode = WAL");
|
|
11
|
+
this.initTables();
|
|
12
|
+
}
|
|
13
|
+
initTables() {
|
|
14
|
+
this.db.exec(`
|
|
15
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
timestamp TEXT NOT NULL,
|
|
18
|
+
source TEXT NOT NULL,
|
|
19
|
+
level TEXT NOT NULL,
|
|
20
|
+
category TEXT NOT NULL,
|
|
21
|
+
message TEXT NOT NULL,
|
|
22
|
+
data TEXT
|
|
23
|
+
)
|
|
24
|
+
`);
|
|
25
|
+
this.db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS connector_events (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
timestamp TEXT NOT NULL,
|
|
29
|
+
connector TEXT NOT NULL,
|
|
30
|
+
method TEXT NOT NULL,
|
|
31
|
+
direction TEXT NOT NULL,
|
|
32
|
+
payload TEXT NOT NULL,
|
|
33
|
+
resources_accessed TEXT NOT NULL
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
this.db.exec(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS egress_blocks (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
timestamp TEXT NOT NULL,
|
|
40
|
+
pattern TEXT NOT NULL,
|
|
41
|
+
matched_text TEXT NOT NULL,
|
|
42
|
+
redacted_text TEXT NOT NULL,
|
|
43
|
+
connector TEXT,
|
|
44
|
+
destination TEXT,
|
|
45
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
46
|
+
approved_by TEXT,
|
|
47
|
+
approved_at TEXT
|
|
48
|
+
)
|
|
49
|
+
`);
|
|
50
|
+
this.db.exec(`
|
|
51
|
+
CREATE TABLE IF NOT EXISTS exposure_scans (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
timestamp TEXT NOT NULL,
|
|
54
|
+
agent TEXT NOT NULL,
|
|
55
|
+
pid INTEGER,
|
|
56
|
+
port INTEGER NOT NULL,
|
|
57
|
+
bind_address TEXT NOT NULL,
|
|
58
|
+
has_auth TEXT NOT NULL,
|
|
59
|
+
severity TEXT NOT NULL,
|
|
60
|
+
action TEXT NOT NULL,
|
|
61
|
+
message TEXT NOT NULL
|
|
62
|
+
)
|
|
63
|
+
`);
|
|
64
|
+
this.db.exec(`
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_events_level ON events(level);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_connector_events_connector ON connector_events(connector);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_egress_blocks_status ON egress_blocks(status);
|
|
70
|
+
`);
|
|
71
|
+
this.db.exec(`
|
|
72
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
73
|
+
id TEXT PRIMARY KEY,
|
|
74
|
+
agent TEXT NOT NULL,
|
|
75
|
+
pid INTEGER NOT NULL,
|
|
76
|
+
start_time TEXT NOT NULL,
|
|
77
|
+
end_time TEXT,
|
|
78
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
79
|
+
command_count INTEGER NOT NULL DEFAULT 0,
|
|
80
|
+
blocked_count INTEGER NOT NULL DEFAULT 0,
|
|
81
|
+
avg_risk_score REAL NOT NULL DEFAULT 0,
|
|
82
|
+
working_dir TEXT NOT NULL
|
|
83
|
+
)
|
|
84
|
+
`);
|
|
85
|
+
this.db.exec(`
|
|
86
|
+
CREATE TABLE IF NOT EXISTS commands (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
session_id TEXT NOT NULL,
|
|
89
|
+
timestamp TEXT NOT NULL,
|
|
90
|
+
command TEXT NOT NULL,
|
|
91
|
+
allowed INTEGER NOT NULL,
|
|
92
|
+
risk_score INTEGER NOT NULL,
|
|
93
|
+
risk_level TEXT NOT NULL,
|
|
94
|
+
risk_factors TEXT NOT NULL,
|
|
95
|
+
duration_ms INTEGER NOT NULL,
|
|
96
|
+
violations TEXT NOT NULL,
|
|
97
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
98
|
+
)
|
|
99
|
+
`);
|
|
100
|
+
this.db.exec(`
|
|
101
|
+
CREATE TABLE IF NOT EXISTS bro_events (
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
session_id TEXT,
|
|
104
|
+
timestamp TEXT NOT NULL,
|
|
105
|
+
event_type TEXT NOT NULL,
|
|
106
|
+
input_context TEXT NOT NULL,
|
|
107
|
+
output_summary TEXT NOT NULL,
|
|
108
|
+
model_used TEXT NOT NULL,
|
|
109
|
+
latency_ms INTEGER NOT NULL,
|
|
110
|
+
success INTEGER NOT NULL,
|
|
111
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
112
|
+
)
|
|
113
|
+
`);
|
|
114
|
+
this.db.exec(`
|
|
115
|
+
CREATE TABLE IF NOT EXISTS bro_status (
|
|
116
|
+
id TEXT PRIMARY KEY,
|
|
117
|
+
timestamp TEXT NOT NULL,
|
|
118
|
+
ollama_available INTEGER NOT NULL,
|
|
119
|
+
ollama_model TEXT NOT NULL,
|
|
120
|
+
platform TEXT NOT NULL,
|
|
121
|
+
shell TEXT NOT NULL,
|
|
122
|
+
project_type TEXT
|
|
123
|
+
)
|
|
124
|
+
`);
|
|
125
|
+
this.db.exec(`
|
|
126
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_commands_session_id ON commands(session_id);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_commands_timestamp ON commands(timestamp);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_commands_allowed ON commands(allowed);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_bro_events_session_id ON bro_events(session_id);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_bro_events_timestamp ON bro_events(timestamp);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_bro_status_timestamp ON bro_status(timestamp);
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
// ─────────────────────────────────────────────────────────────
|
|
137
|
+
// Events
|
|
138
|
+
// ─────────────────────────────────────────────────────────────
|
|
139
|
+
insertEvent(input) {
|
|
140
|
+
const id = randomUUID();
|
|
141
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
142
|
+
const data = input.data ? JSON.stringify(input.data) : null;
|
|
143
|
+
const stmt = this.db.prepare(`
|
|
144
|
+
INSERT INTO events (id, timestamp, source, level, category, message, data)
|
|
145
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
146
|
+
`);
|
|
147
|
+
stmt.run(id, timestamp, input.source, input.level, input.category, input.message, data);
|
|
148
|
+
return id;
|
|
149
|
+
}
|
|
150
|
+
getEvents(filter = {}) {
|
|
151
|
+
const conditions = [];
|
|
152
|
+
const params = [];
|
|
153
|
+
if (filter.source) {
|
|
154
|
+
conditions.push("source = ?");
|
|
155
|
+
params.push(filter.source);
|
|
156
|
+
}
|
|
157
|
+
if (filter.level) {
|
|
158
|
+
conditions.push("level = ?");
|
|
159
|
+
params.push(filter.level);
|
|
160
|
+
}
|
|
161
|
+
if (filter.category) {
|
|
162
|
+
conditions.push("category = ?");
|
|
163
|
+
params.push(filter.category);
|
|
164
|
+
}
|
|
165
|
+
if (filter.since) {
|
|
166
|
+
conditions.push("timestamp >= ?");
|
|
167
|
+
params.push(filter.since.toISOString());
|
|
168
|
+
}
|
|
169
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
170
|
+
const limit = filter.limit ?? 100;
|
|
171
|
+
const offset = filter.offset ?? 0;
|
|
172
|
+
const stmt = this.db.prepare(`
|
|
173
|
+
SELECT * FROM events
|
|
174
|
+
${whereClause}
|
|
175
|
+
ORDER BY timestamp DESC
|
|
176
|
+
LIMIT ? OFFSET ?
|
|
177
|
+
`);
|
|
178
|
+
params.push(limit, offset);
|
|
179
|
+
const rows = stmt.all(...params);
|
|
180
|
+
return rows.map((row) => ({
|
|
181
|
+
id: row.id,
|
|
182
|
+
timestamp: new Date(row.timestamp),
|
|
183
|
+
source: row.source,
|
|
184
|
+
level: row.level,
|
|
185
|
+
category: row.category,
|
|
186
|
+
message: row.message,
|
|
187
|
+
data: row.data ? JSON.parse(row.data) : void 0
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
// ─────────────────────────────────────────────────────────────
|
|
191
|
+
// Connector Events
|
|
192
|
+
// ─────────────────────────────────────────────────────────────
|
|
193
|
+
insertConnectorEvent(input) {
|
|
194
|
+
const id = randomUUID();
|
|
195
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
196
|
+
const stmt = this.db.prepare(`
|
|
197
|
+
INSERT INTO connector_events (id, timestamp, connector, method, direction, payload, resources_accessed)
|
|
198
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
199
|
+
`);
|
|
200
|
+
stmt.run(
|
|
201
|
+
id,
|
|
202
|
+
timestamp,
|
|
203
|
+
input.connector,
|
|
204
|
+
input.method,
|
|
205
|
+
input.direction,
|
|
206
|
+
JSON.stringify(input.payload),
|
|
207
|
+
JSON.stringify(input.resourcesAccessed)
|
|
208
|
+
);
|
|
209
|
+
return id;
|
|
210
|
+
}
|
|
211
|
+
getConnectorEvents(connector, limit = 100) {
|
|
212
|
+
const stmt = this.db.prepare(`
|
|
213
|
+
SELECT * FROM connector_events
|
|
214
|
+
WHERE connector = ?
|
|
215
|
+
ORDER BY timestamp DESC
|
|
216
|
+
LIMIT ?
|
|
217
|
+
`);
|
|
218
|
+
const rows = stmt.all(connector, limit);
|
|
219
|
+
return rows.map((row) => ({
|
|
220
|
+
id: row.id,
|
|
221
|
+
timestamp: new Date(row.timestamp),
|
|
222
|
+
connector: row.connector,
|
|
223
|
+
method: row.method,
|
|
224
|
+
direction: row.direction,
|
|
225
|
+
payload: JSON.parse(row.payload),
|
|
226
|
+
resourcesAccessed: JSON.parse(row.resources_accessed)
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
getAllConnectorEvents(limit = 100) {
|
|
230
|
+
const stmt = this.db.prepare(`
|
|
231
|
+
SELECT * FROM connector_events
|
|
232
|
+
ORDER BY timestamp DESC
|
|
233
|
+
LIMIT ?
|
|
234
|
+
`);
|
|
235
|
+
const rows = stmt.all(limit);
|
|
236
|
+
return rows.map((row) => ({
|
|
237
|
+
id: row.id,
|
|
238
|
+
timestamp: new Date(row.timestamp),
|
|
239
|
+
connector: row.connector,
|
|
240
|
+
method: row.method,
|
|
241
|
+
direction: row.direction,
|
|
242
|
+
payload: JSON.parse(row.payload),
|
|
243
|
+
resourcesAccessed: JSON.parse(row.resources_accessed)
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
// ─────────────────────────────────────────────────────────────
|
|
247
|
+
// Egress Blocks
|
|
248
|
+
// ─────────────────────────────────────────────────────────────
|
|
249
|
+
insertEgressBlock(input) {
|
|
250
|
+
const id = randomUUID();
|
|
251
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
252
|
+
const stmt = this.db.prepare(`
|
|
253
|
+
INSERT INTO egress_blocks (id, timestamp, pattern, matched_text, redacted_text, connector, destination, status)
|
|
254
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
|
|
255
|
+
`);
|
|
256
|
+
stmt.run(
|
|
257
|
+
id,
|
|
258
|
+
timestamp,
|
|
259
|
+
JSON.stringify(input.pattern),
|
|
260
|
+
input.matchedText,
|
|
261
|
+
input.redactedText,
|
|
262
|
+
input.connector ?? null,
|
|
263
|
+
input.destination ?? null
|
|
264
|
+
);
|
|
265
|
+
return id;
|
|
266
|
+
}
|
|
267
|
+
getPendingBlocks() {
|
|
268
|
+
const stmt = this.db.prepare(`
|
|
269
|
+
SELECT * FROM egress_blocks
|
|
270
|
+
WHERE status = 'pending'
|
|
271
|
+
ORDER BY timestamp DESC
|
|
272
|
+
`);
|
|
273
|
+
const rows = stmt.all();
|
|
274
|
+
return rows.map((row) => this.rowToEgressMatch(row));
|
|
275
|
+
}
|
|
276
|
+
getBlock(id) {
|
|
277
|
+
const stmt = this.db.prepare(`
|
|
278
|
+
SELECT * FROM egress_blocks WHERE id = ?
|
|
279
|
+
`);
|
|
280
|
+
const row = stmt.get(id);
|
|
281
|
+
if (!row) return null;
|
|
282
|
+
return this.rowToEgressMatch(row);
|
|
283
|
+
}
|
|
284
|
+
approveBlock(id, approvedBy) {
|
|
285
|
+
const stmt = this.db.prepare(`
|
|
286
|
+
UPDATE egress_blocks
|
|
287
|
+
SET status = 'approved', approved_by = ?, approved_at = ?
|
|
288
|
+
WHERE id = ?
|
|
289
|
+
`);
|
|
290
|
+
stmt.run(approvedBy, (/* @__PURE__ */ new Date()).toISOString(), id);
|
|
291
|
+
}
|
|
292
|
+
denyBlock(id, deniedBy) {
|
|
293
|
+
const stmt = this.db.prepare(`
|
|
294
|
+
UPDATE egress_blocks
|
|
295
|
+
SET status = 'denied', approved_by = ?, approved_at = ?
|
|
296
|
+
WHERE id = ?
|
|
297
|
+
`);
|
|
298
|
+
stmt.run(deniedBy, (/* @__PURE__ */ new Date()).toISOString(), id);
|
|
299
|
+
}
|
|
300
|
+
rowToEgressMatch(row) {
|
|
301
|
+
return {
|
|
302
|
+
id: row.id,
|
|
303
|
+
timestamp: new Date(row.timestamp),
|
|
304
|
+
pattern: JSON.parse(row.pattern),
|
|
305
|
+
matchedText: row.matched_text,
|
|
306
|
+
redactedText: row.redacted_text,
|
|
307
|
+
connector: row.connector ?? void 0,
|
|
308
|
+
destination: row.destination ?? void 0,
|
|
309
|
+
status: row.status,
|
|
310
|
+
approvedBy: row.approved_by ?? void 0,
|
|
311
|
+
approvedAt: row.approved_at ? new Date(row.approved_at) : void 0
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
// ─────────────────────────────────────────────────────────────
|
|
315
|
+
// Exposure Scans
|
|
316
|
+
// ─────────────────────────────────────────────────────────────
|
|
317
|
+
insertExposureScan(result) {
|
|
318
|
+
const id = randomUUID();
|
|
319
|
+
const timestamp = result.timestamp.toISOString();
|
|
320
|
+
const stmt = this.db.prepare(`
|
|
321
|
+
INSERT INTO exposure_scans (id, timestamp, agent, pid, port, bind_address, has_auth, severity, action, message)
|
|
322
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
323
|
+
`);
|
|
324
|
+
stmt.run(
|
|
325
|
+
id,
|
|
326
|
+
timestamp,
|
|
327
|
+
result.agent,
|
|
328
|
+
result.pid ?? null,
|
|
329
|
+
result.port,
|
|
330
|
+
result.bindAddress,
|
|
331
|
+
String(result.hasAuth),
|
|
332
|
+
result.severity,
|
|
333
|
+
result.action,
|
|
334
|
+
result.message
|
|
335
|
+
);
|
|
336
|
+
return id;
|
|
337
|
+
}
|
|
338
|
+
getRecentExposures(limit = 100) {
|
|
339
|
+
const stmt = this.db.prepare(`
|
|
340
|
+
SELECT * FROM exposure_scans
|
|
341
|
+
ORDER BY timestamp DESC
|
|
342
|
+
LIMIT ?
|
|
343
|
+
`);
|
|
344
|
+
const rows = stmt.all(limit);
|
|
345
|
+
return rows.map((row) => ({
|
|
346
|
+
agent: row.agent,
|
|
347
|
+
pid: row.pid ?? void 0,
|
|
348
|
+
port: row.port,
|
|
349
|
+
bindAddress: row.bind_address,
|
|
350
|
+
hasAuth: row.has_auth === "true" ? true : row.has_auth === "false" ? false : "unknown",
|
|
351
|
+
severity: row.severity,
|
|
352
|
+
action: row.action,
|
|
353
|
+
message: row.message,
|
|
354
|
+
timestamp: new Date(row.timestamp)
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
// ─────────────────────────────────────────────────────────────
|
|
358
|
+
// Sessions
|
|
359
|
+
// ─────────────────────────────────────────────────────────────
|
|
360
|
+
insertSession(input) {
|
|
361
|
+
const id = randomUUID();
|
|
362
|
+
const startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
363
|
+
const stmt = this.db.prepare(`
|
|
364
|
+
INSERT INTO sessions (id, agent, pid, start_time, status, command_count, blocked_count, avg_risk_score, working_dir)
|
|
365
|
+
VALUES (?, ?, ?, ?, 'active', 0, 0, 0, ?)
|
|
366
|
+
`);
|
|
367
|
+
stmt.run(id, input.agent, input.pid, startTime, input.workingDir);
|
|
368
|
+
return id;
|
|
369
|
+
}
|
|
370
|
+
updateSession(id, updates) {
|
|
371
|
+
const setClauses = [];
|
|
372
|
+
const params = [];
|
|
373
|
+
if (updates.endTime !== void 0) {
|
|
374
|
+
setClauses.push("end_time = ?");
|
|
375
|
+
params.push(updates.endTime.toISOString());
|
|
376
|
+
}
|
|
377
|
+
if (updates.status !== void 0) {
|
|
378
|
+
setClauses.push("status = ?");
|
|
379
|
+
params.push(updates.status);
|
|
380
|
+
}
|
|
381
|
+
if (updates.commandCount !== void 0) {
|
|
382
|
+
setClauses.push("command_count = ?");
|
|
383
|
+
params.push(updates.commandCount);
|
|
384
|
+
}
|
|
385
|
+
if (updates.blockedCount !== void 0) {
|
|
386
|
+
setClauses.push("blocked_count = ?");
|
|
387
|
+
params.push(updates.blockedCount);
|
|
388
|
+
}
|
|
389
|
+
if (updates.avgRiskScore !== void 0) {
|
|
390
|
+
setClauses.push("avg_risk_score = ?");
|
|
391
|
+
params.push(updates.avgRiskScore);
|
|
392
|
+
}
|
|
393
|
+
if (setClauses.length === 0) return;
|
|
394
|
+
params.push(id);
|
|
395
|
+
const stmt = this.db.prepare(`
|
|
396
|
+
UPDATE sessions SET ${setClauses.join(", ")} WHERE id = ?
|
|
397
|
+
`);
|
|
398
|
+
stmt.run(...params);
|
|
399
|
+
}
|
|
400
|
+
getSession(id) {
|
|
401
|
+
const stmt = this.db.prepare("SELECT * FROM sessions WHERE id = ?");
|
|
402
|
+
const row = stmt.get(id);
|
|
403
|
+
if (!row) return null;
|
|
404
|
+
return this.rowToSession(row);
|
|
405
|
+
}
|
|
406
|
+
getSessions(filter = {}) {
|
|
407
|
+
const conditions = [];
|
|
408
|
+
const params = [];
|
|
409
|
+
if (filter.status) {
|
|
410
|
+
conditions.push("status = ?");
|
|
411
|
+
params.push(filter.status);
|
|
412
|
+
}
|
|
413
|
+
if (filter.since) {
|
|
414
|
+
conditions.push("start_time >= ?");
|
|
415
|
+
params.push(filter.since.toISOString());
|
|
416
|
+
}
|
|
417
|
+
if (filter.until) {
|
|
418
|
+
conditions.push("start_time <= ?");
|
|
419
|
+
params.push(filter.until.toISOString());
|
|
420
|
+
}
|
|
421
|
+
if (filter.agent) {
|
|
422
|
+
conditions.push("agent = ?");
|
|
423
|
+
params.push(filter.agent);
|
|
424
|
+
}
|
|
425
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
426
|
+
const limit = filter.limit ?? 100;
|
|
427
|
+
const offset = filter.offset ?? 0;
|
|
428
|
+
const stmt = this.db.prepare(`
|
|
429
|
+
SELECT * FROM sessions
|
|
430
|
+
${whereClause}
|
|
431
|
+
ORDER BY start_time DESC
|
|
432
|
+
LIMIT ? OFFSET ?
|
|
433
|
+
`);
|
|
434
|
+
params.push(limit, offset);
|
|
435
|
+
const rows = stmt.all(...params);
|
|
436
|
+
return rows.map((row) => this.rowToSession(row));
|
|
437
|
+
}
|
|
438
|
+
getActiveSession() {
|
|
439
|
+
const stmt = this.db.prepare(`
|
|
440
|
+
SELECT * FROM sessions WHERE status = 'active' ORDER BY start_time DESC LIMIT 1
|
|
441
|
+
`);
|
|
442
|
+
const row = stmt.get();
|
|
443
|
+
if (!row) return null;
|
|
444
|
+
return this.rowToSession(row);
|
|
445
|
+
}
|
|
446
|
+
rowToSession(row) {
|
|
447
|
+
return {
|
|
448
|
+
id: row.id,
|
|
449
|
+
agent: row.agent,
|
|
450
|
+
pid: row.pid,
|
|
451
|
+
startTime: new Date(row.start_time),
|
|
452
|
+
endTime: row.end_time ? new Date(row.end_time) : void 0,
|
|
453
|
+
status: row.status,
|
|
454
|
+
commandCount: row.command_count,
|
|
455
|
+
blockedCount: row.blocked_count,
|
|
456
|
+
avgRiskScore: row.avg_risk_score,
|
|
457
|
+
workingDir: row.working_dir
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// ─────────────────────────────────────────────────────────────
|
|
461
|
+
// Commands
|
|
462
|
+
// ─────────────────────────────────────────────────────────────
|
|
463
|
+
insertCommand(input) {
|
|
464
|
+
const id = randomUUID();
|
|
465
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
466
|
+
const stmt = this.db.prepare(`
|
|
467
|
+
INSERT INTO commands (id, session_id, timestamp, command, allowed, risk_score, risk_level, risk_factors, duration_ms, violations)
|
|
468
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
469
|
+
`);
|
|
470
|
+
stmt.run(
|
|
471
|
+
id,
|
|
472
|
+
input.sessionId,
|
|
473
|
+
timestamp,
|
|
474
|
+
input.command,
|
|
475
|
+
input.allowed ? 1 : 0,
|
|
476
|
+
input.riskScore,
|
|
477
|
+
input.riskLevel,
|
|
478
|
+
JSON.stringify(input.riskFactors),
|
|
479
|
+
input.durationMs,
|
|
480
|
+
JSON.stringify(input.violations)
|
|
481
|
+
);
|
|
482
|
+
return id;
|
|
483
|
+
}
|
|
484
|
+
getCommands(filter = {}) {
|
|
485
|
+
const conditions = [];
|
|
486
|
+
const params = [];
|
|
487
|
+
if (filter.sessionId) {
|
|
488
|
+
conditions.push("session_id = ?");
|
|
489
|
+
params.push(filter.sessionId);
|
|
490
|
+
}
|
|
491
|
+
if (filter.allowed !== void 0) {
|
|
492
|
+
conditions.push("allowed = ?");
|
|
493
|
+
params.push(filter.allowed ? 1 : 0);
|
|
494
|
+
}
|
|
495
|
+
if (filter.riskLevel) {
|
|
496
|
+
conditions.push("risk_level = ?");
|
|
497
|
+
params.push(filter.riskLevel);
|
|
498
|
+
}
|
|
499
|
+
if (filter.since) {
|
|
500
|
+
conditions.push("timestamp >= ?");
|
|
501
|
+
params.push(filter.since.toISOString());
|
|
502
|
+
}
|
|
503
|
+
if (filter.afterId) {
|
|
504
|
+
conditions.push("id > ?");
|
|
505
|
+
params.push(filter.afterId);
|
|
506
|
+
}
|
|
507
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
508
|
+
const limit = filter.limit ?? 100;
|
|
509
|
+
const offset = filter.offset ?? 0;
|
|
510
|
+
const stmt = this.db.prepare(`
|
|
511
|
+
SELECT * FROM commands
|
|
512
|
+
${whereClause}
|
|
513
|
+
ORDER BY timestamp DESC
|
|
514
|
+
LIMIT ? OFFSET ?
|
|
515
|
+
`);
|
|
516
|
+
params.push(limit, offset);
|
|
517
|
+
const rows = stmt.all(...params);
|
|
518
|
+
return rows.map((row) => this.rowToCommand(row));
|
|
519
|
+
}
|
|
520
|
+
getCommandsBySession(sessionId, limit = 100) {
|
|
521
|
+
return this.getCommands({ sessionId, limit });
|
|
522
|
+
}
|
|
523
|
+
getLiveCommands(limit = 20) {
|
|
524
|
+
const stmt = this.db.prepare(`
|
|
525
|
+
SELECT * FROM commands
|
|
526
|
+
ORDER BY timestamp DESC
|
|
527
|
+
LIMIT ?
|
|
528
|
+
`);
|
|
529
|
+
const rows = stmt.all(limit);
|
|
530
|
+
return rows.map((row) => this.rowToCommand(row));
|
|
531
|
+
}
|
|
532
|
+
rowToCommand(row) {
|
|
533
|
+
return {
|
|
534
|
+
id: row.id,
|
|
535
|
+
sessionId: row.session_id,
|
|
536
|
+
timestamp: new Date(row.timestamp),
|
|
537
|
+
command: row.command,
|
|
538
|
+
allowed: row.allowed === 1,
|
|
539
|
+
riskScore: row.risk_score,
|
|
540
|
+
riskLevel: row.risk_level,
|
|
541
|
+
riskFactors: JSON.parse(row.risk_factors),
|
|
542
|
+
durationMs: row.duration_ms,
|
|
543
|
+
violations: JSON.parse(row.violations)
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
// ─────────────────────────────────────────────────────────────
|
|
547
|
+
// Bro Events
|
|
548
|
+
// ─────────────────────────────────────────────────────────────
|
|
549
|
+
insertBroEvent(input) {
|
|
550
|
+
const id = randomUUID();
|
|
551
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
552
|
+
const stmt = this.db.prepare(`
|
|
553
|
+
INSERT INTO bro_events (id, session_id, timestamp, event_type, input_context, output_summary, model_used, latency_ms, success)
|
|
554
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
555
|
+
`);
|
|
556
|
+
stmt.run(
|
|
557
|
+
id,
|
|
558
|
+
input.sessionId ?? null,
|
|
559
|
+
timestamp,
|
|
560
|
+
input.eventType,
|
|
561
|
+
input.inputContext,
|
|
562
|
+
input.outputSummary,
|
|
563
|
+
input.modelUsed,
|
|
564
|
+
input.latencyMs,
|
|
565
|
+
input.success ? 1 : 0
|
|
566
|
+
);
|
|
567
|
+
return id;
|
|
568
|
+
}
|
|
569
|
+
getBroEvents(limit = 100, sessionId) {
|
|
570
|
+
let stmt;
|
|
571
|
+
let rows;
|
|
572
|
+
if (sessionId) {
|
|
573
|
+
stmt = this.db.prepare(`
|
|
574
|
+
SELECT * FROM bro_events
|
|
575
|
+
WHERE session_id = ?
|
|
576
|
+
ORDER BY timestamp DESC
|
|
577
|
+
LIMIT ?
|
|
578
|
+
`);
|
|
579
|
+
rows = stmt.all(sessionId, limit);
|
|
580
|
+
} else {
|
|
581
|
+
stmt = this.db.prepare(`
|
|
582
|
+
SELECT * FROM bro_events
|
|
583
|
+
ORDER BY timestamp DESC
|
|
584
|
+
LIMIT ?
|
|
585
|
+
`);
|
|
586
|
+
rows = stmt.all(limit);
|
|
587
|
+
}
|
|
588
|
+
return rows.map((row) => ({
|
|
589
|
+
id: row.id,
|
|
590
|
+
sessionId: row.session_id,
|
|
591
|
+
timestamp: new Date(row.timestamp),
|
|
592
|
+
eventType: row.event_type,
|
|
593
|
+
inputContext: row.input_context,
|
|
594
|
+
outputSummary: row.output_summary,
|
|
595
|
+
modelUsed: row.model_used,
|
|
596
|
+
latencyMs: row.latency_ms,
|
|
597
|
+
success: row.success === 1
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
// ─────────────────────────────────────────────────────────────
|
|
601
|
+
// Bro Status
|
|
602
|
+
// ─────────────────────────────────────────────────────────────
|
|
603
|
+
updateBroStatus(input) {
|
|
604
|
+
const id = randomUUID();
|
|
605
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
606
|
+
const stmt = this.db.prepare(`
|
|
607
|
+
INSERT INTO bro_status (id, timestamp, ollama_available, ollama_model, platform, shell, project_type)
|
|
608
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
609
|
+
`);
|
|
610
|
+
stmt.run(
|
|
611
|
+
id,
|
|
612
|
+
timestamp,
|
|
613
|
+
input.ollamaAvailable ? 1 : 0,
|
|
614
|
+
input.ollamaModel,
|
|
615
|
+
input.platform,
|
|
616
|
+
input.shell,
|
|
617
|
+
input.projectType ?? null
|
|
618
|
+
);
|
|
619
|
+
return id;
|
|
620
|
+
}
|
|
621
|
+
getLatestBroStatus() {
|
|
622
|
+
const stmt = this.db.prepare(`
|
|
623
|
+
SELECT * FROM bro_status
|
|
624
|
+
ORDER BY timestamp DESC
|
|
625
|
+
LIMIT 1
|
|
626
|
+
`);
|
|
627
|
+
const row = stmt.get();
|
|
628
|
+
if (!row) return null;
|
|
629
|
+
return {
|
|
630
|
+
id: row.id,
|
|
631
|
+
timestamp: new Date(row.timestamp),
|
|
632
|
+
ollamaAvailable: row.ollama_available === 1,
|
|
633
|
+
ollamaModel: row.ollama_model,
|
|
634
|
+
platform: row.platform,
|
|
635
|
+
shell: row.shell,
|
|
636
|
+
projectType: row.project_type
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
// ─────────────────────────────────────────────────────────────
|
|
640
|
+
// Session Metrics
|
|
641
|
+
// ─────────────────────────────────────────────────────────────
|
|
642
|
+
getSessionMetrics(sessionId) {
|
|
643
|
+
const totalRow = this.db.prepare(`
|
|
644
|
+
SELECT COUNT(*) as count FROM commands WHERE session_id = ?
|
|
645
|
+
`).get(sessionId);
|
|
646
|
+
const allowedRow = this.db.prepare(`
|
|
647
|
+
SELECT COUNT(*) as count FROM commands WHERE session_id = ? AND allowed = 1
|
|
648
|
+
`).get(sessionId);
|
|
649
|
+
const blockedRow = this.db.prepare(`
|
|
650
|
+
SELECT COUNT(*) as count FROM commands WHERE session_id = ? AND allowed = 0
|
|
651
|
+
`).get(sessionId);
|
|
652
|
+
const avgRow = this.db.prepare(`
|
|
653
|
+
SELECT AVG(risk_score) as avg FROM commands WHERE session_id = ?
|
|
654
|
+
`).get(sessionId);
|
|
655
|
+
const riskRows = this.db.prepare(`
|
|
656
|
+
SELECT risk_level, COUNT(*) as count FROM commands WHERE session_id = ? GROUP BY risk_level
|
|
657
|
+
`).all(sessionId);
|
|
658
|
+
const riskDistribution = {};
|
|
659
|
+
for (const row of riskRows) {
|
|
660
|
+
riskDistribution[row.risk_level] = row.count;
|
|
661
|
+
}
|
|
662
|
+
const cmdRows = this.db.prepare(`
|
|
663
|
+
SELECT command, COUNT(*) as count FROM commands WHERE session_id = ?
|
|
664
|
+
GROUP BY command ORDER BY count DESC LIMIT 10
|
|
665
|
+
`).all(sessionId);
|
|
666
|
+
return {
|
|
667
|
+
totalCommands: totalRow.count,
|
|
668
|
+
allowedCommands: allowedRow.count,
|
|
669
|
+
blockedCommands: blockedRow.count,
|
|
670
|
+
avgRiskScore: avgRow.avg ?? 0,
|
|
671
|
+
riskDistribution,
|
|
672
|
+
topCommands: cmdRows
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
// ─────────────────────────────────────────────────────────────
|
|
676
|
+
// Stats
|
|
677
|
+
// ─────────────────────────────────────────────────────────────
|
|
678
|
+
getStats() {
|
|
679
|
+
const totalEventsRow = this.db.prepare("SELECT COUNT(*) as count FROM events").get();
|
|
680
|
+
const sourceRows = this.db.prepare(`
|
|
681
|
+
SELECT source, COUNT(*) as count FROM events GROUP BY source
|
|
682
|
+
`).all();
|
|
683
|
+
const eventsBySource = {};
|
|
684
|
+
for (const row of sourceRows) {
|
|
685
|
+
eventsBySource[row.source] = row.count;
|
|
686
|
+
}
|
|
687
|
+
const levelRows = this.db.prepare(`
|
|
688
|
+
SELECT level, COUNT(*) as count FROM events GROUP BY level
|
|
689
|
+
`).all();
|
|
690
|
+
const eventsByLevel = {};
|
|
691
|
+
for (const row of levelRows) {
|
|
692
|
+
eventsByLevel[row.level] = row.count;
|
|
693
|
+
}
|
|
694
|
+
const pendingBlocksRow = this.db.prepare(`
|
|
695
|
+
SELECT COUNT(*) as count FROM egress_blocks WHERE status = 'pending'
|
|
696
|
+
`).get();
|
|
697
|
+
const connectorCountRow = this.db.prepare(`
|
|
698
|
+
SELECT COUNT(DISTINCT connector) as count FROM connector_events
|
|
699
|
+
`).get();
|
|
700
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
701
|
+
const recentExposuresRow = this.db.prepare(`
|
|
702
|
+
SELECT COUNT(*) as count FROM exposure_scans WHERE timestamp >= ?
|
|
703
|
+
`).get(oneDayAgo);
|
|
704
|
+
const activeSessionsRow = this.db.prepare(`
|
|
705
|
+
SELECT COUNT(*) as count FROM sessions WHERE status = 'active'
|
|
706
|
+
`).get();
|
|
707
|
+
const todayStart = /* @__PURE__ */ new Date();
|
|
708
|
+
todayStart.setHours(0, 0, 0, 0);
|
|
709
|
+
const todayCommandsRow = this.db.prepare(`
|
|
710
|
+
SELECT COUNT(*) as count FROM commands WHERE timestamp >= ?
|
|
711
|
+
`).get(todayStart.toISOString());
|
|
712
|
+
const todayViolationsRow = this.db.prepare(`
|
|
713
|
+
SELECT COUNT(*) as count FROM commands WHERE timestamp >= ? AND allowed = 0
|
|
714
|
+
`).get(todayStart.toISOString());
|
|
715
|
+
const avgRiskRow = this.db.prepare(`
|
|
716
|
+
SELECT AVG(risk_score) as avg FROM commands WHERE timestamp >= ?
|
|
717
|
+
`).get(oneDayAgo);
|
|
718
|
+
const latestStatus = this.getLatestBroStatus();
|
|
719
|
+
let ollamaStatus = "unknown";
|
|
720
|
+
if (latestStatus) {
|
|
721
|
+
ollamaStatus = latestStatus.ollamaAvailable ? "connected" : "disconnected";
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
totalEvents: totalEventsRow.count,
|
|
725
|
+
eventsBySource,
|
|
726
|
+
eventsByLevel,
|
|
727
|
+
pendingBlocks: pendingBlocksRow.count,
|
|
728
|
+
connectorCount: connectorCountRow.count,
|
|
729
|
+
recentExposures: recentExposuresRow.count,
|
|
730
|
+
activeSessions: activeSessionsRow.count,
|
|
731
|
+
todayCommands: todayCommandsRow.count,
|
|
732
|
+
todayViolations: todayViolationsRow.count,
|
|
733
|
+
avgRiskScore24h: avgRiskRow.avg ?? 0,
|
|
734
|
+
ollamaStatus
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
// ─────────────────────────────────────────────────────────────
|
|
738
|
+
// Maintenance
|
|
739
|
+
// ─────────────────────────────────────────────────────────────
|
|
740
|
+
cleanup(olderThanDays = 30) {
|
|
741
|
+
const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
742
|
+
const eventsDeleted = this.db.prepare("DELETE FROM events WHERE timestamp < ?").run(cutoff).changes;
|
|
743
|
+
const connectorDeleted = this.db.prepare("DELETE FROM connector_events WHERE timestamp < ?").run(cutoff).changes;
|
|
744
|
+
const blocksDeleted = this.db.prepare(`
|
|
745
|
+
DELETE FROM egress_blocks WHERE timestamp < ? AND status != 'pending'
|
|
746
|
+
`).run(cutoff).changes;
|
|
747
|
+
const exposuresDeleted = this.db.prepare("DELETE FROM exposure_scans WHERE timestamp < ?").run(cutoff).changes;
|
|
748
|
+
const commandsDeleted = this.db.prepare("DELETE FROM commands WHERE timestamp < ?").run(cutoff).changes;
|
|
749
|
+
const sessionsDeleted = this.db.prepare(`
|
|
750
|
+
DELETE FROM sessions WHERE start_time < ? AND status != 'active'
|
|
751
|
+
`).run(cutoff).changes;
|
|
752
|
+
const broEventsDeleted = this.db.prepare("DELETE FROM bro_events WHERE timestamp < ?").run(cutoff).changes;
|
|
753
|
+
const broStatusDeleted = this.db.prepare("DELETE FROM bro_status WHERE timestamp < ?").run(cutoff).changes;
|
|
754
|
+
return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted + commandsDeleted + sessionsDeleted + broEventsDeleted + broStatusDeleted;
|
|
755
|
+
}
|
|
756
|
+
close() {
|
|
757
|
+
this.db.close();
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
var db_default = DashboardDB;
|
|
761
|
+
|
|
762
|
+
export {
|
|
763
|
+
DashboardDB,
|
|
764
|
+
db_default
|
|
765
|
+
};
|
|
766
|
+
//# sourceMappingURL=chunk-YUMNBQAY.js.map
|