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.
Files changed (53) hide show
  1. package/README.md +45 -44
  2. package/dist/{chunk-LZYW7XQO.js → chunk-25TREQ6V.js} +131 -5
  3. package/dist/chunk-25TREQ6V.js.map +1 -0
  4. package/dist/{chunk-RTZ4QWG2.js → chunk-2CI2MRKI.js} +19 -3
  5. package/dist/chunk-2CI2MRKI.js.map +1 -0
  6. package/dist/chunk-5BBPRDWL.js +186 -0
  7. package/dist/chunk-5BBPRDWL.js.map +1 -0
  8. package/dist/{chunk-7OEWYFN3.js → chunk-6QVMBCSX.js} +7 -306
  9. package/dist/chunk-6QVMBCSX.js.map +1 -0
  10. package/dist/{chunk-RDNSS3ME.js → chunk-6SLR5WPD.js} +173 -5
  11. package/dist/chunk-6SLR5WPD.js.map +1 -0
  12. package/dist/{chunk-KYDMPE4N.js → chunk-AZVT6AZY.js} +20 -2
  13. package/dist/chunk-AZVT6AZY.js.map +1 -0
  14. package/dist/{chunk-CG6VEHJM.js → chunk-C4GZNBFF.js} +2 -2
  15. package/dist/{chunk-EMLEJVJZ.js → chunk-JOIAG54E.js} +1 -107
  16. package/dist/chunk-JOIAG54E.js.map +1 -0
  17. package/dist/{chunk-QWZGB4V3.js → chunk-PAZIDRXK.js} +42 -181
  18. package/dist/chunk-PAZIDRXK.js.map +1 -0
  19. package/dist/chunk-PLSHJHHR.js +293 -0
  20. package/dist/chunk-PLSHJHHR.js.map +1 -0
  21. package/dist/chunk-R5I5DEXE.js +228 -0
  22. package/dist/chunk-R5I5DEXE.js.map +1 -0
  23. package/dist/cli.js +157 -122
  24. package/dist/cli.js.map +1 -1
  25. package/dist/{config-I5NCK3RJ.js → config-IXBXMIUA.js} +2 -2
  26. package/dist/{db-ETWTBXAE.js → db-GJALN3R7.js} +2 -2
  27. package/dist/{display-UH7KEHOW.js → display-UDIACHTP.js} +3 -3
  28. package/dist/{engine-EGPAS2EX.js → engine-4WNPXVMS.js} +3 -2
  29. package/dist/index.d.ts +57 -57
  30. package/dist/index.js +17 -8
  31. package/dist/index.js.map +1 -1
  32. package/dist/{ollama-5JVKNFOV.js → ollama-TNMD5WHW.js} +2 -2
  33. package/dist/server-3CMTP4W4.js +13 -0
  34. package/dist/{setup-YS27MOPE.js → setup-U4R5QJMV.js} +2 -2
  35. package/dist/static/index.html +75 -28
  36. package/dist/{writer-3NAVABN6.js → writer-OMHUMJR5.js} +3 -3
  37. package/dist/writer-OMHUMJR5.js.map +1 -0
  38. package/package.json +2 -1
  39. package/dist/chunk-7OEWYFN3.js.map +0 -1
  40. package/dist/chunk-EMLEJVJZ.js.map +0 -1
  41. package/dist/chunk-KYDMPE4N.js.map +0 -1
  42. package/dist/chunk-LZYW7XQO.js.map +0 -1
  43. package/dist/chunk-QWZGB4V3.js.map +0 -1
  44. package/dist/chunk-RDNSS3ME.js.map +0 -1
  45. package/dist/chunk-RTZ4QWG2.js.map +0 -1
  46. /package/dist/{chunk-CG6VEHJM.js.map → chunk-C4GZNBFF.js.map} +0 -0
  47. /package/dist/{config-I5NCK3RJ.js.map → config-IXBXMIUA.js.map} +0 -0
  48. /package/dist/{db-ETWTBXAE.js.map → db-GJALN3R7.js.map} +0 -0
  49. /package/dist/{display-UH7KEHOW.js.map → display-UDIACHTP.js.map} +0 -0
  50. /package/dist/{engine-EGPAS2EX.js.map → engine-4WNPXVMS.js.map} +0 -0
  51. /package/dist/{ollama-5JVKNFOV.js.map → ollama-TNMD5WHW.js.map} +0 -0
  52. /package/dist/{writer-3NAVABN6.js.map → server-3CMTP4W4.js.map} +0 -0
  53. /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":[]}