bashbros 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -44
- package/dist/{chunk-LZYW7XQO.js → chunk-25TREQ6V.js} +131 -5
- package/dist/chunk-25TREQ6V.js.map +1 -0
- package/dist/{chunk-RTZ4QWG2.js → chunk-2CI2MRKI.js} +19 -3
- package/dist/chunk-2CI2MRKI.js.map +1 -0
- package/dist/chunk-5BBPRDWL.js +186 -0
- package/dist/chunk-5BBPRDWL.js.map +1 -0
- package/dist/{chunk-7OEWYFN3.js → chunk-6QVMBCSX.js} +7 -306
- package/dist/chunk-6QVMBCSX.js.map +1 -0
- package/dist/{chunk-RDNSS3ME.js → chunk-6SLR5WPD.js} +173 -5
- package/dist/chunk-6SLR5WPD.js.map +1 -0
- package/dist/{chunk-KYDMPE4N.js → chunk-AZVT6AZY.js} +20 -2
- package/dist/chunk-AZVT6AZY.js.map +1 -0
- package/dist/{chunk-CG6VEHJM.js → chunk-C4GZNBFF.js} +2 -2
- package/dist/{chunk-EMLEJVJZ.js → chunk-JOIAG54E.js} +1 -107
- package/dist/chunk-JOIAG54E.js.map +1 -0
- package/dist/{chunk-QWZGB4V3.js → chunk-PAZIDRXK.js} +42 -181
- package/dist/chunk-PAZIDRXK.js.map +1 -0
- package/dist/chunk-PLSHJHHR.js +293 -0
- package/dist/chunk-PLSHJHHR.js.map +1 -0
- package/dist/chunk-R5I5DEXE.js +228 -0
- package/dist/chunk-R5I5DEXE.js.map +1 -0
- package/dist/cli.js +157 -122
- package/dist/cli.js.map +1 -1
- package/dist/{config-I5NCK3RJ.js → config-IXBXMIUA.js} +2 -2
- package/dist/{db-ETWTBXAE.js → db-GJALN3R7.js} +2 -2
- package/dist/{display-UH7KEHOW.js → display-UDIACHTP.js} +3 -3
- package/dist/{engine-EGPAS2EX.js → engine-4WNPXVMS.js} +3 -2
- package/dist/index.d.ts +57 -57
- package/dist/index.js +17 -8
- package/dist/index.js.map +1 -1
- package/dist/{ollama-5JVKNFOV.js → ollama-TNMD5WHW.js} +2 -2
- package/dist/server-3CMTP4W4.js +13 -0
- package/dist/{setup-YS27MOPE.js → setup-U4R5QJMV.js} +2 -2
- package/dist/static/index.html +75 -28
- package/dist/{writer-3NAVABN6.js → writer-OMHUMJR5.js} +3 -3
- package/dist/writer-OMHUMJR5.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-7OEWYFN3.js.map +0 -1
- package/dist/chunk-EMLEJVJZ.js.map +0 -1
- package/dist/chunk-KYDMPE4N.js.map +0 -1
- package/dist/chunk-LZYW7XQO.js.map +0 -1
- package/dist/chunk-QWZGB4V3.js.map +0 -1
- package/dist/chunk-RDNSS3ME.js.map +0 -1
- package/dist/chunk-RTZ4QWG2.js.map +0 -1
- /package/dist/{chunk-CG6VEHJM.js.map → chunk-C4GZNBFF.js.map} +0 -0
- /package/dist/{config-I5NCK3RJ.js.map → config-IXBXMIUA.js.map} +0 -0
- /package/dist/{db-ETWTBXAE.js.map → db-GJALN3R7.js.map} +0 -0
- /package/dist/{display-UH7KEHOW.js.map → display-UDIACHTP.js.map} +0 -0
- /package/dist/{engine-EGPAS2EX.js.map → engine-4WNPXVMS.js.map} +0 -0
- /package/dist/{ollama-5JVKNFOV.js.map → ollama-TNMD5WHW.js.map} +0 -0
- /package/dist/{writer-3NAVABN6.js.map → server-3CMTP4W4.js.map} +0 -0
- /package/dist/{setup-YS27MOPE.js.map → setup-U4R5QJMV.js.map} +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CommandSuggester
|
|
4
|
+
} from "./chunk-5BBPRDWL.js";
|
|
5
|
+
import {
|
|
6
|
+
OllamaClient
|
|
7
|
+
} from "./chunk-JOIAG54E.js";
|
|
8
|
+
import {
|
|
9
|
+
SecretsGuard
|
|
10
|
+
} from "./chunk-R5I5DEXE.js";
|
|
11
|
+
import {
|
|
12
|
+
DashboardDB
|
|
13
|
+
} from "./chunk-6SLR5WPD.js";
|
|
14
|
+
|
|
15
|
+
// src/mcp/server.ts
|
|
16
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
import {
|
|
19
|
+
CallToolRequestSchema,
|
|
20
|
+
ListToolsRequestSchema
|
|
21
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
|
|
23
|
+
// src/mcp/tools.ts
|
|
24
|
+
function detectCapabilityTier(parameterSize) {
|
|
25
|
+
const match = parameterSize.match(/(\d+\.?\d*)([BM])/i);
|
|
26
|
+
if (!match) return "basic";
|
|
27
|
+
const size = parseFloat(match[1]);
|
|
28
|
+
const unit = match[2].toUpperCase();
|
|
29
|
+
const params = unit === "M" ? size / 1e3 : size;
|
|
30
|
+
if (params >= 33) return "advanced";
|
|
31
|
+
if (params >= 14) return "moderate";
|
|
32
|
+
return "basic";
|
|
33
|
+
}
|
|
34
|
+
async function sessionSummary(input, db, ollama) {
|
|
35
|
+
const count = input.count ?? 3;
|
|
36
|
+
const sessions = db.getSessions({
|
|
37
|
+
agent: input.agent,
|
|
38
|
+
limit: count
|
|
39
|
+
});
|
|
40
|
+
const results = [];
|
|
41
|
+
for (const session of sessions) {
|
|
42
|
+
const metrics = db.getSessionMetrics(session.id);
|
|
43
|
+
const durationMs = session.endTime ? session.endTime.getTime() - session.startTime.getTime() : Date.now() - session.startTime.getTime();
|
|
44
|
+
const durationMin = Math.round(durationMs / 6e4);
|
|
45
|
+
let summary = `${metrics.totalCommands} commands, ${metrics.blockedCommands} blocked, avg risk ${session.avgRiskScore.toFixed(1)}`;
|
|
46
|
+
if (ollama && await ollama.isAvailable()) {
|
|
47
|
+
const topCmds = metrics.topCommands.slice(0, 5).map((c) => c.command).join(", ");
|
|
48
|
+
const prompt = `Summarize this coding session in 1-2 sentences:
|
|
49
|
+
Agent: ${session.agent}, Repo: ${session.repoName || "unknown"}
|
|
50
|
+
Duration: ${durationMin} minutes, Commands: ${metrics.totalCommands}, Blocked: ${metrics.blockedCommands}
|
|
51
|
+
Top commands: ${topCmds}
|
|
52
|
+
Risk: avg ${session.avgRiskScore.toFixed(1)}/10`;
|
|
53
|
+
try {
|
|
54
|
+
const aiSummary = await ollama.generate(prompt, "You are a concise session summarizer. Respond in 1-2 sentences.");
|
|
55
|
+
if (aiSummary && aiSummary.length > 10) {
|
|
56
|
+
summary = aiSummary.trim();
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
results.push({
|
|
62
|
+
id: session.id,
|
|
63
|
+
agent: session.agent,
|
|
64
|
+
repoName: session.repoName,
|
|
65
|
+
workingDir: session.workingDir,
|
|
66
|
+
startTime: session.startTime.toISOString(),
|
|
67
|
+
endTime: session.endTime?.toISOString() ?? null,
|
|
68
|
+
duration: `${durationMin}m`,
|
|
69
|
+
commandCount: session.commandCount,
|
|
70
|
+
blockedCount: session.blockedCount,
|
|
71
|
+
avgRiskScore: session.avgRiskScore,
|
|
72
|
+
topCommands: metrics.topCommands.slice(0, 5),
|
|
73
|
+
summary
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return { sessions: results };
|
|
77
|
+
}
|
|
78
|
+
async function traceSearch(input, db) {
|
|
79
|
+
const limit = input.limit ?? 10;
|
|
80
|
+
let matches;
|
|
81
|
+
if (input.session_id) {
|
|
82
|
+
const sessionCommands = db.getCommands({ sessionId: input.session_id, limit: 1e3 });
|
|
83
|
+
matches = sessionCommands.filter(
|
|
84
|
+
(c) => c.command.toLowerCase().includes(input.query.toLowerCase())
|
|
85
|
+
).slice(0, limit);
|
|
86
|
+
} else {
|
|
87
|
+
matches = db.searchCommands(input.query, limit);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
matches: matches.map((m) => ({
|
|
91
|
+
command: m.command,
|
|
92
|
+
timestamp: m.timestamp.toISOString(),
|
|
93
|
+
sessionId: m.sessionId,
|
|
94
|
+
riskLevel: m.riskLevel,
|
|
95
|
+
exitInfo: m.allowed ? "allowed" : `blocked: ${m.violations.join(", ")}`,
|
|
96
|
+
allowed: m.allowed
|
|
97
|
+
})),
|
|
98
|
+
totalMatches: matches.length
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function historySuggest(input, suggester) {
|
|
102
|
+
const context = {
|
|
103
|
+
lastCommand: input.last_command,
|
|
104
|
+
lastOutput: input.last_output?.substring(0, 500),
|
|
105
|
+
cwd: input.cwd
|
|
106
|
+
};
|
|
107
|
+
const suggestions = await suggester.suggestAsync(context);
|
|
108
|
+
return {
|
|
109
|
+
suggestions: suggestions.map((s) => ({
|
|
110
|
+
command: s.command,
|
|
111
|
+
confidence: s.confidence,
|
|
112
|
+
source: s.source || "pattern",
|
|
113
|
+
reason: s.description || ""
|
|
114
|
+
}))
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function secretScan(input, guard) {
|
|
118
|
+
const result = guard.scanText(input.text);
|
|
119
|
+
return {
|
|
120
|
+
clean: result.clean,
|
|
121
|
+
findings: result.findings.map((f) => ({
|
|
122
|
+
pattern: f.pattern,
|
|
123
|
+
redacted: f.redacted,
|
|
124
|
+
line: f.line,
|
|
125
|
+
severity: f.severity
|
|
126
|
+
}))
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function codeTask(input, ollama, tier) {
|
|
130
|
+
const tierDescription = {
|
|
131
|
+
basic: "You handle simple edits: formatting, renames, adding imports, boilerplate, type annotations.",
|
|
132
|
+
moderate: "You handle moderate tasks: writing tests, adding error handling, refactoring single functions, implementing interfaces.",
|
|
133
|
+
advanced: "You handle complex tasks: multi-function refactors within a file, pattern-following generation, complex logic."
|
|
134
|
+
};
|
|
135
|
+
const systemPrompt = `You are a coding assistant. ${tierDescription[tier]}
|
|
136
|
+
Return ONLY the modified code, no explanations, no markdown fences.
|
|
137
|
+
If the language is specified, use it. Otherwise infer from the code.`;
|
|
138
|
+
const prompt = [
|
|
139
|
+
`Task: ${input.task}`,
|
|
140
|
+
input.language ? `Language: ${input.language}` : "",
|
|
141
|
+
input.context ? `Context:
|
|
142
|
+
${input.context}` : "",
|
|
143
|
+
`
|
|
144
|
+
Code:
|
|
145
|
+
${input.code}`
|
|
146
|
+
].filter(Boolean).join("\n");
|
|
147
|
+
const result = await ollama.generate(prompt, systemPrompt);
|
|
148
|
+
return {
|
|
149
|
+
result: result.trim(),
|
|
150
|
+
model_used: ollama.getModel(),
|
|
151
|
+
capability_tier: tier,
|
|
152
|
+
confidence: tier === "advanced" ? "high" : tier === "moderate" ? "medium" : "low"
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/mcp/server.ts
|
|
157
|
+
import { homedir } from "os";
|
|
158
|
+
import { join } from "path";
|
|
159
|
+
async function startMCPServer() {
|
|
160
|
+
const dbPath = join(homedir(), ".bashbros", "dashboard.db");
|
|
161
|
+
const db = new DashboardDB(dbPath);
|
|
162
|
+
const ollama = new OllamaClient();
|
|
163
|
+
const suggester = new CommandSuggester(null, ollama);
|
|
164
|
+
const guard = new SecretsGuard({ enabled: true, mode: "block", patterns: ["*.env", "*.pem", "*.key"] });
|
|
165
|
+
let tier = "basic";
|
|
166
|
+
try {
|
|
167
|
+
if (await ollama.isAvailable()) {
|
|
168
|
+
const modelInfo = await ollama.showModel(ollama.getModel());
|
|
169
|
+
if (modelInfo?.details?.parameter_size) {
|
|
170
|
+
tier = detectCapabilityTier(modelInfo.details.parameter_size);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
const server = new Server(
|
|
176
|
+
{
|
|
177
|
+
name: "bashbros",
|
|
178
|
+
version: "0.1.5"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
capabilities: {
|
|
182
|
+
tools: {}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
187
|
+
tools: [
|
|
188
|
+
{
|
|
189
|
+
name: "session_summary",
|
|
190
|
+
description: "Get structured summaries of recent coding sessions. Shows what was worked on, errors hit, and resolutions across sessions.",
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
count: { type: "number", description: "Number of recent sessions to summarize (default 3)" },
|
|
195
|
+
agent: { type: "string", description: 'Filter by agent name (e.g., "claude-code", "gemini")' }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "trace_search",
|
|
201
|
+
description: "Search past sessions for matching commands and patterns. Find how a problem was solved before or what commands were used for a task.",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
query: { type: "string", description: "Search query -- command fragment or keyword" },
|
|
206
|
+
limit: { type: "number", description: "Max results (default 10)" },
|
|
207
|
+
session_id: { type: "string", description: "Scope search to a specific session" }
|
|
208
|
+
},
|
|
209
|
+
required: ["query"]
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "history_suggest",
|
|
214
|
+
description: "Get command suggestions based on local usage patterns and project-specific workflows. Suggests what to run next based on actual history.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
last_command: { type: "string", description: "The most recent command that was run" },
|
|
219
|
+
last_output: { type: "string", description: "Output of the last command (first 500 chars)" },
|
|
220
|
+
cwd: { type: "string", description: "Current working directory" }
|
|
221
|
+
},
|
|
222
|
+
required: ["last_command"]
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "secret_scan",
|
|
227
|
+
description: "Scan text for leaked credentials, API keys, tokens, and private keys. Use before committing code or sharing output.",
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
text: { type: "string", description: "Text to scan for secrets" }
|
|
232
|
+
},
|
|
233
|
+
required: ["text"]
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "code_task",
|
|
238
|
+
description: `Perform a bounded coding task on provided code using a local model. Capability tier: ${tier}. ${tier === "basic" ? "Handles: formatting, renames, imports, boilerplate, type annotations." : tier === "moderate" ? "Handles: tests, error handling, function refactors, interface implementations." : "Handles: complex logic, multi-function refactors, pattern-following generation."}`,
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: "object",
|
|
241
|
+
properties: {
|
|
242
|
+
task: { type: "string", description: "What to do with the code" },
|
|
243
|
+
code: { type: "string", description: "The source code to work on" },
|
|
244
|
+
language: { type: "string", description: "Language hint (ts, py, etc.)" },
|
|
245
|
+
context: { type: "string", description: "Additional context (related types, imports)" }
|
|
246
|
+
},
|
|
247
|
+
required: ["task", "code"]
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
}));
|
|
252
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
253
|
+
const { name, arguments: args } = request.params;
|
|
254
|
+
try {
|
|
255
|
+
switch (name) {
|
|
256
|
+
case "session_summary": {
|
|
257
|
+
const result = await sessionSummary(args, db, ollama);
|
|
258
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
259
|
+
}
|
|
260
|
+
case "trace_search": {
|
|
261
|
+
const result = await traceSearch(args, db);
|
|
262
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
263
|
+
}
|
|
264
|
+
case "history_suggest": {
|
|
265
|
+
const result = await historySuggest(args, suggester);
|
|
266
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
267
|
+
}
|
|
268
|
+
case "secret_scan": {
|
|
269
|
+
const result = secretScan(args, guard);
|
|
270
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
271
|
+
}
|
|
272
|
+
case "code_task": {
|
|
273
|
+
if (!await ollama.isAvailable()) {
|
|
274
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Ollama not available. Start Ollama to use code_task." }) }] };
|
|
275
|
+
}
|
|
276
|
+
const result = await codeTask(args, ollama, tier);
|
|
277
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
278
|
+
}
|
|
279
|
+
default:
|
|
280
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
const transport = new StdioServerTransport();
|
|
287
|
+
await server.connect(transport);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export {
|
|
291
|
+
startMCPServer
|
|
292
|
+
};
|
|
293
|
+
//# sourceMappingURL=chunk-PLSHJHHR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp/server.ts","../src/mcp/tools.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js'\r\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\r\nimport {\r\n CallToolRequestSchema,\r\n ListToolsRequestSchema,\r\n} from '@modelcontextprotocol/sdk/types.js'\r\nimport { DashboardDB } from '../dashboard/db.js'\r\nimport { OllamaClient } from '../bro/ollama.js'\r\nimport { CommandSuggester } from '../bro/suggester.js'\r\nimport { SecretsGuard } from '../policy/secrets-guard.js'\r\nimport {\r\n sessionSummary,\r\n traceSearch,\r\n historySuggest,\r\n secretScan,\r\n codeTask,\r\n detectCapabilityTier,\r\n type CapabilityTier,\r\n} from './tools.js'\r\nimport { homedir } from 'os'\r\nimport { join } from 'path'\r\n\r\nexport async function startMCPServer(): Promise<void> {\r\n // Initialize shared resources\r\n const dbPath = join(homedir(), '.bashbros', 'dashboard.db')\r\n const db = new DashboardDB(dbPath)\r\n const ollama = new OllamaClient()\r\n const suggester = new CommandSuggester(null, ollama)\r\n const guard = new SecretsGuard({ enabled: true, mode: 'block', patterns: ['*.env', '*.pem', '*.key'] })\r\n\r\n // Detect capability tier from active model\r\n let tier: CapabilityTier = 'basic'\r\n try {\r\n if (await ollama.isAvailable()) {\r\n const modelInfo = await ollama.showModel(ollama.getModel())\r\n if (modelInfo?.details?.parameter_size) {\r\n tier = detectCapabilityTier(modelInfo.details.parameter_size)\r\n }\r\n }\r\n } catch {\r\n // Default to basic\r\n }\r\n\r\n const server = new Server(\r\n {\r\n name: 'bashbros',\r\n version: '0.1.5',\r\n },\r\n {\r\n capabilities: {\r\n tools: {},\r\n },\r\n }\r\n )\r\n\r\n // List tools\r\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\r\n tools: [\r\n {\r\n name: 'session_summary',\r\n description: 'Get structured summaries of recent coding sessions. Shows what was worked on, errors hit, and resolutions across sessions.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n count: { type: 'number', description: 'Number of recent sessions to summarize (default 3)' },\r\n agent: { type: 'string', description: 'Filter by agent name (e.g., \"claude-code\", \"gemini\")' },\r\n },\r\n },\r\n },\r\n {\r\n name: 'trace_search',\r\n description: 'Search past sessions for matching commands and patterns. Find how a problem was solved before or what commands were used for a task.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n query: { type: 'string', description: 'Search query -- command fragment or keyword' },\r\n limit: { type: 'number', description: 'Max results (default 10)' },\r\n session_id: { type: 'string', description: 'Scope search to a specific session' },\r\n },\r\n required: ['query'],\r\n },\r\n },\r\n {\r\n name: 'history_suggest',\r\n description: 'Get command suggestions based on local usage patterns and project-specific workflows. Suggests what to run next based on actual history.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n last_command: { type: 'string', description: 'The most recent command that was run' },\r\n last_output: { type: 'string', description: 'Output of the last command (first 500 chars)' },\r\n cwd: { type: 'string', description: 'Current working directory' },\r\n },\r\n required: ['last_command'],\r\n },\r\n },\r\n {\r\n name: 'secret_scan',\r\n description: 'Scan text for leaked credentials, API keys, tokens, and private keys. Use before committing code or sharing output.',\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n text: { type: 'string', description: 'Text to scan for secrets' },\r\n },\r\n required: ['text'],\r\n },\r\n },\r\n {\r\n name: 'code_task',\r\n description: `Perform a bounded coding task on provided code using a local model. Capability tier: ${tier}. ${\r\n tier === 'basic' ? 'Handles: formatting, renames, imports, boilerplate, type annotations.'\r\n : tier === 'moderate' ? 'Handles: tests, error handling, function refactors, interface implementations.'\r\n : 'Handles: complex logic, multi-function refactors, pattern-following generation.'\r\n }`,\r\n inputSchema: {\r\n type: 'object' as const,\r\n properties: {\r\n task: { type: 'string', description: 'What to do with the code' },\r\n code: { type: 'string', description: 'The source code to work on' },\r\n language: { type: 'string', description: 'Language hint (ts, py, etc.)' },\r\n context: { type: 'string', description: 'Additional context (related types, imports)' },\r\n },\r\n required: ['task', 'code'],\r\n },\r\n },\r\n ],\r\n }))\r\n\r\n // Handle tool calls\r\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\r\n const { name, arguments: args } = request.params\r\n\r\n try {\r\n switch (name) {\r\n case 'session_summary': {\r\n const result = await sessionSummary(args as any, db, ollama)\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }\r\n }\r\n case 'trace_search': {\r\n const result = await traceSearch(args as any, db)\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }\r\n }\r\n case 'history_suggest': {\r\n const result = await historySuggest(args as any, suggester)\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }\r\n }\r\n case 'secret_scan': {\r\n const result = secretScan(args as any, guard)\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }\r\n }\r\n case 'code_task': {\r\n if (!await ollama.isAvailable()) {\r\n return { content: [{ type: 'text', text: JSON.stringify({ error: 'Ollama not available. Start Ollama to use code_task.' }) }] }\r\n }\r\n const result = await codeTask(args as any, ollama, tier)\r\n return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }\r\n }\r\n default:\r\n return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }\r\n }\r\n } catch (error: any) {\r\n return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }\r\n }\r\n })\r\n\r\n // Start server on stdio\r\n const transport = new StdioServerTransport()\r\n await server.connect(transport)\r\n}\r\n","import type { DashboardDB, SessionRecord, CommandRecord } from '../dashboard/db.js'\r\nimport type { OllamaClient } from '../bro/ollama.js'\r\nimport type { CommandSuggester, SuggestionContext } from '../bro/suggester.js'\r\nimport type { SecretsGuard } from '../policy/secrets-guard.js'\r\n\r\n// ── Capability Tier ──\r\n\r\nexport type CapabilityTier = 'basic' | 'moderate' | 'advanced'\r\n\r\nexport function detectCapabilityTier(parameterSize: string): CapabilityTier {\r\n // parameterSize from Ollama /api/show e.g., \"7B\", \"14B\", \"70B\"\r\n const match = parameterSize.match(/(\\d+\\.?\\d*)([BM])/i)\r\n if (!match) return 'basic'\r\n const size = parseFloat(match[1])\r\n const unit = match[2].toUpperCase()\r\n const params = unit === 'M' ? size / 1000 : size\r\n if (params >= 33) return 'advanced'\r\n if (params >= 14) return 'moderate'\r\n return 'basic'\r\n}\r\n\r\n// ── session_summary ──\r\n\r\nexport interface SessionSummaryInput {\r\n count?: number\r\n agent?: string\r\n}\r\n\r\nexport interface SessionSummaryOutput {\r\n sessions: Array<{\r\n id: string\r\n agent: string\r\n repoName: string | null\r\n workingDir: string\r\n startTime: string\r\n endTime: string | null\r\n duration: string\r\n commandCount: number\r\n blockedCount: number\r\n avgRiskScore: number\r\n topCommands: Array<{ command: string; count: number }>\r\n summary: string\r\n }>\r\n}\r\n\r\nexport async function sessionSummary(\r\n input: SessionSummaryInput,\r\n db: DashboardDB,\r\n ollama: OllamaClient | null\r\n): Promise<SessionSummaryOutput> {\r\n const count = input.count ?? 3\r\n const sessions = db.getSessions({\r\n agent: input.agent,\r\n limit: count,\r\n })\r\n\r\n const results = []\r\n for (const session of sessions) {\r\n const metrics = db.getSessionMetrics(session.id)\r\n const durationMs = session.endTime\r\n ? session.endTime.getTime() - session.startTime.getTime()\r\n : Date.now() - session.startTime.getTime()\r\n const durationMin = Math.round(durationMs / 60000)\r\n\r\n // Generate natural language summary if Ollama available\r\n let summary = `${metrics.totalCommands} commands, ${metrics.blockedCommands} blocked, avg risk ${session.avgRiskScore.toFixed(1)}`\r\n if (ollama && await ollama.isAvailable()) {\r\n const topCmds = metrics.topCommands.slice(0, 5).map(c => c.command).join(', ')\r\n const prompt = `Summarize this coding session in 1-2 sentences:\r\nAgent: ${session.agent}, Repo: ${session.repoName || 'unknown'}\r\nDuration: ${durationMin} minutes, Commands: ${metrics.totalCommands}, Blocked: ${metrics.blockedCommands}\r\nTop commands: ${topCmds}\r\nRisk: avg ${session.avgRiskScore.toFixed(1)}/10`\r\n try {\r\n const aiSummary = await ollama.generate(prompt, 'You are a concise session summarizer. Respond in 1-2 sentences.')\r\n if (aiSummary && aiSummary.length > 10) {\r\n summary = aiSummary.trim()\r\n }\r\n } catch {\r\n // Use default summary\r\n }\r\n }\r\n\r\n results.push({\r\n id: session.id,\r\n agent: session.agent,\r\n repoName: session.repoName,\r\n workingDir: session.workingDir,\r\n startTime: session.startTime.toISOString(),\r\n endTime: session.endTime?.toISOString() ?? null,\r\n duration: `${durationMin}m`,\r\n commandCount: session.commandCount,\r\n blockedCount: session.blockedCount,\r\n avgRiskScore: session.avgRiskScore,\r\n topCommands: metrics.topCommands.slice(0, 5),\r\n summary,\r\n })\r\n }\r\n\r\n return { sessions: results }\r\n}\r\n\r\n// ── trace_search ──\r\n\r\nexport interface TraceSearchInput {\r\n query: string\r\n limit?: number\r\n session_id?: string\r\n}\r\n\r\nexport interface TraceSearchOutput {\r\n matches: Array<{\r\n command: string\r\n timestamp: string\r\n sessionId: string\r\n riskLevel: string\r\n exitInfo: string\r\n allowed: boolean\r\n }>\r\n totalMatches: number\r\n}\r\n\r\nexport async function traceSearch(\r\n input: TraceSearchInput,\r\n db: DashboardDB\r\n): Promise<TraceSearchOutput> {\r\n const limit = input.limit ?? 10\r\n\r\n // Search commands by text\r\n let matches: CommandRecord[]\r\n if (input.session_id) {\r\n const sessionCommands = db.getCommands({ sessionId: input.session_id, limit: 1000 })\r\n matches = sessionCommands.filter(c =>\r\n c.command.toLowerCase().includes(input.query.toLowerCase())\r\n ).slice(0, limit)\r\n } else {\r\n matches = db.searchCommands(input.query, limit)\r\n }\r\n\r\n return {\r\n matches: matches.map(m => ({\r\n command: m.command,\r\n timestamp: m.timestamp.toISOString(),\r\n sessionId: m.sessionId,\r\n riskLevel: m.riskLevel,\r\n exitInfo: m.allowed ? 'allowed' : `blocked: ${m.violations.join(', ')}`,\r\n allowed: m.allowed,\r\n })),\r\n totalMatches: matches.length,\r\n }\r\n}\r\n\r\n// ── history_suggest ──\r\n\r\nexport interface HistorySuggestInput {\r\n last_command: string\r\n last_output?: string\r\n cwd?: string\r\n}\r\n\r\nexport interface HistorySuggestOutput {\r\n suggestions: Array<{\r\n command: string\r\n confidence: number\r\n source: string\r\n reason: string\r\n }>\r\n}\r\n\r\nexport async function historySuggest(\r\n input: HistorySuggestInput,\r\n suggester: CommandSuggester\r\n): Promise<HistorySuggestOutput> {\r\n const context: SuggestionContext = {\r\n lastCommand: input.last_command,\r\n lastOutput: input.last_output?.substring(0, 500),\r\n cwd: input.cwd,\r\n }\r\n\r\n const suggestions = await suggester.suggestAsync(context)\r\n\r\n return {\r\n suggestions: suggestions.map(s => ({\r\n command: s.command,\r\n confidence: s.confidence,\r\n source: s.source || 'pattern',\r\n reason: s.description || '',\r\n })),\r\n }\r\n}\r\n\r\n// ── secret_scan ──\r\n\r\nexport interface SecretScanInput {\r\n text: string\r\n}\r\n\r\nexport interface SecretScanOutput {\r\n clean: boolean\r\n findings: Array<{\r\n pattern: string\r\n redacted: string\r\n line: number\r\n severity: string\r\n }>\r\n}\r\n\r\nexport function secretScan(\r\n input: SecretScanInput,\r\n guard: SecretsGuard\r\n): SecretScanOutput {\r\n const result = guard.scanText(input.text)\r\n return {\r\n clean: result.clean,\r\n findings: result.findings.map(f => ({\r\n pattern: f.pattern,\r\n redacted: f.redacted,\r\n line: f.line,\r\n severity: f.severity,\r\n })),\r\n }\r\n}\r\n\r\n// ── code_task ──\r\n\r\nexport interface CodeTaskInput {\r\n task: string\r\n code: string\r\n language?: string\r\n context?: string\r\n}\r\n\r\nexport interface CodeTaskOutput {\r\n result: string\r\n model_used: string\r\n capability_tier: CapabilityTier\r\n confidence: string\r\n}\r\n\r\nexport async function codeTask(\r\n input: CodeTaskInput,\r\n ollama: OllamaClient,\r\n tier: CapabilityTier\r\n): Promise<CodeTaskOutput> {\r\n const tierDescription = {\r\n basic: 'You handle simple edits: formatting, renames, adding imports, boilerplate, type annotations.',\r\n moderate: 'You handle moderate tasks: writing tests, adding error handling, refactoring single functions, implementing interfaces.',\r\n advanced: 'You handle complex tasks: multi-function refactors within a file, pattern-following generation, complex logic.',\r\n }\r\n\r\n const systemPrompt = `You are a coding assistant. ${tierDescription[tier]}\r\nReturn ONLY the modified code, no explanations, no markdown fences.\r\nIf the language is specified, use it. Otherwise infer from the code.`\r\n\r\n const prompt = [\r\n `Task: ${input.task}`,\r\n input.language ? `Language: ${input.language}` : '',\r\n input.context ? `Context:\\n${input.context}` : '',\r\n `\\nCode:\\n${input.code}`,\r\n ].filter(Boolean).join('\\n')\r\n\r\n const result = await ollama.generate(prompt, systemPrompt)\r\n\r\n return {\r\n result: result.trim(),\r\n model_used: ollama.getModel(),\r\n capability_tier: tier,\r\n confidence: tier === 'advanced' ? 'high' : tier === 'moderate' ? 'medium' : 'low',\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/policy/secrets-guard.ts
|
|
4
|
+
var SecretsGuard = class _SecretsGuard {
|
|
5
|
+
constructor(policy) {
|
|
6
|
+
this.policy = policy;
|
|
7
|
+
this.patterns = policy.patterns.map((p) => this.globToRegex(p));
|
|
8
|
+
}
|
|
9
|
+
patterns;
|
|
10
|
+
static TEXT_PATTERNS = [
|
|
11
|
+
{ name: "AWS Access Key", regex: /AKIA[0-9A-Z]{16}/g, severity: "critical" },
|
|
12
|
+
{ name: "AWS Secret Key", regex: /(?:aws_secret_access_key|secret_key)\s*[=:]\s*[A-Za-z0-9/+=]{40}/gi, severity: "critical" },
|
|
13
|
+
{ name: "GitHub Token", regex: /gh[ps]_[A-Za-z0-9_]{36,}/g, severity: "critical" },
|
|
14
|
+
{ name: "GitHub Fine-Grained Token", regex: /github_pat_[A-Za-z0-9_]{22,}/g, severity: "critical" },
|
|
15
|
+
{ name: "Generic API Key", regex: /(?:api_key|apikey|api-key)\s*[=:]\s*['"]?[A-Za-z0-9_\-]{20,}['"]?/gi, severity: "high" },
|
|
16
|
+
{ name: "Private Key", regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g, severity: "critical" },
|
|
17
|
+
{ name: "Generic Secret", regex: /(?:secret|password|passwd|token)\s*[=:]\s*['"]?[^\s'"]{8,}['"]?/gi, severity: "high" },
|
|
18
|
+
{ name: "Slack Token", regex: /xox[bprs]-[0-9a-zA-Z-]{10,}/g, severity: "critical" },
|
|
19
|
+
{ name: "Stripe Key", regex: /[sr]k_(?:live|test)_[A-Za-z0-9]{24,}/g, severity: "critical" }
|
|
20
|
+
];
|
|
21
|
+
scanText(text) {
|
|
22
|
+
if (!this.policy.enabled) {
|
|
23
|
+
return { clean: true, findings: [] };
|
|
24
|
+
}
|
|
25
|
+
const lines = text.split("\n");
|
|
26
|
+
const findings = [];
|
|
27
|
+
for (const patternDef of _SecretsGuard.TEXT_PATTERNS) {
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const regex = new RegExp(patternDef.regex.source, patternDef.regex.flags);
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = regex.exec(lines[i])) !== null) {
|
|
32
|
+
const matched = match[0];
|
|
33
|
+
const redacted = matched.substring(0, 4) + "***" + matched.substring(matched.length - 2);
|
|
34
|
+
findings.push({
|
|
35
|
+
pattern: patternDef.name,
|
|
36
|
+
redacted,
|
|
37
|
+
line: i + 1,
|
|
38
|
+
severity: patternDef.severity
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { clean: findings.length === 0, findings };
|
|
44
|
+
}
|
|
45
|
+
check(command, paths) {
|
|
46
|
+
if (!this.policy.enabled) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
for (const path of paths) {
|
|
50
|
+
if (this.isSecretPath(path)) {
|
|
51
|
+
return {
|
|
52
|
+
type: "secrets",
|
|
53
|
+
rule: `pattern match: ${path}`,
|
|
54
|
+
message: `Blocked: command accesses ${path} (sensitive file)`,
|
|
55
|
+
remediation: [
|
|
56
|
+
"Risk: credential or secret exposure",
|
|
57
|
+
`To allow: bashbros allow "${command.split(/\s+/)[0]} ${path}" --once`
|
|
58
|
+
],
|
|
59
|
+
severity: "critical"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const dangerousPatterns = [
|
|
64
|
+
// Direct file access (multiple readers)
|
|
65
|
+
/(cat|head|tail|less|more|bat)\s+.*\.env/i,
|
|
66
|
+
/(cat|head|tail|less|more|bat)\s+.*\.pem/i,
|
|
67
|
+
/(cat|head|tail|less|more|bat)\s+.*\.key/i,
|
|
68
|
+
/(cat|head|tail|less|more|bat)\s+.*credentials/i,
|
|
69
|
+
/(cat|head|tail|less|more|bat)\s+.*secret/i,
|
|
70
|
+
/(cat|head|tail|less|more|bat)\s+.*password/i,
|
|
71
|
+
/(cat|head|tail|less|more|bat)\s+.*token/i,
|
|
72
|
+
// Python/Perl/Ruby file readers
|
|
73
|
+
/python.*open\s*\(.*\.(env|pem|key)/i,
|
|
74
|
+
/python.*-c.*open/i,
|
|
75
|
+
/perl.*-[pne].*\.(env|pem|key)/i,
|
|
76
|
+
/ruby.*-e.*File\.(read|open)/i,
|
|
77
|
+
// Environment variable exposure
|
|
78
|
+
/echo\s+\$\w*(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|API)/i,
|
|
79
|
+
/printenv.*(KEY|SECRET|TOKEN|PASSWORD)/i,
|
|
80
|
+
/env\s*\|\s*grep.*(KEY|SECRET|TOKEN|PASSWORD)/i,
|
|
81
|
+
// Curl/wget with secrets
|
|
82
|
+
/curl.*-d.*\$\w*(KEY|SECRET|TOKEN)/i,
|
|
83
|
+
/curl.*-H.*Authorization/i,
|
|
84
|
+
/wget.*--header.*Authorization/i,
|
|
85
|
+
// Base64 encoding (obfuscation attempt)
|
|
86
|
+
/base64.*\.env/i,
|
|
87
|
+
/base64.*\.pem/i,
|
|
88
|
+
/base64.*\.key/i,
|
|
89
|
+
/base64\s+-d/i,
|
|
90
|
+
// Decoding could reveal secrets
|
|
91
|
+
// SECURITY FIX: Command substitution bypass attempts
|
|
92
|
+
/cat\s+\$\(/i,
|
|
93
|
+
// cat $(...)
|
|
94
|
+
/cat\s+`/i,
|
|
95
|
+
// cat `...`
|
|
96
|
+
/cat\s+\$\{/i,
|
|
97
|
+
// cat ${...}
|
|
98
|
+
// SECURITY FIX: Variable indirection
|
|
99
|
+
/\w+=.*\.env.*;\s*cat\s+\$/i,
|
|
100
|
+
// VAR=.env; cat $VAR
|
|
101
|
+
/\w+=.*secret.*;\s*cat\s+\$/i,
|
|
102
|
+
// SECURITY FIX: Glob expansion bypass
|
|
103
|
+
/cat\s+\*env/i,
|
|
104
|
+
// cat *env
|
|
105
|
+
/cat\s+\.\*env/i,
|
|
106
|
+
// cat .*env
|
|
107
|
+
/cat\s+\?\?env/i,
|
|
108
|
+
// cat ??env
|
|
109
|
+
// SECURITY FIX: Printf/echo tricks
|
|
110
|
+
/printf\s+.*\\x/i,
|
|
111
|
+
// Hex encoding
|
|
112
|
+
/echo\s+-e.*\\x/i,
|
|
113
|
+
// Echo with hex
|
|
114
|
+
/echo\s+-e.*\\[0-7]/i,
|
|
115
|
+
// Octal encoding
|
|
116
|
+
// SECURITY FIX: Here-doc/here-string
|
|
117
|
+
/cat\s*<<.*\.env/i,
|
|
118
|
+
/cat\s*<<<.*secret/i,
|
|
119
|
+
// Process substitution
|
|
120
|
+
/cat\s+<\(/i,
|
|
121
|
+
// cat <(...)
|
|
122
|
+
// History/log access
|
|
123
|
+
/cat.*\.bash_history/i,
|
|
124
|
+
/cat.*\.zsh_history/i,
|
|
125
|
+
/cat.*history/i,
|
|
126
|
+
// AWS/cloud credentials
|
|
127
|
+
/cat.*\.aws\/credentials/i,
|
|
128
|
+
/cat.*\.aws\/config/i,
|
|
129
|
+
/cat.*\.kube\/config/i,
|
|
130
|
+
/cat.*\.docker\/config/i,
|
|
131
|
+
// SSH keys
|
|
132
|
+
/cat.*id_rsa/i,
|
|
133
|
+
/cat.*id_ed25519/i,
|
|
134
|
+
/cat.*id_ecdsa/i,
|
|
135
|
+
/cat.*known_hosts/i,
|
|
136
|
+
/cat.*authorized_keys/i,
|
|
137
|
+
// GPG
|
|
138
|
+
/cat.*\.gnupg/i,
|
|
139
|
+
/gpg.*--export-secret/i,
|
|
140
|
+
// Git credentials
|
|
141
|
+
/cat.*\.git-credentials/i,
|
|
142
|
+
/cat.*\.netrc/i,
|
|
143
|
+
// Database files
|
|
144
|
+
/cat.*\.pgpass/i,
|
|
145
|
+
/cat.*\.my\.cnf/i
|
|
146
|
+
];
|
|
147
|
+
for (const pattern of dangerousPatterns) {
|
|
148
|
+
if (pattern.test(command)) {
|
|
149
|
+
return {
|
|
150
|
+
type: "secrets",
|
|
151
|
+
rule: "dangerous pattern",
|
|
152
|
+
message: "Blocked: command may expose secrets",
|
|
153
|
+
remediation: [
|
|
154
|
+
"Risk: credential exposure via command pattern",
|
|
155
|
+
"Review the command carefully before allowing"
|
|
156
|
+
],
|
|
157
|
+
severity: "high"
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (this.containsEncodedSecretAccess(command)) {
|
|
162
|
+
return {
|
|
163
|
+
type: "secrets",
|
|
164
|
+
rule: "encoded command",
|
|
165
|
+
message: "Blocked: command contains encoded secret access attempt",
|
|
166
|
+
remediation: [
|
|
167
|
+
"Risk: obfuscated credential access detected",
|
|
168
|
+
"This command appears to use encoding to bypass secret detection"
|
|
169
|
+
],
|
|
170
|
+
severity: "critical"
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* SECURITY FIX: Detect base64/hex encoded secret access
|
|
177
|
+
*/
|
|
178
|
+
containsEncodedSecretAccess(command) {
|
|
179
|
+
const sensitiveBase64 = [
|
|
180
|
+
"LmVudg==",
|
|
181
|
+
// .env
|
|
182
|
+
"LnBlbQ==",
|
|
183
|
+
// .pem
|
|
184
|
+
"LmtleQ==",
|
|
185
|
+
// .key
|
|
186
|
+
"aWRfcnNh",
|
|
187
|
+
// id_rsa
|
|
188
|
+
"Y3JlZGVudGlhbHM=",
|
|
189
|
+
// credentials
|
|
190
|
+
"c2VjcmV0"
|
|
191
|
+
// secret
|
|
192
|
+
];
|
|
193
|
+
for (const encoded of sensitiveBase64) {
|
|
194
|
+
if (command.includes(encoded)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const sensitiveHex = [
|
|
199
|
+
"2e656e76",
|
|
200
|
+
// .env
|
|
201
|
+
"2e70656d",
|
|
202
|
+
// .pem
|
|
203
|
+
"2e6b6579",
|
|
204
|
+
// .key
|
|
205
|
+
"69645f727361"
|
|
206
|
+
// id_rsa
|
|
207
|
+
];
|
|
208
|
+
for (const hex of sensitiveHex) {
|
|
209
|
+
if (command.toLowerCase().includes(hex)) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
isSecretPath(path) {
|
|
216
|
+
const lowerPath = path.toLowerCase();
|
|
217
|
+
return this.patterns.some((pattern) => pattern.test(lowerPath));
|
|
218
|
+
}
|
|
219
|
+
globToRegex(glob) {
|
|
220
|
+
const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
221
|
+
return new RegExp(escaped, "i");
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export {
|
|
226
|
+
SecretsGuard
|
|
227
|
+
};
|
|
228
|
+
//# sourceMappingURL=chunk-R5I5DEXE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/policy/secrets-guard.ts"],"sourcesContent":["import type { SecretsPolicy, PolicyViolation } from '../types.js'\n\nexport interface TextScanFinding {\n pattern: string\n redacted: string\n line: number\n severity: 'high' | 'critical'\n}\n\nexport interface TextScanResult {\n clean: boolean\n findings: TextScanFinding[]\n}\n\nexport class SecretsGuard {\n private patterns: RegExp[]\n\n private static readonly TEXT_PATTERNS: Array<{\n name: string\n regex: RegExp\n severity: 'high' | 'critical'\n }> = [\n { name: 'AWS Access Key', regex: /AKIA[0-9A-Z]{16}/g, severity: 'critical' },\n { name: 'AWS Secret Key', regex: /(?:aws_secret_access_key|secret_key)\\s*[=:]\\s*[A-Za-z0-9/+=]{40}/gi, severity: 'critical' },\n { name: 'GitHub Token', regex: /gh[ps]_[A-Za-z0-9_]{36,}/g, severity: 'critical' },\n { name: 'GitHub Fine-Grained Token', regex: /github_pat_[A-Za-z0-9_]{22,}/g, severity: 'critical' },\n { name: 'Generic API Key', regex: /(?:api_key|apikey|api-key)\\s*[=:]\\s*['\"]?[A-Za-z0-9_\\-]{20,}['\"]?/gi, severity: 'high' },\n { name: 'Private Key', regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g, severity: 'critical' },\n { name: 'Generic Secret', regex: /(?:secret|password|passwd|token)\\s*[=:]\\s*['\"]?[^\\s'\"]{8,}['\"]?/gi, severity: 'high' },\n { name: 'Slack Token', regex: /xox[bprs]-[0-9a-zA-Z-]{10,}/g, severity: 'critical' },\n { name: 'Stripe Key', regex: /[sr]k_(?:live|test)_[A-Za-z0-9]{24,}/g, severity: 'critical' },\n ]\n\n constructor(private policy: SecretsPolicy) {\n this.patterns = policy.patterns.map(p => this.globToRegex(p))\n }\n\n scanText(text: string): TextScanResult {\n if (!this.policy.enabled) {\n return { clean: true, findings: [] }\n }\n\n const lines = text.split('\\n')\n const findings: TextScanFinding[] = []\n\n for (const patternDef of SecretsGuard.TEXT_PATTERNS) {\n for (let i = 0; i < lines.length; i++) {\n const regex = new RegExp(patternDef.regex.source, patternDef.regex.flags)\n let match\n while ((match = regex.exec(lines[i])) !== null) {\n const matched = match[0]\n const redacted = matched.substring(0, 4) + '***' + matched.substring(matched.length - 2)\n findings.push({\n pattern: patternDef.name,\n redacted,\n line: i + 1,\n severity: patternDef.severity,\n })\n }\n }\n }\n\n return { clean: findings.length === 0, findings }\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: `Blocked: command accesses ${path} (sensitive file)`,\n remediation: [\n 'Risk: credential or secret exposure',\n `To allow: bashbros allow \"${command.split(/\\s+/)[0]} ${path}\" --once`\n ],\n severity: 'critical'\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: 'Blocked: command may expose secrets',\n remediation: [\n 'Risk: credential exposure via command pattern',\n 'Review the command carefully before allowing'\n ],\n severity: 'high'\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: 'Blocked: command contains encoded secret access attempt',\n remediation: [\n 'Risk: obfuscated credential access detected',\n 'This command appears to use encoding to bypass secret detection'\n ],\n severity: 'critical'\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"],"mappings":"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","names":[]}
|