bashbros 0.1.0 → 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-SB4JS3GU.js → chunk-BW6XCOJH.js} +171 -21
- 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-4R4GV5V2.js → chunk-SQCP6IYB.js} +2 -2
- package/dist/{chunk-43W3RVEL.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 +633 -29
- package/dist/cli.js.map +1 -1
- package/dist/{config-CZMIGNPF.js → config-JLLOTFLI.js} +2 -2
- package/dist/{db-EHQDB5OL.js → db-OBKEXRTP.js} +2 -2
- package/dist/{display-IN4NRJJS.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-43W3RVEL.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-SB4JS3GU.js.map +0 -1
- package/dist/engine-PKLXW6OF.js +0 -9
- /package/dist/{chunk-4R4GV5V2.js.map → chunk-SQCP6IYB.js.map} +0 -0
- /package/dist/{config-CZMIGNPF.js.map → config-JLLOTFLI.js.map} +0 -0
- /package/dist/{db-EHQDB5OL.js.map → db-OBKEXRTP.js.map} +0 -0
- /package/dist/{display-IN4NRJJS.js.map → display-6LZ2HBCU.js.map} +0 -0
- /package/dist/{engine-PKLXW6OF.js.map → engine-EGPAS2EX.js.map} +0 -0
package/dist/chunk-CSRPOGHY.js
DELETED
|
@@ -1,354 +0,0 @@
|
|
|
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
|
-
}
|
|
72
|
-
// ─────────────────────────────────────────────────────────────
|
|
73
|
-
// Events
|
|
74
|
-
// ─────────────────────────────────────────────────────────────
|
|
75
|
-
insertEvent(input) {
|
|
76
|
-
const id = randomUUID();
|
|
77
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
78
|
-
const data = input.data ? JSON.stringify(input.data) : null;
|
|
79
|
-
const stmt = this.db.prepare(`
|
|
80
|
-
INSERT INTO events (id, timestamp, source, level, category, message, data)
|
|
81
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
82
|
-
`);
|
|
83
|
-
stmt.run(id, timestamp, input.source, input.level, input.category, input.message, data);
|
|
84
|
-
return id;
|
|
85
|
-
}
|
|
86
|
-
getEvents(filter = {}) {
|
|
87
|
-
const conditions = [];
|
|
88
|
-
const params = [];
|
|
89
|
-
if (filter.source) {
|
|
90
|
-
conditions.push("source = ?");
|
|
91
|
-
params.push(filter.source);
|
|
92
|
-
}
|
|
93
|
-
if (filter.level) {
|
|
94
|
-
conditions.push("level = ?");
|
|
95
|
-
params.push(filter.level);
|
|
96
|
-
}
|
|
97
|
-
if (filter.category) {
|
|
98
|
-
conditions.push("category = ?");
|
|
99
|
-
params.push(filter.category);
|
|
100
|
-
}
|
|
101
|
-
if (filter.since) {
|
|
102
|
-
conditions.push("timestamp >= ?");
|
|
103
|
-
params.push(filter.since.toISOString());
|
|
104
|
-
}
|
|
105
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
106
|
-
const limit = filter.limit ?? 100;
|
|
107
|
-
const offset = filter.offset ?? 0;
|
|
108
|
-
const stmt = this.db.prepare(`
|
|
109
|
-
SELECT * FROM events
|
|
110
|
-
${whereClause}
|
|
111
|
-
ORDER BY timestamp DESC
|
|
112
|
-
LIMIT ? OFFSET ?
|
|
113
|
-
`);
|
|
114
|
-
params.push(limit, offset);
|
|
115
|
-
const rows = stmt.all(...params);
|
|
116
|
-
return rows.map((row) => ({
|
|
117
|
-
id: row.id,
|
|
118
|
-
timestamp: new Date(row.timestamp),
|
|
119
|
-
source: row.source,
|
|
120
|
-
level: row.level,
|
|
121
|
-
category: row.category,
|
|
122
|
-
message: row.message,
|
|
123
|
-
data: row.data ? JSON.parse(row.data) : void 0
|
|
124
|
-
}));
|
|
125
|
-
}
|
|
126
|
-
// ─────────────────────────────────────────────────────────────
|
|
127
|
-
// Connector Events
|
|
128
|
-
// ─────────────────────────────────────────────────────────────
|
|
129
|
-
insertConnectorEvent(input) {
|
|
130
|
-
const id = randomUUID();
|
|
131
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
132
|
-
const stmt = this.db.prepare(`
|
|
133
|
-
INSERT INTO connector_events (id, timestamp, connector, method, direction, payload, resources_accessed)
|
|
134
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
135
|
-
`);
|
|
136
|
-
stmt.run(
|
|
137
|
-
id,
|
|
138
|
-
timestamp,
|
|
139
|
-
input.connector,
|
|
140
|
-
input.method,
|
|
141
|
-
input.direction,
|
|
142
|
-
JSON.stringify(input.payload),
|
|
143
|
-
JSON.stringify(input.resourcesAccessed)
|
|
144
|
-
);
|
|
145
|
-
return id;
|
|
146
|
-
}
|
|
147
|
-
getConnectorEvents(connector, limit = 100) {
|
|
148
|
-
const stmt = this.db.prepare(`
|
|
149
|
-
SELECT * FROM connector_events
|
|
150
|
-
WHERE connector = ?
|
|
151
|
-
ORDER BY timestamp DESC
|
|
152
|
-
LIMIT ?
|
|
153
|
-
`);
|
|
154
|
-
const rows = stmt.all(connector, limit);
|
|
155
|
-
return rows.map((row) => ({
|
|
156
|
-
id: row.id,
|
|
157
|
-
timestamp: new Date(row.timestamp),
|
|
158
|
-
connector: row.connector,
|
|
159
|
-
method: row.method,
|
|
160
|
-
direction: row.direction,
|
|
161
|
-
payload: JSON.parse(row.payload),
|
|
162
|
-
resourcesAccessed: JSON.parse(row.resources_accessed)
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
165
|
-
getAllConnectorEvents(limit = 100) {
|
|
166
|
-
const stmt = this.db.prepare(`
|
|
167
|
-
SELECT * FROM connector_events
|
|
168
|
-
ORDER BY timestamp DESC
|
|
169
|
-
LIMIT ?
|
|
170
|
-
`);
|
|
171
|
-
const rows = stmt.all(limit);
|
|
172
|
-
return rows.map((row) => ({
|
|
173
|
-
id: row.id,
|
|
174
|
-
timestamp: new Date(row.timestamp),
|
|
175
|
-
connector: row.connector,
|
|
176
|
-
method: row.method,
|
|
177
|
-
direction: row.direction,
|
|
178
|
-
payload: JSON.parse(row.payload),
|
|
179
|
-
resourcesAccessed: JSON.parse(row.resources_accessed)
|
|
180
|
-
}));
|
|
181
|
-
}
|
|
182
|
-
// ─────────────────────────────────────────────────────────────
|
|
183
|
-
// Egress Blocks
|
|
184
|
-
// ─────────────────────────────────────────────────────────────
|
|
185
|
-
insertEgressBlock(input) {
|
|
186
|
-
const id = randomUUID();
|
|
187
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
188
|
-
const stmt = this.db.prepare(`
|
|
189
|
-
INSERT INTO egress_blocks (id, timestamp, pattern, matched_text, redacted_text, connector, destination, status)
|
|
190
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
|
|
191
|
-
`);
|
|
192
|
-
stmt.run(
|
|
193
|
-
id,
|
|
194
|
-
timestamp,
|
|
195
|
-
JSON.stringify(input.pattern),
|
|
196
|
-
input.matchedText,
|
|
197
|
-
input.redactedText,
|
|
198
|
-
input.connector ?? null,
|
|
199
|
-
input.destination ?? null
|
|
200
|
-
);
|
|
201
|
-
return id;
|
|
202
|
-
}
|
|
203
|
-
getPendingBlocks() {
|
|
204
|
-
const stmt = this.db.prepare(`
|
|
205
|
-
SELECT * FROM egress_blocks
|
|
206
|
-
WHERE status = 'pending'
|
|
207
|
-
ORDER BY timestamp DESC
|
|
208
|
-
`);
|
|
209
|
-
const rows = stmt.all();
|
|
210
|
-
return rows.map((row) => this.rowToEgressMatch(row));
|
|
211
|
-
}
|
|
212
|
-
getBlock(id) {
|
|
213
|
-
const stmt = this.db.prepare(`
|
|
214
|
-
SELECT * FROM egress_blocks WHERE id = ?
|
|
215
|
-
`);
|
|
216
|
-
const row = stmt.get(id);
|
|
217
|
-
if (!row) return null;
|
|
218
|
-
return this.rowToEgressMatch(row);
|
|
219
|
-
}
|
|
220
|
-
approveBlock(id, approvedBy) {
|
|
221
|
-
const stmt = this.db.prepare(`
|
|
222
|
-
UPDATE egress_blocks
|
|
223
|
-
SET status = 'approved', approved_by = ?, approved_at = ?
|
|
224
|
-
WHERE id = ?
|
|
225
|
-
`);
|
|
226
|
-
stmt.run(approvedBy, (/* @__PURE__ */ new Date()).toISOString(), id);
|
|
227
|
-
}
|
|
228
|
-
denyBlock(id, deniedBy) {
|
|
229
|
-
const stmt = this.db.prepare(`
|
|
230
|
-
UPDATE egress_blocks
|
|
231
|
-
SET status = 'denied', approved_by = ?, approved_at = ?
|
|
232
|
-
WHERE id = ?
|
|
233
|
-
`);
|
|
234
|
-
stmt.run(deniedBy, (/* @__PURE__ */ new Date()).toISOString(), id);
|
|
235
|
-
}
|
|
236
|
-
rowToEgressMatch(row) {
|
|
237
|
-
return {
|
|
238
|
-
id: row.id,
|
|
239
|
-
timestamp: new Date(row.timestamp),
|
|
240
|
-
pattern: JSON.parse(row.pattern),
|
|
241
|
-
matchedText: row.matched_text,
|
|
242
|
-
redactedText: row.redacted_text,
|
|
243
|
-
connector: row.connector ?? void 0,
|
|
244
|
-
destination: row.destination ?? void 0,
|
|
245
|
-
status: row.status,
|
|
246
|
-
approvedBy: row.approved_by ?? void 0,
|
|
247
|
-
approvedAt: row.approved_at ? new Date(row.approved_at) : void 0
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
// ─────────────────────────────────────────────────────────────
|
|
251
|
-
// Exposure Scans
|
|
252
|
-
// ─────────────────────────────────────────────────────────────
|
|
253
|
-
insertExposureScan(result) {
|
|
254
|
-
const id = randomUUID();
|
|
255
|
-
const timestamp = result.timestamp.toISOString();
|
|
256
|
-
const stmt = this.db.prepare(`
|
|
257
|
-
INSERT INTO exposure_scans (id, timestamp, agent, pid, port, bind_address, has_auth, severity, action, message)
|
|
258
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
259
|
-
`);
|
|
260
|
-
stmt.run(
|
|
261
|
-
id,
|
|
262
|
-
timestamp,
|
|
263
|
-
result.agent,
|
|
264
|
-
result.pid ?? null,
|
|
265
|
-
result.port,
|
|
266
|
-
result.bindAddress,
|
|
267
|
-
String(result.hasAuth),
|
|
268
|
-
result.severity,
|
|
269
|
-
result.action,
|
|
270
|
-
result.message
|
|
271
|
-
);
|
|
272
|
-
return id;
|
|
273
|
-
}
|
|
274
|
-
getRecentExposures(limit = 100) {
|
|
275
|
-
const stmt = this.db.prepare(`
|
|
276
|
-
SELECT * FROM exposure_scans
|
|
277
|
-
ORDER BY timestamp DESC
|
|
278
|
-
LIMIT ?
|
|
279
|
-
`);
|
|
280
|
-
const rows = stmt.all(limit);
|
|
281
|
-
return rows.map((row) => ({
|
|
282
|
-
agent: row.agent,
|
|
283
|
-
pid: row.pid ?? void 0,
|
|
284
|
-
port: row.port,
|
|
285
|
-
bindAddress: row.bind_address,
|
|
286
|
-
hasAuth: row.has_auth === "true" ? true : row.has_auth === "false" ? false : "unknown",
|
|
287
|
-
severity: row.severity,
|
|
288
|
-
action: row.action,
|
|
289
|
-
message: row.message,
|
|
290
|
-
timestamp: new Date(row.timestamp)
|
|
291
|
-
}));
|
|
292
|
-
}
|
|
293
|
-
// ─────────────────────────────────────────────────────────────
|
|
294
|
-
// Stats
|
|
295
|
-
// ─────────────────────────────────────────────────────────────
|
|
296
|
-
getStats() {
|
|
297
|
-
const totalEventsRow = this.db.prepare("SELECT COUNT(*) as count FROM events").get();
|
|
298
|
-
const sourceRows = this.db.prepare(`
|
|
299
|
-
SELECT source, COUNT(*) as count FROM events GROUP BY source
|
|
300
|
-
`).all();
|
|
301
|
-
const eventsBySource = {};
|
|
302
|
-
for (const row of sourceRows) {
|
|
303
|
-
eventsBySource[row.source] = row.count;
|
|
304
|
-
}
|
|
305
|
-
const levelRows = this.db.prepare(`
|
|
306
|
-
SELECT level, COUNT(*) as count FROM events GROUP BY level
|
|
307
|
-
`).all();
|
|
308
|
-
const eventsByLevel = {};
|
|
309
|
-
for (const row of levelRows) {
|
|
310
|
-
eventsByLevel[row.level] = row.count;
|
|
311
|
-
}
|
|
312
|
-
const pendingBlocksRow = this.db.prepare(`
|
|
313
|
-
SELECT COUNT(*) as count FROM egress_blocks WHERE status = 'pending'
|
|
314
|
-
`).get();
|
|
315
|
-
const connectorCountRow = this.db.prepare(`
|
|
316
|
-
SELECT COUNT(DISTINCT connector) as count FROM connector_events
|
|
317
|
-
`).get();
|
|
318
|
-
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
319
|
-
const recentExposuresRow = this.db.prepare(`
|
|
320
|
-
SELECT COUNT(*) as count FROM exposure_scans WHERE timestamp >= ?
|
|
321
|
-
`).get(oneDayAgo);
|
|
322
|
-
return {
|
|
323
|
-
totalEvents: totalEventsRow.count,
|
|
324
|
-
eventsBySource,
|
|
325
|
-
eventsByLevel,
|
|
326
|
-
pendingBlocks: pendingBlocksRow.count,
|
|
327
|
-
connectorCount: connectorCountRow.count,
|
|
328
|
-
recentExposures: recentExposuresRow.count
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
// ─────────────────────────────────────────────────────────────
|
|
332
|
-
// Maintenance
|
|
333
|
-
// ─────────────────────────────────────────────────────────────
|
|
334
|
-
cleanup(olderThanDays = 30) {
|
|
335
|
-
const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
336
|
-
const eventsDeleted = this.db.prepare("DELETE FROM events WHERE timestamp < ?").run(cutoff).changes;
|
|
337
|
-
const connectorDeleted = this.db.prepare("DELETE FROM connector_events WHERE timestamp < ?").run(cutoff).changes;
|
|
338
|
-
const blocksDeleted = this.db.prepare(`
|
|
339
|
-
DELETE FROM egress_blocks WHERE timestamp < ? AND status != 'pending'
|
|
340
|
-
`).run(cutoff).changes;
|
|
341
|
-
const exposuresDeleted = this.db.prepare("DELETE FROM exposure_scans WHERE timestamp < ?").run(cutoff).changes;
|
|
342
|
-
return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted;
|
|
343
|
-
}
|
|
344
|
-
close() {
|
|
345
|
-
this.db.close();
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
var db_default = DashboardDB;
|
|
349
|
-
|
|
350
|
-
export {
|
|
351
|
-
DashboardDB,
|
|
352
|
-
db_default
|
|
353
|
-
};
|
|
354
|
-
//# sourceMappingURL=chunk-CSRPOGHY.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dashboard/db.ts"],"sourcesContent":["/**\r\n * Dashboard Database Module\r\n * SQLite-based storage for security events, connector activity, and egress blocks\r\n */\r\n\r\nimport Database from 'better-sqlite3'\r\nimport { randomUUID } from 'crypto'\r\nimport type {\r\n EventSource,\r\n EventLevel,\r\n UnifiedEvent,\r\n ConnectorEvent,\r\n EgressMatch,\r\n EgressPattern,\r\n RedactedPayload,\r\n ExposureResult\r\n} from '../policy/ward/types.js'\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Types\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nexport interface EventFilter {\r\n source?: EventSource\r\n level?: EventLevel\r\n category?: string\r\n since?: Date\r\n limit?: number\r\n offset?: number\r\n}\r\n\r\nexport interface InsertEventInput {\r\n source: EventSource\r\n level: EventLevel\r\n category: string\r\n message: string\r\n data?: Record<string, unknown>\r\n}\r\n\r\nexport interface InsertConnectorEventInput {\r\n connector: string\r\n method: string\r\n direction: 'inbound' | 'outbound'\r\n payload: RedactedPayload\r\n resourcesAccessed: string[]\r\n}\r\n\r\nexport interface InsertEgressBlockInput {\r\n pattern: EgressPattern\r\n matchedText: string\r\n redactedText: string\r\n connector?: string\r\n destination?: string\r\n}\r\n\r\nexport interface DashboardStats {\r\n totalEvents: number\r\n eventsBySource: Record<string, number>\r\n eventsByLevel: Record<string, number>\r\n pendingBlocks: number\r\n connectorCount: number\r\n recentExposures: number\r\n}\r\n\r\n// ─────────────────────────────────────────────────────────────\r\n// Database Class\r\n// ─────────────────────────────────────────────────────────────\r\n\r\nexport class DashboardDB {\r\n private db: Database.Database\r\n\r\n constructor(dbPath: string = '.bashbros.db') {\r\n this.db = new Database(dbPath)\r\n this.db.pragma('journal_mode = WAL')\r\n this.initTables()\r\n }\r\n\r\n private initTables(): void {\r\n // Events table - unified security events\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS events (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n source TEXT NOT NULL,\r\n level TEXT NOT NULL,\r\n category TEXT NOT NULL,\r\n message TEXT NOT NULL,\r\n data TEXT\r\n )\r\n `)\r\n\r\n // Connector events table - MCP connector activity\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS connector_events (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n connector TEXT NOT NULL,\r\n method TEXT NOT NULL,\r\n direction TEXT NOT NULL,\r\n payload TEXT NOT NULL,\r\n resources_accessed TEXT NOT NULL\r\n )\r\n `)\r\n\r\n // Egress blocks table - blocked sensitive data\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS egress_blocks (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n pattern TEXT NOT NULL,\r\n matched_text TEXT NOT NULL,\r\n redacted_text TEXT NOT NULL,\r\n connector TEXT,\r\n destination TEXT,\r\n status TEXT NOT NULL DEFAULT 'pending',\r\n approved_by TEXT,\r\n approved_at TEXT\r\n )\r\n `)\r\n\r\n // Exposure scans table - network exposure scan results\r\n this.db.exec(`\r\n CREATE TABLE IF NOT EXISTS exposure_scans (\r\n id TEXT PRIMARY KEY,\r\n timestamp TEXT NOT NULL,\r\n agent TEXT NOT NULL,\r\n pid INTEGER,\r\n port INTEGER NOT NULL,\r\n bind_address TEXT NOT NULL,\r\n has_auth TEXT NOT NULL,\r\n severity TEXT NOT NULL,\r\n action TEXT NOT NULL,\r\n message TEXT NOT NULL\r\n )\r\n `)\r\n\r\n // Create indexes for common queries\r\n this.db.exec(`\r\n CREATE INDEX IF NOT EXISTS idx_events_source ON events(source);\r\n CREATE INDEX IF NOT EXISTS idx_events_level ON events(level);\r\n CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\r\n CREATE INDEX IF NOT EXISTS idx_connector_events_connector ON connector_events(connector);\r\n CREATE INDEX IF NOT EXISTS idx_egress_blocks_status ON egress_blocks(status);\r\n `)\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Events\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertEvent(input: InsertEventInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n const data = input.data ? JSON.stringify(input.data) : null\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO events (id, timestamp, source, level, category, message, data)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(id, timestamp, input.source, input.level, input.category, input.message, data)\r\n return id\r\n }\r\n\r\n getEvents(filter: EventFilter = {}): UnifiedEvent[] {\r\n const conditions: string[] = []\r\n const params: unknown[] = []\r\n\r\n if (filter.source) {\r\n conditions.push('source = ?')\r\n params.push(filter.source)\r\n }\r\n\r\n if (filter.level) {\r\n conditions.push('level = ?')\r\n params.push(filter.level)\r\n }\r\n\r\n if (filter.category) {\r\n conditions.push('category = ?')\r\n params.push(filter.category)\r\n }\r\n\r\n if (filter.since) {\r\n conditions.push('timestamp >= ?')\r\n params.push(filter.since.toISOString())\r\n }\r\n\r\n const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\r\n const limit = filter.limit ?? 100\r\n const offset = filter.offset ?? 0\r\n\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM events\r\n ${whereClause}\r\n ORDER BY timestamp DESC\r\n LIMIT ? OFFSET ?\r\n `)\r\n\r\n params.push(limit, offset)\r\n const rows = stmt.all(...params) as Array<{\r\n id: string\r\n timestamp: string\r\n source: EventSource\r\n level: EventLevel\r\n category: string\r\n message: string\r\n data: string | null\r\n }>\r\n\r\n return rows.map(row => ({\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n source: row.source,\r\n level: row.level,\r\n category: row.category,\r\n message: row.message,\r\n data: row.data ? JSON.parse(row.data) : undefined\r\n }))\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Connector Events\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertConnectorEvent(input: InsertConnectorEventInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO connector_events (id, timestamp, connector, method, direction, payload, resources_accessed)\r\n VALUES (?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n input.connector,\r\n input.method,\r\n input.direction,\r\n JSON.stringify(input.payload),\r\n JSON.stringify(input.resourcesAccessed)\r\n )\r\n\r\n return id\r\n }\r\n\r\n getConnectorEvents(connector: string, limit: number = 100): ConnectorEvent[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM connector_events\r\n WHERE connector = ?\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n\r\n const rows = stmt.all(connector, limit) as Array<{\r\n id: string\r\n timestamp: string\r\n connector: string\r\n method: string\r\n direction: 'inbound' | 'outbound'\r\n payload: string\r\n resources_accessed: string\r\n }>\r\n\r\n return rows.map(row => ({\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n connector: row.connector,\r\n method: row.method,\r\n direction: row.direction,\r\n payload: JSON.parse(row.payload),\r\n resourcesAccessed: JSON.parse(row.resources_accessed)\r\n }))\r\n }\r\n\r\n getAllConnectorEvents(limit: number = 100): ConnectorEvent[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM connector_events\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n\r\n const rows = stmt.all(limit) as Array<{\r\n id: string\r\n timestamp: string\r\n connector: string\r\n method: string\r\n direction: 'inbound' | 'outbound'\r\n payload: string\r\n resources_accessed: string\r\n }>\r\n\r\n return rows.map(row => ({\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n connector: row.connector,\r\n method: row.method,\r\n direction: row.direction,\r\n payload: JSON.parse(row.payload),\r\n resourcesAccessed: JSON.parse(row.resources_accessed)\r\n }))\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Egress Blocks\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertEgressBlock(input: InsertEgressBlockInput): string {\r\n const id = randomUUID()\r\n const timestamp = new Date().toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO egress_blocks (id, timestamp, pattern, matched_text, redacted_text, connector, destination, status)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n JSON.stringify(input.pattern),\r\n input.matchedText,\r\n input.redactedText,\r\n input.connector ?? null,\r\n input.destination ?? null\r\n )\r\n\r\n return id\r\n }\r\n\r\n getPendingBlocks(): EgressMatch[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM egress_blocks\r\n WHERE status = 'pending'\r\n ORDER BY timestamp DESC\r\n `)\r\n\r\n const rows = stmt.all() as Array<{\r\n id: string\r\n timestamp: string\r\n pattern: string\r\n matched_text: string\r\n redacted_text: string\r\n connector: string | null\r\n destination: string | null\r\n status: 'pending' | 'approved' | 'denied'\r\n approved_by: string | null\r\n approved_at: string | null\r\n }>\r\n\r\n return rows.map(row => this.rowToEgressMatch(row))\r\n }\r\n\r\n getBlock(id: string): EgressMatch | null {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM egress_blocks WHERE id = ?\r\n `)\r\n\r\n const row = stmt.get(id) as {\r\n id: string\r\n timestamp: string\r\n pattern: string\r\n matched_text: string\r\n redacted_text: string\r\n connector: string | null\r\n destination: string | null\r\n status: 'pending' | 'approved' | 'denied'\r\n approved_by: string | null\r\n approved_at: string | null\r\n } | undefined\r\n\r\n if (!row) return null\r\n return this.rowToEgressMatch(row)\r\n }\r\n\r\n approveBlock(id: string, approvedBy: string): void {\r\n const stmt = this.db.prepare(`\r\n UPDATE egress_blocks\r\n SET status = 'approved', approved_by = ?, approved_at = ?\r\n WHERE id = ?\r\n `)\r\n\r\n stmt.run(approvedBy, new Date().toISOString(), id)\r\n }\r\n\r\n denyBlock(id: string, deniedBy: string): void {\r\n const stmt = this.db.prepare(`\r\n UPDATE egress_blocks\r\n SET status = 'denied', approved_by = ?, approved_at = ?\r\n WHERE id = ?\r\n `)\r\n\r\n stmt.run(deniedBy, new Date().toISOString(), id)\r\n }\r\n\r\n private rowToEgressMatch(row: {\r\n id: string\r\n timestamp: string\r\n pattern: string\r\n matched_text: string\r\n redacted_text: string\r\n connector: string | null\r\n destination: string | null\r\n status: 'pending' | 'approved' | 'denied'\r\n approved_by: string | null\r\n approved_at: string | null\r\n }): EgressMatch {\r\n return {\r\n id: row.id,\r\n timestamp: new Date(row.timestamp),\r\n pattern: JSON.parse(row.pattern),\r\n matchedText: row.matched_text,\r\n redactedText: row.redacted_text,\r\n connector: row.connector ?? undefined,\r\n destination: row.destination ?? undefined,\r\n status: row.status,\r\n approvedBy: row.approved_by ?? undefined,\r\n approvedAt: row.approved_at ? new Date(row.approved_at) : undefined\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Exposure Scans\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n insertExposureScan(result: ExposureResult): string {\r\n const id = randomUUID()\r\n const timestamp = result.timestamp.toISOString()\r\n\r\n const stmt = this.db.prepare(`\r\n INSERT INTO exposure_scans (id, timestamp, agent, pid, port, bind_address, has_auth, severity, action, message)\r\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\r\n `)\r\n\r\n stmt.run(\r\n id,\r\n timestamp,\r\n result.agent,\r\n result.pid ?? null,\r\n result.port,\r\n result.bindAddress,\r\n String(result.hasAuth),\r\n result.severity,\r\n result.action,\r\n result.message\r\n )\r\n\r\n return id\r\n }\r\n\r\n getRecentExposures(limit: number = 100): ExposureResult[] {\r\n const stmt = this.db.prepare(`\r\n SELECT * FROM exposure_scans\r\n ORDER BY timestamp DESC\r\n LIMIT ?\r\n `)\r\n\r\n const rows = stmt.all(limit) as Array<{\r\n id: string\r\n timestamp: string\r\n agent: string\r\n pid: number | null\r\n port: number\r\n bind_address: string\r\n has_auth: string\r\n severity: string\r\n action: string\r\n message: string\r\n }>\r\n\r\n return rows.map(row => ({\r\n agent: row.agent,\r\n pid: row.pid ?? undefined,\r\n port: row.port,\r\n bindAddress: row.bind_address,\r\n hasAuth: row.has_auth === 'true' ? true : row.has_auth === 'false' ? false : 'unknown' as const,\r\n severity: row.severity as ExposureResult['severity'],\r\n action: row.action as ExposureResult['action'],\r\n message: row.message,\r\n timestamp: new Date(row.timestamp)\r\n }))\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Stats\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n getStats(): DashboardStats {\r\n // Total events\r\n const totalEventsRow = this.db.prepare('SELECT COUNT(*) as count FROM events').get() as { count: number }\r\n\r\n // Events by source\r\n const sourceRows = this.db.prepare(`\r\n SELECT source, COUNT(*) as count FROM events GROUP BY source\r\n `).all() as Array<{ source: string; count: number }>\r\n\r\n const eventsBySource: Record<string, number> = {}\r\n for (const row of sourceRows) {\r\n eventsBySource[row.source] = row.count\r\n }\r\n\r\n // Events by level\r\n const levelRows = this.db.prepare(`\r\n SELECT level, COUNT(*) as count FROM events GROUP BY level\r\n `).all() as Array<{ level: string; count: number }>\r\n\r\n const eventsByLevel: Record<string, number> = {}\r\n for (const row of levelRows) {\r\n eventsByLevel[row.level] = row.count\r\n }\r\n\r\n // Pending blocks\r\n const pendingBlocksRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM egress_blocks WHERE status = 'pending'\r\n `).get() as { count: number }\r\n\r\n // Unique connectors\r\n const connectorCountRow = this.db.prepare(`\r\n SELECT COUNT(DISTINCT connector) as count FROM connector_events\r\n `).get() as { count: number }\r\n\r\n // Recent exposures (last 24 hours)\r\n const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\r\n const recentExposuresRow = this.db.prepare(`\r\n SELECT COUNT(*) as count FROM exposure_scans WHERE timestamp >= ?\r\n `).get(oneDayAgo) as { count: number }\r\n\r\n return {\r\n totalEvents: totalEventsRow.count,\r\n eventsBySource,\r\n eventsByLevel,\r\n pendingBlocks: pendingBlocksRow.count,\r\n connectorCount: connectorCountRow.count,\r\n recentExposures: recentExposuresRow.count\r\n }\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────\r\n // Maintenance\r\n // ─────────────────────────────────────────────────────────────\r\n\r\n cleanup(olderThanDays: number = 30): number {\r\n const cutoff = new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000).toISOString()\r\n\r\n const eventsDeleted = this.db.prepare('DELETE FROM events WHERE timestamp < ?').run(cutoff).changes\r\n const connectorDeleted = this.db.prepare('DELETE FROM connector_events WHERE timestamp < ?').run(cutoff).changes\r\n const blocksDeleted = this.db.prepare(`\r\n DELETE FROM egress_blocks WHERE timestamp < ? AND status != 'pending'\r\n `).run(cutoff).changes\r\n const exposuresDeleted = this.db.prepare('DELETE FROM exposure_scans WHERE timestamp < ?').run(cutoff).changes\r\n\r\n return eventsDeleted + connectorDeleted + blocksDeleted + exposuresDeleted\r\n }\r\n\r\n close(): void {\r\n this.db.close()\r\n }\r\n}\r\n\r\nexport default DashboardDB\r\n"],"mappings":";;;AAKA,OAAO,cAAc;AACrB,SAAS,kBAAkB;AA8DpB,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EAER,YAAY,SAAiB,gBAAgB;AAC3C,SAAK,KAAK,IAAI,SAAS,MAAM;AAC7B,SAAK,GAAG,OAAO,oBAAoB;AACnC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,aAAmB;AAEzB,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAUZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAUZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAaZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAaZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAMZ;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,OAAiC;AAC3C,UAAM,KAAK,WAAW;AACtB,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,UAAM,OAAO,MAAM,OAAO,KAAK,UAAU,MAAM,IAAI,IAAI;AAEvD,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG5B;AAED,SAAK,IAAI,IAAI,WAAW,MAAM,QAAQ,MAAM,OAAO,MAAM,UAAU,MAAM,SAAS,IAAI;AACtF,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,SAAsB,CAAC,GAAmB;AAClD,UAAM,aAAuB,CAAC;AAC9B,UAAM,SAAoB,CAAC;AAE3B,QAAI,OAAO,QAAQ;AACjB,iBAAW,KAAK,YAAY;AAC5B,aAAO,KAAK,OAAO,MAAM;AAAA,IAC3B;AAEA,QAAI,OAAO,OAAO;AAChB,iBAAW,KAAK,WAAW;AAC3B,aAAO,KAAK,OAAO,KAAK;AAAA,IAC1B;AAEA,QAAI,OAAO,UAAU;AACnB,iBAAW,KAAK,cAAc;AAC9B,aAAO,KAAK,OAAO,QAAQ;AAAA,IAC7B;AAEA,QAAI,OAAO,OAAO;AAChB,iBAAW,KAAK,gBAAgB;AAChC,aAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AAAA,IACxC;AAEA,UAAM,cAAc,WAAW,SAAS,IAAI,SAAS,WAAW,KAAK,OAAO,CAAC,KAAK;AAClF,UAAM,QAAQ,OAAO,SAAS;AAC9B,UAAM,SAAS,OAAO,UAAU;AAEhC,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA,QAEzB,WAAW;AAAA;AAAA;AAAA,KAGd;AAED,WAAO,KAAK,OAAO,MAAM;AACzB,UAAM,OAAO,KAAK,IAAI,GAAG,MAAM;AAU/B,WAAO,KAAK,IAAI,UAAQ;AAAA,MACtB,IAAI,IAAI;AAAA,MACR,WAAW,IAAI,KAAK,IAAI,SAAS;AAAA,MACjC,QAAQ,IAAI;AAAA,MACZ,OAAO,IAAI;AAAA,MACX,UAAU,IAAI;AAAA,MACd,SAAS,IAAI;AAAA,MACb,MAAM,IAAI,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI;AAAA,IAC1C,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAMA,qBAAqB,OAA0C;AAC7D,UAAM,KAAK,WAAW;AACtB,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG5B;AAED,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK,UAAU,MAAM,OAAO;AAAA,MAC5B,KAAK,UAAU,MAAM,iBAAiB;AAAA,IACxC;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,mBAAmB,WAAmB,QAAgB,KAAuB;AAC3E,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,KAK5B;AAED,UAAM,OAAO,KAAK,IAAI,WAAW,KAAK;AAUtC,WAAO,KAAK,IAAI,UAAQ;AAAA,MACtB,IAAI,IAAI;AAAA,MACR,WAAW,IAAI,KAAK,IAAI,SAAS;AAAA,MACjC,WAAW,IAAI;AAAA,MACf,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,MACf,SAAS,KAAK,MAAM,IAAI,OAAO;AAAA,MAC/B,mBAAmB,KAAK,MAAM,IAAI,kBAAkB;AAAA,IACtD,EAAE;AAAA,EACJ;AAAA,EAEA,sBAAsB,QAAgB,KAAuB;AAC3D,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,KAI5B;AAED,UAAM,OAAO,KAAK,IAAI,KAAK;AAU3B,WAAO,KAAK,IAAI,UAAQ;AAAA,MACtB,IAAI,IAAI;AAAA,MACR,WAAW,IAAI,KAAK,IAAI,SAAS;AAAA,MACjC,WAAW,IAAI;AAAA,MACf,QAAQ,IAAI;AAAA,MACZ,WAAW,IAAI;AAAA,MACf,SAAS,KAAK,MAAM,IAAI,OAAO;AAAA,MAC/B,mBAAmB,KAAK,MAAM,IAAI,kBAAkB;AAAA,IACtD,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB,OAAuC;AACvD,UAAM,KAAK,WAAW;AACtB,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG5B;AAED,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA,KAAK,UAAU,MAAM,OAAO;AAAA,MAC5B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,aAAa;AAAA,MACnB,MAAM,eAAe;AAAA,IACvB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,mBAAkC;AAChC,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,KAI5B;AAED,UAAM,OAAO,KAAK,IAAI;AAatB,WAAO,KAAK,IAAI,SAAO,KAAK,iBAAiB,GAAG,CAAC;AAAA,EACnD;AAAA,EAEA,SAAS,IAAgC;AACvC,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA,KAE5B;AAED,UAAM,MAAM,KAAK,IAAI,EAAE;AAavB,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,KAAK,iBAAiB,GAAG;AAAA,EAClC;AAAA,EAEA,aAAa,IAAY,YAA0B;AACjD,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,KAI5B;AAED,SAAK,IAAI,aAAY,oBAAI,KAAK,GAAE,YAAY,GAAG,EAAE;AAAA,EACnD;AAAA,EAEA,UAAU,IAAY,UAAwB;AAC5C,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,KAI5B;AAED,SAAK,IAAI,WAAU,oBAAI,KAAK,GAAE,YAAY,GAAG,EAAE;AAAA,EACjD;AAAA,EAEQ,iBAAiB,KAWT;AACd,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,WAAW,IAAI,KAAK,IAAI,SAAS;AAAA,MACjC,SAAS,KAAK,MAAM,IAAI,OAAO;AAAA,MAC/B,aAAa,IAAI;AAAA,MACjB,cAAc,IAAI;AAAA,MAClB,WAAW,IAAI,aAAa;AAAA,MAC5B,aAAa,IAAI,eAAe;AAAA,MAChC,QAAQ,IAAI;AAAA,MACZ,YAAY,IAAI,eAAe;AAAA,MAC/B,YAAY,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,QAAgC;AACjD,UAAM,KAAK,WAAW;AACtB,UAAM,YAAY,OAAO,UAAU,YAAY;AAE/C,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG5B;AAED,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,OAAO,OAAO;AAAA,MACd,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO,OAAO,OAAO;AAAA,MACrB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,mBAAmB,QAAgB,KAAuB;AACxD,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,KAI5B;AAED,UAAM,OAAO,KAAK,IAAI,KAAK;AAa3B,WAAO,KAAK,IAAI,UAAQ;AAAA,MACtB,OAAO,IAAI;AAAA,MACX,KAAK,IAAI,OAAO;AAAA,MAChB,MAAM,IAAI;AAAA,MACV,aAAa,IAAI;AAAA,MACjB,SAAS,IAAI,aAAa,SAAS,OAAO,IAAI,aAAa,UAAU,QAAQ;AAAA,MAC7E,UAAU,IAAI;AAAA,MACd,QAAQ,IAAI;AAAA,MACZ,SAAS,IAAI;AAAA,MACb,WAAW,IAAI,KAAK,IAAI,SAAS;AAAA,IACnC,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAMA,WAA2B;AAEzB,UAAM,iBAAiB,KAAK,GAAG,QAAQ,sCAAsC,EAAE,IAAI;AAGnF,UAAM,aAAa,KAAK,GAAG,QAAQ;AAAA;AAAA,KAElC,EAAE,IAAI;AAEP,UAAM,iBAAyC,CAAC;AAChD,eAAW,OAAO,YAAY;AAC5B,qBAAe,IAAI,MAAM,IAAI,IAAI;AAAA,IACnC;AAGA,UAAM,YAAY,KAAK,GAAG,QAAQ;AAAA;AAAA,KAEjC,EAAE,IAAI;AAEP,UAAM,gBAAwC,CAAC;AAC/C,eAAW,OAAO,WAAW;AAC3B,oBAAc,IAAI,KAAK,IAAI,IAAI;AAAA,IACjC;AAGA,UAAM,mBAAmB,KAAK,GAAG,QAAQ;AAAA;AAAA,KAExC,EAAE,IAAI;AAGP,UAAM,oBAAoB,KAAK,GAAG,QAAQ;AAAA;AAAA,KAEzC,EAAE,IAAI;AAGP,UAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,GAAI,EAAE,YAAY;AACzE,UAAM,qBAAqB,KAAK,GAAG,QAAQ;AAAA;AAAA,KAE1C,EAAE,IAAI,SAAS;AAEhB,WAAO;AAAA,MACL,aAAa,eAAe;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,eAAe,iBAAiB;AAAA,MAChC,gBAAgB,kBAAkB;AAAA,MAClC,iBAAiB,mBAAmB;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,gBAAwB,IAAY;AAC1C,UAAM,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,gBAAgB,KAAK,KAAK,KAAK,GAAI,EAAE,YAAY;AAEtF,UAAM,gBAAgB,KAAK,GAAG,QAAQ,wCAAwC,EAAE,IAAI,MAAM,EAAE;AAC5F,UAAM,mBAAmB,KAAK,GAAG,QAAQ,kDAAkD,EAAE,IAAI,MAAM,EAAE;AACzG,UAAM,gBAAgB,KAAK,GAAG,QAAQ;AAAA;AAAA,KAErC,EAAE,IAAI,MAAM,EAAE;AACf,UAAM,mBAAmB,KAAK,GAAG,QAAQ,gDAAgD,EAAE,IAAI,MAAM,EAAE;AAEvG,WAAO,gBAAgB,mBAAmB,gBAAgB;AAAA,EAC5D;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;AAEA,IAAO,aAAQ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/policy/command-filter.ts","../src/policy/path-sandbox.ts","../src/policy/secrets-guard.ts","../src/policy/rate-limiter.ts","../src/session.ts","../src/policy/engine.ts"],"sourcesContent":["import type { CommandPolicy, PolicyViolation } from '../types.js'\r\n\r\nexport class CommandFilter {\r\n private allowPatterns: RegExp[]\r\n private blockPatterns: RegExp[]\r\n\r\n constructor(private policy: CommandPolicy) {\r\n this.allowPatterns = policy.allow.map(p => this.globToRegex(p))\r\n this.blockPatterns = policy.block.map(p => this.globToRegex(p))\r\n }\r\n\r\n check(command: string): PolicyViolation | null {\r\n // Check block list first (higher priority)\r\n for (let i = 0; i < this.blockPatterns.length; i++) {\r\n if (this.blockPatterns[i].test(command)) {\r\n return {\r\n type: 'command',\r\n rule: `block[${i}]: ${this.policy.block[i]}`,\r\n message: `Command matches blocked pattern: ${this.policy.block[i]}`\r\n }\r\n }\r\n }\r\n\r\n // If allow list is empty or contains '*', allow by default\r\n if (this.policy.allow.length === 0 || this.policy.allow.includes('*')) {\r\n return null\r\n }\r\n\r\n // Check if command matches any allow pattern\r\n const allowed = this.allowPatterns.some(pattern => pattern.test(command))\r\n\r\n if (!allowed) {\r\n return {\r\n type: 'command',\r\n rule: 'allow (no match)',\r\n message: 'Command not in allowlist'\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n private globToRegex(glob: string): RegExp {\r\n // Escape special regex chars except *\r\n const escaped = glob\r\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n .replace(/\\*/g, '.*')\r\n\r\n return new RegExp(`^${escaped}$`, 'i')\r\n }\r\n}\r\n","import { resolve } from 'path'\nimport { homedir } from 'os'\nimport { realpathSync, lstatSync, existsSync } from 'fs'\nimport type { PathPolicy, PolicyViolation } from '../types.js'\n\nexport class PathSandbox {\n private allowedPaths: string[]\n private blockedPaths: string[]\n\n constructor(private policy: PathPolicy) {\n this.allowedPaths = policy.allow.map(p => this.normalizePath(p))\n this.blockedPaths = policy.block.map(p => this.normalizePath(p))\n }\n\n check(path: string): PolicyViolation | null {\n // SECURITY: Resolve symlinks to get real path\n const { realPath, isSymlink } = this.resolvePath(path)\n\n // Check for symlink attacks\n if (isSymlink) {\n const originalNormalized = this.normalizePath(path)\n // If symlink points outside of where it appears to be, block it\n if (!realPath.startsWith(originalNormalized.split('/')[0])) {\n return {\n type: 'path',\n rule: 'symlink_escape',\n message: `Symlink escape detected: ${path} -> ${realPath}`\n }\n }\n }\n\n // Check block list first (use real path)\n for (const blocked of this.blockedPaths) {\n if (realPath.startsWith(blocked) || realPath === blocked) {\n return {\n type: 'path',\n rule: `block: ${blocked}`,\n message: `Access to path is blocked: ${path}`\n }\n }\n }\n\n // If allow list contains '*', allow anything not blocked\n if (this.policy.allow.includes('*')) {\n return null\n }\n\n // Check if real path is within allowed directories\n const allowed = this.allowedPaths.some(\n allowedPath =>\n realPath.startsWith(allowedPath) || realPath === allowedPath\n )\n\n if (!allowed) {\n return {\n type: 'path',\n rule: 'allow (outside sandbox)',\n message: `Path is outside allowed directories: ${path}`\n }\n }\n\n return null\n }\n\n /**\n * SECURITY FIX: Resolve symlinks to detect escape attempts\n */\n private resolvePath(path: string): { realPath: string; isSymlink: boolean } {\n const normalizedPath = this.normalizePath(path)\n\n try {\n // Check if path exists and is a symlink\n if (existsSync(normalizedPath)) {\n const stats = lstatSync(normalizedPath)\n const isSymlink = stats.isSymbolicLink()\n\n // Get real path (follows symlinks)\n const realPath = realpathSync(normalizedPath)\n\n return { realPath, isSymlink }\n }\n } catch {\n // Path doesn't exist yet or can't be accessed\n }\n\n return { realPath: normalizedPath, isSymlink: false }\n }\n\n private normalizePath(path: string): string {\n // Expand ~ to home directory\n if (path.startsWith('~')) {\n path = path.replace('~', homedir())\n }\n\n // Handle . as current directory\n if (path === '.') {\n return process.cwd()\n }\n\n return resolve(path)\n }\n\n /**\n * Check if a path would escape the sandbox via symlink\n */\n isSymlinkEscape(path: string): boolean {\n const { realPath, isSymlink } = this.resolvePath(path)\n\n if (!isSymlink) return false\n\n // Check if real path is in blocked list\n for (const blocked of this.blockedPaths) {\n if (realPath.startsWith(blocked)) {\n return true\n }\n }\n\n // Check if real path escapes allowed directories\n if (!this.policy.allow.includes('*')) {\n const inAllowed = this.allowedPaths.some(\n allowedPath => realPath.startsWith(allowedPath)\n )\n if (!inAllowed) {\n return true\n }\n }\n\n return false\n }\n}\n","import type { SecretsPolicy, PolicyViolation } from '../types.js'\n\nexport class SecretsGuard {\n private patterns: RegExp[]\n\n constructor(private policy: SecretsPolicy) {\n this.patterns = policy.patterns.map(p => this.globToRegex(p))\n }\n\n check(command: string, paths: string[]): PolicyViolation | null {\n if (!this.policy.enabled) {\n return null\n }\n\n // Check command for secret file access\n for (const path of paths) {\n if (this.isSecretPath(path)) {\n return {\n type: 'secrets',\n rule: `pattern match: ${path}`,\n message: `Attempted access to sensitive file: ${path}`\n }\n }\n }\n\n // Check for common secret-leaking patterns in commands\n // SECURITY FIX: Enhanced patterns to catch bypass attempts\n const dangerousPatterns = [\n // Direct file access (multiple readers)\n /(cat|head|tail|less|more|bat)\\s+.*\\.env/i,\n /(cat|head|tail|less|more|bat)\\s+.*\\.pem/i,\n /(cat|head|tail|less|more|bat)\\s+.*\\.key/i,\n /(cat|head|tail|less|more|bat)\\s+.*credentials/i,\n /(cat|head|tail|less|more|bat)\\s+.*secret/i,\n /(cat|head|tail|less|more|bat)\\s+.*password/i,\n /(cat|head|tail|less|more|bat)\\s+.*token/i,\n\n // Python/Perl/Ruby file readers\n /python.*open\\s*\\(.*\\.(env|pem|key)/i,\n /python.*-c.*open/i,\n /perl.*-[pne].*\\.(env|pem|key)/i,\n /ruby.*-e.*File\\.(read|open)/i,\n\n // Environment variable exposure\n /echo\\s+\\$\\w*(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|API)/i,\n /printenv.*(KEY|SECRET|TOKEN|PASSWORD)/i,\n /env\\s*\\|\\s*grep.*(KEY|SECRET|TOKEN|PASSWORD)/i,\n\n // Curl/wget with secrets\n /curl.*-d.*\\$\\w*(KEY|SECRET|TOKEN)/i,\n /curl.*-H.*Authorization/i,\n /wget.*--header.*Authorization/i,\n\n // Base64 encoding (obfuscation attempt)\n /base64.*\\.env/i,\n /base64.*\\.pem/i,\n /base64.*\\.key/i,\n /base64\\s+-d/i, // Decoding could reveal secrets\n\n // SECURITY FIX: Command substitution bypass attempts\n /cat\\s+\\$\\(/i, // cat $(...)\n /cat\\s+`/i, // cat `...`\n /cat\\s+\\$\\{/i, // cat ${...}\n\n // SECURITY FIX: Variable indirection\n /\\w+=.*\\.env.*;\\s*cat\\s+\\$/i, // VAR=.env; cat $VAR\n /\\w+=.*secret.*;\\s*cat\\s+\\$/i,\n\n // SECURITY FIX: Glob expansion bypass\n /cat\\s+\\*env/i, // cat *env\n /cat\\s+\\.\\*env/i, // cat .*env\n /cat\\s+\\?\\?env/i, // cat ??env\n\n // SECURITY FIX: Printf/echo tricks\n /printf\\s+.*\\\\x/i, // Hex encoding\n /echo\\s+-e.*\\\\x/i, // Echo with hex\n /echo\\s+-e.*\\\\[0-7]/i, // Octal encoding\n\n // SECURITY FIX: Here-doc/here-string\n /cat\\s*<<.*\\.env/i,\n /cat\\s*<<<.*secret/i,\n\n // Process substitution\n /cat\\s+<\\(/i, // cat <(...)\n\n // History/log access\n /cat.*\\.bash_history/i,\n /cat.*\\.zsh_history/i,\n /cat.*history/i,\n\n // AWS/cloud credentials\n /cat.*\\.aws\\/credentials/i,\n /cat.*\\.aws\\/config/i,\n /cat.*\\.kube\\/config/i,\n /cat.*\\.docker\\/config/i,\n\n // SSH keys\n /cat.*id_rsa/i,\n /cat.*id_ed25519/i,\n /cat.*id_ecdsa/i,\n /cat.*known_hosts/i,\n /cat.*authorized_keys/i,\n\n // GPG\n /cat.*\\.gnupg/i,\n /gpg.*--export-secret/i,\n\n // Git credentials\n /cat.*\\.git-credentials/i,\n /cat.*\\.netrc/i,\n\n // Database files\n /cat.*\\.pgpass/i,\n /cat.*\\.my\\.cnf/i,\n ]\n\n for (const pattern of dangerousPatterns) {\n if (pattern.test(command)) {\n return {\n type: 'secrets',\n rule: 'dangerous pattern',\n message: 'Command may expose secrets'\n }\n }\n }\n\n // SECURITY FIX: Check for encoded commands\n if (this.containsEncodedSecretAccess(command)) {\n return {\n type: 'secrets',\n rule: 'encoded command',\n message: 'Command contains encoded secret access attempt'\n }\n }\n\n return null\n }\n\n /**\n * SECURITY FIX: Detect base64/hex encoded secret access\n */\n private containsEncodedSecretAccess(command: string): boolean {\n // Check for base64 encoded sensitive paths\n const sensitiveBase64 = [\n 'LmVudg==', // .env\n 'LnBlbQ==', // .pem\n 'LmtleQ==', // .key\n 'aWRfcnNh', // id_rsa\n 'Y3JlZGVudGlhbHM=', // credentials\n 'c2VjcmV0', // secret\n ]\n\n for (const encoded of sensitiveBase64) {\n if (command.includes(encoded)) {\n return true\n }\n }\n\n // Check for hex encoded paths\n const sensitiveHex = [\n '2e656e76', // .env\n '2e70656d', // .pem\n '2e6b6579', // .key\n '69645f727361', // id_rsa\n ]\n\n for (const hex of sensitiveHex) {\n if (command.toLowerCase().includes(hex)) {\n return true\n }\n }\n\n return false\n }\n\n private isSecretPath(path: string): boolean {\n const lowerPath = path.toLowerCase()\n\n return this.patterns.some(pattern => pattern.test(lowerPath))\n }\n\n private globToRegex(glob: string): RegExp {\n const escaped = glob\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n\n return new RegExp(escaped, 'i')\n }\n}\n","import type { RateLimitPolicy, PolicyViolation } from '../types.js'\r\n\r\nexport class RateLimiter {\r\n private minuteWindow: number[] = []\r\n private hourWindow: number[] = []\r\n\r\n constructor(private policy: RateLimitPolicy) {}\r\n\r\n check(): PolicyViolation | null {\r\n if (!this.policy.enabled) {\r\n return null\r\n }\r\n\r\n const now = Date.now()\r\n this.cleanup(now)\r\n\r\n // Check per-minute limit\r\n if (this.minuteWindow.length >= this.policy.maxPerMinute) {\r\n return {\r\n type: 'rate_limit',\r\n rule: `maxPerMinute: ${this.policy.maxPerMinute}`,\r\n message: `Rate limit exceeded: ${this.minuteWindow.length}/${this.policy.maxPerMinute} commands per minute`\r\n }\r\n }\r\n\r\n // Check per-hour limit\r\n if (this.hourWindow.length >= this.policy.maxPerHour) {\r\n return {\r\n type: 'rate_limit',\r\n rule: `maxPerHour: ${this.policy.maxPerHour}`,\r\n message: `Rate limit exceeded: ${this.hourWindow.length}/${this.policy.maxPerHour} commands per hour`\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n record(): void {\r\n const now = Date.now()\r\n this.minuteWindow.push(now)\r\n this.hourWindow.push(now)\r\n }\r\n\r\n private cleanup(now: number): void {\r\n const oneMinuteAgo = now - 60 * 1000\r\n const oneHourAgo = now - 60 * 60 * 1000\r\n\r\n this.minuteWindow = this.minuteWindow.filter(t => t > oneMinuteAgo)\r\n this.hourWindow = this.hourWindow.filter(t => t > oneHourAgo)\r\n }\r\n\r\n getStats(): { minute: number; hour: number } {\r\n const now = Date.now()\r\n this.cleanup(now)\r\n\r\n return {\r\n minute: this.minuteWindow.length,\r\n hour: this.hourWindow.length\r\n }\r\n }\r\n}\r\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { homedir } from 'os'\r\n\r\n/**\r\n * Session-based allowlist for temporary command permissions.\r\n * Stored in a temp file that gets cleared on restart.\r\n */\r\n\r\nconst SESSION_FILE = join(homedir(), '.bashbros', 'session-allow.json')\r\n\r\ninterface SessionData {\r\n pid: number\r\n startTime: number\r\n allowedCommands: string[]\r\n}\r\n\r\nfunction ensureDir(): void {\r\n const dir = join(homedir(), '.bashbros')\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true, mode: 0o700 })\r\n }\r\n}\r\n\r\nfunction loadSession(): SessionData | null {\r\n try {\r\n if (!existsSync(SESSION_FILE)) {\r\n return null\r\n }\r\n\r\n const data = JSON.parse(readFileSync(SESSION_FILE, 'utf-8'))\r\n\r\n // Check if session is from current process\r\n if (data.pid !== process.pid) {\r\n // Different process - check if it's stale (older than 24 hours)\r\n const age = Date.now() - data.startTime\r\n if (age > 24 * 60 * 60 * 1000) {\r\n return null\r\n }\r\n }\r\n\r\n return data\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\nfunction saveSession(data: SessionData): void {\r\n ensureDir()\r\n writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), { mode: 0o600 })\r\n}\r\n\r\nfunction getOrCreateSession(): SessionData {\r\n const existing = loadSession()\r\n\r\n if (existing) {\r\n return existing\r\n }\r\n\r\n const newSession: SessionData = {\r\n pid: process.pid,\r\n startTime: Date.now(),\r\n allowedCommands: []\r\n }\r\n\r\n saveSession(newSession)\r\n return newSession\r\n}\r\n\r\n/**\r\n * Add a command to the session allowlist\r\n */\r\nexport function allowForSession(command: string): void {\r\n const session = getOrCreateSession()\r\n\r\n if (!session.allowedCommands.includes(command)) {\r\n session.allowedCommands.push(command)\r\n saveSession(session)\r\n }\r\n}\r\n\r\n/**\r\n * Check if a command is allowed for this session\r\n */\r\nexport function isAllowedForSession(command: string): boolean {\r\n const session = loadSession()\r\n\r\n if (!session) {\r\n return false\r\n }\r\n\r\n // Check exact match\r\n if (session.allowedCommands.includes(command)) {\r\n return true\r\n }\r\n\r\n // Check pattern match (command starts with allowed pattern)\r\n for (const allowed of session.allowedCommands) {\r\n if (allowed.endsWith('*')) {\r\n const prefix = allowed.slice(0, -1)\r\n if (command.startsWith(prefix)) {\r\n return true\r\n }\r\n }\r\n }\r\n\r\n return false\r\n}\r\n\r\n/**\r\n * Get all commands allowed for this session\r\n */\r\nexport function getSessionAllowlist(): string[] {\r\n const session = loadSession()\r\n return session?.allowedCommands || []\r\n}\r\n\r\n/**\r\n * Clear the session allowlist\r\n */\r\nexport function clearSessionAllowlist(): void {\r\n const session = getOrCreateSession()\r\n session.allowedCommands = []\r\n saveSession(session)\r\n}\r\n","import type { BashBrosConfig, PolicyViolation } from '../types.js'\r\nimport { CommandFilter } from './command-filter.js'\r\nimport { PathSandbox } from './path-sandbox.js'\r\nimport { SecretsGuard } from './secrets-guard.js'\r\nimport { RateLimiter } from './rate-limiter.js'\r\nimport { isAllowedForSession } from '../session.js'\r\n\r\nexport class PolicyEngine {\r\n private commandFilter: CommandFilter\r\n private pathSandbox: PathSandbox\r\n private secretsGuard: SecretsGuard\r\n private rateLimiter: RateLimiter\r\n\r\n constructor(private config: BashBrosConfig) {\r\n this.commandFilter = new CommandFilter(config.commands)\r\n this.pathSandbox = new PathSandbox(config.paths)\r\n this.secretsGuard = new SecretsGuard(config.secrets)\r\n this.rateLimiter = new RateLimiter(config.rateLimit)\r\n }\r\n\r\n validate(command: string): PolicyViolation[] {\r\n const violations: PolicyViolation[] = []\r\n\r\n // Check rate limit first\r\n const rateViolation = this.rateLimiter.check()\r\n if (rateViolation) {\r\n violations.push(rateViolation)\r\n return violations // Early exit on rate limit\r\n }\r\n\r\n // Check session allowlist first (temporary permissions)\r\n if (isAllowedForSession(command)) {\r\n this.rateLimiter.record()\r\n return [] // Allowed for this session\r\n }\r\n\r\n // Check command against allow/block lists\r\n const commandViolation = this.commandFilter.check(command)\r\n if (commandViolation) {\r\n violations.push(commandViolation)\r\n }\r\n\r\n // Extract paths from command and check sandbox\r\n const paths = this.extractPaths(command)\r\n for (const path of paths) {\r\n const pathViolation = this.pathSandbox.check(path)\r\n if (pathViolation) {\r\n violations.push(pathViolation)\r\n }\r\n }\r\n\r\n // Check for secrets access\r\n if (this.config.secrets.enabled) {\r\n const secretsViolation = this.secretsGuard.check(command, paths)\r\n if (secretsViolation) {\r\n violations.push(secretsViolation)\r\n }\r\n }\r\n\r\n // Record for rate limiting\r\n this.rateLimiter.record()\r\n\r\n return violations\r\n }\r\n\r\n private extractPaths(command: string): string[] {\r\n const paths: string[] = []\r\n\r\n // Remove quotes for analysis but preserve content\r\n const unquoted = command.replace(/[\"']/g, ' ')\r\n\r\n // Simple path extraction - look for file-like arguments\r\n const tokens = unquoted.split(/\\s+/)\r\n\r\n for (const token of tokens) {\r\n // Skip flags and empty tokens\r\n if (token.startsWith('-') || !token) continue\r\n\r\n // Check if it looks like a path\r\n if (\r\n token.startsWith('/') ||\r\n token.startsWith('./') ||\r\n token.startsWith('../') ||\r\n token.startsWith('~/') ||\r\n token.startsWith('$HOME') ||\r\n token.startsWith('${HOME}') ||\r\n token.includes('.env') ||\r\n token.includes('.pem') ||\r\n token.includes('.key') ||\r\n token.includes('.ssh') ||\r\n token.includes('.aws') ||\r\n token.includes('.gnupg') ||\r\n token.includes('.kube') ||\r\n token.includes('credentials') ||\r\n token.includes('secret') ||\r\n token.includes('password') ||\r\n token.includes('id_rsa') ||\r\n token.includes('id_ed25519') ||\r\n // Files with extensions that might be sensitive\r\n /\\.(env|pem|key|crt|pfx|p12|jks|keystore)$/i.test(token)\r\n ) {\r\n paths.push(token)\r\n }\r\n }\r\n\r\n // Also extract paths from variable assignments\r\n const varAssignments = command.match(/\\w+=[^\\s;]+/g) || []\r\n for (const assignment of varAssignments) {\r\n const value = assignment.split('=')[1]\r\n if (value && (value.includes('/') || value.includes('.'))) {\r\n paths.push(value)\r\n }\r\n }\r\n\r\n return paths\r\n }\r\n\r\n isAllowed(command: string): boolean {\r\n return this.validate(command).length === 0\r\n }\r\n}\r\n"],"mappings":"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","names":["existsSync","homedir"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync, existsSync, statSync } from 'fs'\nimport { parse } from 'yaml'\nimport { join } from 'path'\nimport { homedir } from 'os'\nimport type {\n BashBrosConfig,\n SecurityProfile,\n RiskScoringPolicy,\n LoopDetectionPolicy,\n AnomalyDetectionPolicy,\n OutputScanningPolicy,\n UndoPolicy,\n RiskPattern,\n WardPolicy,\n DashboardPolicy\n} from './types.js'\n\nconst CONFIG_FILENAME = '.bashbros.yml'\n\n// Configuration limits for validation\nconst CONFIG_LIMITS = {\n maxPerMinute: { min: 1, max: 10000 },\n maxPerHour: { min: 1, max: 100000 },\n maxPatterns: 100,\n maxPathLength: 1000\n}\n\nexport function findConfig(): string | null {\n // Check current directory\n if (existsSync(CONFIG_FILENAME)) {\n return CONFIG_FILENAME\n }\n\n // Check home directory\n const homeConfig = join(homedir(), CONFIG_FILENAME)\n if (existsSync(homeConfig)) {\n return homeConfig\n }\n\n // Check ~/.bashbros/config.yml\n const dotConfig = join(homedir(), '.bashbros', 'config.yml')\n if (existsSync(dotConfig)) {\n return dotConfig\n }\n\n return null\n}\n\n/**\n * SECURITY: Validate config file permissions\n */\nfunction validateConfigPermissions(configPath: string): void {\n try {\n const stats = statSync(configPath)\n\n // On Unix, check if file is world-writable (security risk)\n if (process.platform !== 'win32') {\n const mode = stats.mode\n const worldWritable = (mode & 0o002) !== 0\n const groupWritable = (mode & 0o020) !== 0\n\n if (worldWritable || groupWritable) {\n console.warn(`⚠️ Warning: Config file ${configPath} has insecure permissions`)\n console.warn(' Run: chmod 600 ' + configPath)\n }\n }\n } catch {\n // Ignore permission check errors\n }\n}\n\nexport function loadConfig(path?: string): BashBrosConfig {\n const configPath = path || findConfig()\n\n if (!configPath) {\n return getDefaultConfig()\n }\n\n // SECURITY: Check file permissions\n validateConfigPermissions(configPath)\n\n const content = readFileSync(configPath, 'utf-8')\n\n // SECURITY: Use safe YAML parsing (no custom tags)\n let parsed: unknown\n try {\n parsed = parse(content, { strict: true })\n } catch (error) {\n console.error('Failed to parse config file:', error)\n return getDefaultConfig()\n }\n\n // SECURITY: Validate parsed config\n const validated = validateConfig(parsed)\n\n return mergeWithDefaults(validated)\n}\n\n/**\n * SECURITY: Validate and sanitize config values\n */\nfunction validateConfig(parsed: unknown): Partial<BashBrosConfig> {\n if (!parsed || typeof parsed !== 'object') {\n return {}\n }\n\n const config = parsed as Record<string, unknown>\n const validated: Partial<BashBrosConfig> = {}\n\n // Validate agent type\n const validAgents = ['claude-code', 'clawdbot', 'gemini-cli', 'aider', 'opencode', 'custom']\n if (typeof config.agent === 'string' && validAgents.includes(config.agent)) {\n validated.agent = config.agent as BashBrosConfig['agent']\n }\n\n // Validate profile\n const validProfiles = ['balanced', 'strict', 'permissive', 'custom']\n if (typeof config.profile === 'string' && validProfiles.includes(config.profile)) {\n validated.profile = config.profile as SecurityProfile\n }\n\n // Validate commands\n if (config.commands && typeof config.commands === 'object') {\n const cmds = config.commands as Record<string, unknown>\n validated.commands = {\n allow: validateStringArray(cmds.allow, CONFIG_LIMITS.maxPatterns),\n block: validateStringArray(cmds.block, CONFIG_LIMITS.maxPatterns)\n }\n }\n\n // Validate paths\n if (config.paths && typeof config.paths === 'object') {\n const paths = config.paths as Record<string, unknown>\n validated.paths = {\n allow: validatePathArray(paths.allow),\n block: validatePathArray(paths.block)\n }\n }\n\n // Validate secrets\n if (config.secrets && typeof config.secrets === 'object') {\n const secrets = config.secrets as Record<string, unknown>\n validated.secrets = {\n enabled: typeof secrets.enabled === 'boolean' ? secrets.enabled : true,\n mode: secrets.mode === 'audit' ? 'audit' : 'block',\n patterns: validateStringArray(secrets.patterns, CONFIG_LIMITS.maxPatterns)\n }\n }\n\n // Validate audit\n if (config.audit && typeof config.audit === 'object') {\n const audit = config.audit as Record<string, unknown>\n validated.audit = {\n enabled: typeof audit.enabled === 'boolean' ? audit.enabled : true,\n destination: validateAuditDestination(audit.destination),\n remotePath: validateRemotePath(audit.remotePath)\n }\n }\n\n // Validate rate limit\n if (config.rateLimit && typeof config.rateLimit === 'object') {\n const rl = config.rateLimit as Record<string, unknown>\n const maxPerMinute = validateNumber(rl.maxPerMinute, CONFIG_LIMITS.maxPerMinute)\n const maxPerHour = validateNumber(rl.maxPerHour, CONFIG_LIMITS.maxPerHour)\n\n // SECURITY: Ensure hour limit >= minute limit\n validated.rateLimit = {\n enabled: typeof rl.enabled === 'boolean' ? rl.enabled : true,\n maxPerMinute,\n maxPerHour: Math.max(maxPerHour, maxPerMinute)\n }\n }\n\n // Validate risk scoring\n if (config.riskScoring && typeof config.riskScoring === 'object') {\n const rs = config.riskScoring as Record<string, unknown>\n validated.riskScoring = {\n enabled: typeof rs.enabled === 'boolean' ? rs.enabled : true,\n blockThreshold: validateNumber(rs.blockThreshold, { min: 1, max: 10 }),\n warnThreshold: validateNumber(rs.warnThreshold, { min: 1, max: 10 }),\n customPatterns: validateRiskPatterns(rs.customPatterns)\n }\n }\n\n // Validate loop detection\n if (config.loopDetection && typeof config.loopDetection === 'object') {\n const ld = config.loopDetection as Record<string, unknown>\n validated.loopDetection = {\n enabled: typeof ld.enabled === 'boolean' ? ld.enabled : true,\n maxRepeats: validateNumber(ld.maxRepeats, { min: 1, max: 100 }),\n maxTurns: validateNumber(ld.maxTurns, { min: 10, max: 10000 }),\n similarityThreshold: validateNumber(ld.similarityThreshold, { min: 0, max: 1 }) / 1, // Keep as float\n cooldownMs: validateNumber(ld.cooldownMs, { min: 0, max: 60000 }),\n windowSize: validateNumber(ld.windowSize, { min: 5, max: 100 }),\n action: ld.action === 'block' ? 'block' : 'warn'\n }\n }\n\n // Validate anomaly detection\n if (config.anomalyDetection && typeof config.anomalyDetection === 'object') {\n const ad = config.anomalyDetection as Record<string, unknown>\n validated.anomalyDetection = {\n enabled: typeof ad.enabled === 'boolean' ? ad.enabled : true,\n workingHours: validateWorkingHours(ad.workingHours),\n typicalCommandsPerMinute: validateNumber(ad.typicalCommandsPerMinute, { min: 1, max: 1000 }),\n learningCommands: validateNumber(ad.learningCommands, { min: 10, max: 500 }),\n suspiciousPatterns: validateStringArray(ad.suspiciousPatterns, 50),\n action: ad.action === 'block' ? 'block' : 'warn'\n }\n }\n\n // Validate output scanning\n if (config.outputScanning && typeof config.outputScanning === 'object') {\n const os = config.outputScanning as Record<string, unknown>\n validated.outputScanning = {\n enabled: typeof os.enabled === 'boolean' ? os.enabled : true,\n scanForSecrets: typeof os.scanForSecrets === 'boolean' ? os.scanForSecrets : true,\n scanForErrors: typeof os.scanForErrors === 'boolean' ? os.scanForErrors : true,\n maxOutputLength: validateNumber(os.maxOutputLength, { min: 1000, max: 10000000 }),\n redactPatterns: validateStringArray(os.redactPatterns, 50)\n }\n }\n\n // Validate undo\n if (config.undo && typeof config.undo === 'object') {\n const undo = config.undo as Record<string, unknown>\n validated.undo = {\n enabled: typeof undo.enabled === 'boolean' ? undo.enabled : true,\n maxStackSize: validateNumber(undo.maxStackSize, { min: 10, max: 1000 }),\n maxFileSize: validateNumber(undo.maxFileSize, { min: 1024, max: 100 * 1024 * 1024 }),\n ttlMinutes: validateNumber(undo.ttlMinutes, { min: 5, max: 1440 }),\n backupPath: typeof undo.backupPath === 'string' ? undo.backupPath.slice(0, 500) : '~/.bashbros/undo'\n }\n }\n\n return validated\n}\n\nfunction validateRiskPatterns(value: unknown): RiskPattern[] {\n if (!Array.isArray(value)) return []\n\n return value\n .filter((item): item is Record<string, unknown> =>\n item && typeof item === 'object' &&\n typeof item.pattern === 'string' &&\n typeof item.score === 'number' &&\n typeof item.factor === 'string'\n )\n .slice(0, 50)\n .map(item => ({\n pattern: String(item.pattern).slice(0, 500),\n score: Math.max(1, Math.min(10, Math.floor(Number(item.score)))),\n factor: String(item.factor).slice(0, 200)\n }))\n}\n\nfunction validateWorkingHours(value: unknown): [number, number] {\n if (!Array.isArray(value) || value.length !== 2) {\n return [6, 22]\n }\n\n const start = Math.max(0, Math.min(23, Math.floor(Number(value[0]) || 0)))\n const end = Math.max(0, Math.min(24, Math.floor(Number(value[1]) || 24)))\n\n return [start, end]\n}\n\nfunction validateStringArray(value: unknown, maxItems: number): string[] {\n if (!Array.isArray(value)) return []\n\n return value\n .filter((item): item is string => typeof item === 'string')\n .slice(0, maxItems)\n .map(s => s.slice(0, 500)) // Limit string length\n}\n\nfunction validatePathArray(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n\n return value\n .filter((item): item is string => typeof item === 'string')\n .slice(0, CONFIG_LIMITS.maxPatterns)\n .map(s => s.slice(0, CONFIG_LIMITS.maxPathLength))\n .filter(s => !s.includes('\\0')) // Block null bytes\n}\n\nfunction validateNumber(value: unknown, limits: { min: number; max: number }): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return limits.min\n }\n return Math.max(limits.min, Math.min(limits.max, Math.floor(value)))\n}\n\nfunction validateAuditDestination(value: unknown): 'local' | 'remote' | 'both' {\n if (value === 'remote' || value === 'both') {\n return value\n }\n return 'local'\n}\n\n/**\n * SECURITY: Validate remote audit path (must be HTTPS)\n */\nfunction validateRemotePath(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined\n }\n\n try {\n const url = new URL(value)\n\n // SECURITY: Only allow HTTPS\n if (url.protocol !== 'https:') {\n console.warn('⚠️ Warning: Remote audit path must use HTTPS. Ignoring:', value)\n return undefined\n }\n\n // Block localhost/private IPs for remote\n const hostname = url.hostname.toLowerCase()\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('192.168.') || hostname.startsWith('10.')) {\n // Allow for testing but warn\n console.warn('⚠️ Warning: Remote audit path points to local address')\n }\n\n return value\n } catch {\n console.warn('⚠️ Warning: Invalid remote audit URL:', value)\n return undefined\n }\n}\n\nexport function getDefaultConfig(): BashBrosConfig {\n return {\n agent: 'claude-code',\n profile: 'balanced',\n commands: getDefaultCommands('balanced'),\n paths: getDefaultPaths('balanced'),\n secrets: {\n enabled: true,\n mode: 'block',\n patterns: [\n '.env*',\n '*.pem',\n '*.key',\n '*credentials*',\n '*secret*',\n '.aws/*',\n '.ssh/*'\n ]\n },\n audit: {\n enabled: true,\n destination: 'local'\n },\n rateLimit: {\n enabled: true,\n maxPerMinute: 100,\n maxPerHour: 1000\n },\n riskScoring: getDefaultRiskScoring('balanced'),\n loopDetection: getDefaultLoopDetection('balanced'),\n anomalyDetection: getDefaultAnomalyDetection('balanced'),\n outputScanning: getDefaultOutputScanning('balanced'),\n undo: getDefaultUndo(),\n ward: getDefaultWard(),\n dashboard: getDefaultDashboard()\n }\n}\n\nfunction getDefaultRiskScoring(profile: SecurityProfile): RiskScoringPolicy {\n const thresholds: Record<string, { block: number; warn: number }> = {\n strict: { block: 6, warn: 3 },\n balanced: { block: 9, warn: 6 },\n permissive: { block: 10, warn: 8 }\n }\n const t = thresholds[profile] || thresholds.balanced\n\n return {\n enabled: true,\n blockThreshold: t.block,\n warnThreshold: t.warn,\n customPatterns: []\n }\n}\n\nfunction getDefaultLoopDetection(profile: SecurityProfile): LoopDetectionPolicy {\n const settings: Record<string, { maxRepeats: number; maxTurns: number; action: 'warn' | 'block' }> = {\n strict: { maxRepeats: 2, maxTurns: 50, action: 'block' },\n balanced: { maxRepeats: 3, maxTurns: 100, action: 'warn' },\n permissive: { maxRepeats: 5, maxTurns: 200, action: 'warn' }\n }\n const s = settings[profile] || settings.balanced\n\n return {\n enabled: true,\n maxRepeats: s.maxRepeats,\n maxTurns: s.maxTurns,\n similarityThreshold: 0.85,\n cooldownMs: 1000,\n windowSize: 20,\n action: s.action\n }\n}\n\nfunction getDefaultAnomalyDetection(profile: SecurityProfile): AnomalyDetectionPolicy {\n return {\n enabled: profile !== 'permissive',\n workingHours: [6, 22],\n typicalCommandsPerMinute: 30,\n learningCommands: 50,\n suspiciousPatterns: [],\n action: profile === 'strict' ? 'block' : 'warn'\n }\n}\n\nfunction getDefaultOutputScanning(profile: SecurityProfile): OutputScanningPolicy {\n return {\n enabled: true,\n scanForSecrets: true,\n scanForErrors: true,\n maxOutputLength: 100000,\n redactPatterns: [\n 'password\\\\s*[=:]\\\\s*\\\\S+',\n 'api[_-]?key\\\\s*[=:]\\\\s*\\\\S+',\n 'secret\\\\s*[=:]\\\\s*\\\\S+',\n 'token\\\\s*[=:]\\\\s*\\\\S+',\n 'Bearer\\\\s+[A-Za-z0-9\\\\-._~+/]+=*',\n 'sk-[A-Za-z0-9]{20,}',\n 'ghp_[A-Za-z0-9]{36}',\n 'glpat-[A-Za-z0-9\\\\-]{20,}'\n ]\n }\n}\n\nfunction getDefaultUndo(): UndoPolicy {\n return {\n enabled: true,\n maxStackSize: 100,\n maxFileSize: 10 * 1024 * 1024, // 10MB\n ttlMinutes: 60, // 1 hour\n backupPath: '~/.bashbros/undo'\n }\n}\n\nfunction getDefaultWard(): WardPolicy {\n return {\n enabled: true,\n exposure: {\n scanInterval: 30000, // 30 seconds\n externalProbe: false,\n severityActions: {\n low: 'alert',\n medium: 'alert',\n high: 'block',\n critical: 'block_and_kill'\n }\n },\n connectors: {\n proxyAllMcp: false,\n telemetryRetention: '7d'\n },\n egress: {\n defaultAction: 'block'\n }\n }\n}\n\nfunction getDefaultDashboard(): DashboardPolicy {\n return {\n enabled: true,\n port: 7890,\n bind: '127.0.0.1'\n }\n}\n\nfunction getDefaultCommands(profile: SecurityProfile) {\n const dangerousCommands = [\n 'rm -rf /',\n 'rm -rf ~',\n 'rm -rf /*',\n ':(){:|:&};:',\n 'mkfs',\n 'dd if=/dev/zero',\n '> /dev/sda',\n 'chmod -R 777 /',\n 'curl * | bash',\n 'wget * | bash',\n 'curl * | sh',\n 'wget * | sh'\n ]\n\n const commonAllowed = [\n 'ls *', 'cat *', 'head *', 'tail *', 'grep *',\n 'git *', 'npm *', 'npx *', 'pnpm *', 'yarn *',\n 'node *', 'python *', 'pip *',\n 'mkdir *', 'touch *', 'cp *', 'mv *',\n 'cd *', 'pwd', 'echo *', 'which *',\n 'code *', 'vim *', 'nano *'\n ]\n\n if (profile === 'strict') {\n return { allow: [], block: dangerousCommands }\n }\n\n if (profile === 'permissive') {\n return { allow: ['*'], block: dangerousCommands }\n }\n\n // balanced\n return { allow: commonAllowed, block: dangerousCommands }\n}\n\nfunction getDefaultPaths(profile: SecurityProfile) {\n const dangerousPaths = [\n '~/.ssh',\n '~/.aws',\n '~/.gnupg',\n '~/.config/gh',\n '/etc/passwd',\n '/etc/shadow'\n ]\n\n if (profile === 'strict') {\n return { allow: ['.'], block: dangerousPaths }\n }\n\n if (profile === 'permissive') {\n return { allow: ['*'], block: dangerousPaths }\n }\n\n // balanced\n return { allow: ['.', '~'], block: dangerousPaths }\n}\n\nfunction mergeWithDefaults(parsed: Partial<BashBrosConfig>): BashBrosConfig {\n const defaults = getDefaultConfig()\n return {\n ...defaults,\n ...parsed,\n commands: { ...defaults.commands, ...parsed.commands },\n paths: { ...defaults.paths, ...parsed.paths },\n secrets: { ...defaults.secrets, ...parsed.secrets },\n audit: { ...defaults.audit, ...parsed.audit },\n rateLimit: { ...defaults.rateLimit, ...parsed.rateLimit },\n riskScoring: { ...defaults.riskScoring, ...parsed.riskScoring },\n loopDetection: { ...defaults.loopDetection, ...parsed.loopDetection },\n anomalyDetection: { ...defaults.anomalyDetection, ...parsed.anomalyDetection },\n outputScanning: { ...defaults.outputScanning, ...parsed.outputScanning },\n undo: { ...defaults.undo, ...parsed.undo },\n ward: { ...defaults.ward, ...parsed.ward },\n dashboard: { ...defaults.dashboard, ...parsed.dashboard }\n }\n}\n\nexport { BashBrosConfig }\n"],"mappings":"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","names":[]}
|
package/dist/engine-PKLXW6OF.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|