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
@@ -121,41 +121,6 @@ Respond with ONLY the command, no explanation. If unsure, respond with "none".`;
121
121
  return null;
122
122
  }
123
123
  }
124
- /**
125
- * Ask Bash Bro to explain a command
126
- */
127
- async explainCommand(command) {
128
- const systemPrompt = `You are Bash Bro, a helpful command-line assistant.
129
- Explain what the given command does in 1-2 sentences. Be concise and accurate.`;
130
- try {
131
- return await this.generate(`Explain: ${command}`, systemPrompt);
132
- } catch {
133
- return "Could not explain command.";
134
- }
135
- }
136
- /**
137
- * Ask Bash Bro to fix a command that failed
138
- */
139
- async fixCommand(command, error) {
140
- const systemPrompt = `You are Bash Bro, a helpful command-line assistant.
141
- Given a failed command and its error, suggest a fixed version.
142
- Respond with ONLY the fixed command, no explanation. If you can't fix it, respond with "none".`;
143
- try {
144
- const response = await this.generate(
145
- `Command: ${command}
146
- Error: ${error}
147
- Fixed command:`,
148
- systemPrompt
149
- );
150
- const fixed = response.trim();
151
- if (fixed.toLowerCase() === "none" || fixed.length > 500) {
152
- return null;
153
- }
154
- return fixed;
155
- } catch {
156
- return null;
157
- }
158
- }
159
124
  /**
160
125
  * Show detailed info about a model
161
126
  */
@@ -251,24 +216,6 @@ Fixed command:`,
251
216
  getModel() {
252
217
  return this.config.model;
253
218
  }
254
- /**
255
- * Generate a shell script from a natural language description
256
- */
257
- async generateScript(description, shell = "bash") {
258
- const systemPrompt = `You are Bash Bro, a helpful command-line assistant.
259
- Generate a ${shell} script based on the user's description.
260
- Output ONLY the script, no explanation. Start with the shebang line.
261
- Keep scripts simple, readable, and well-commented.`;
262
- try {
263
- const response = await this.generate(
264
- `Generate a ${shell} script that: ${description}`,
265
- systemPrompt
266
- );
267
- return response.trim();
268
- } catch {
269
- return null;
270
- }
271
- }
272
219
  /**
273
220
  * Analyze command safety and provide recommendations
274
221
  */
@@ -301,59 +248,6 @@ Respond with JSON only, in this format:
301
248
  };
302
249
  }
303
250
  }
304
- /**
305
- * Summarize a series of commands and their outputs
306
- */
307
- async summarizeSession(commands) {
308
- const systemPrompt = `You are Bash Bro, a helpful command-line assistant.
309
- Summarize what happened in this terminal session in 2-3 sentences.
310
- Focus on what was accomplished and any issues encountered.`;
311
- const sessionText = commands.map((c) => {
312
- let text = `$ ${c.command}`;
313
- if (c.output) text += `
314
- ${c.output.slice(0, 500)}`;
315
- if (c.error) text += `
316
- Error: ${c.error.slice(0, 200)}`;
317
- return text;
318
- }).join("\n\n");
319
- try {
320
- return await this.generate(sessionText, systemPrompt);
321
- } catch {
322
- return "Could not summarize session.";
323
- }
324
- }
325
- /**
326
- * Get help for a specific tool or command
327
- */
328
- async getHelp(topic) {
329
- const systemPrompt = `You are Bash Bro, a helpful command-line assistant.
330
- Provide concise help about the requested command or topic.
331
- Include common usage examples. Keep it practical and brief.`;
332
- try {
333
- return await this.generate(`Help with: ${topic}`, systemPrompt);
334
- } catch {
335
- return "Could not get help for this topic.";
336
- }
337
- }
338
- /**
339
- * Convert natural language to a command
340
- */
341
- async naturalToCommand(description) {
342
- const systemPrompt = `You are Bash Bro, a helpful command-line assistant.
343
- Convert the user's request into a single command line.
344
- Respond with ONLY the command, no explanation.
345
- If you can't convert it, respond with "none".`;
346
- try {
347
- const response = await this.generate(description, systemPrompt);
348
- const command = response.trim();
349
- if (command.toLowerCase() === "none" || command.length > 300) {
350
- return null;
351
- }
352
- return command;
353
- } catch {
354
- return null;
355
- }
356
- }
357
251
  /**
358
252
  * Generate with a temporary model override (for adapter-specific calls)
359
253
  */
@@ -371,4 +265,4 @@ If you can't convert it, respond with "none".`;
371
265
  export {
372
266
  OllamaClient
373
267
  };
374
- //# sourceMappingURL=chunk-EMLEJVJZ.js.map
268
+ //# sourceMappingURL=chunk-JOIAG54E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bro/ollama.ts"],"sourcesContent":["/**\n * Simple Ollama client for local model inference.\n * Keeps it minimal - just what we need for Bash Bro.\n */\n\nexport interface OllamaConfig {\n host: string\n model: string\n timeout: number\n}\n\nexport interface ChatMessage {\n role: 'system' | 'user' | 'assistant'\n content: string\n}\n\nexport interface GenerateResponse {\n response: string\n done: boolean\n context?: number[]\n}\n\nexport interface ModelInfo {\n modelfile: string\n parameters: string\n template: string\n details: {\n parent_model: string\n format: string\n family: string\n families: string[]\n parameter_size: string\n quantization_level: string\n }\n}\n\nexport interface RunningModel {\n name: string\n model: string\n size: number\n size_vram: number\n digest: string\n details: {\n family: string\n parameter_size: string\n quantization_level: string\n }\n expires_at: string\n}\n\nconst DEFAULT_CONFIG: OllamaConfig = {\n host: 'http://localhost:11434',\n model: 'qwen2.5-coder:7b',\n timeout: 30000\n}\n\nexport class OllamaClient {\n private config: OllamaConfig\n\n constructor(config: Partial<OllamaConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config }\n }\n\n /**\n * Check if Ollama is running and accessible\n */\n async isAvailable(): Promise<boolean> {\n try {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 5000)\n\n const response = await fetch(`${this.config.host}/api/tags`, {\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n return response.ok\n } catch {\n return false\n }\n }\n\n /**\n * List available models\n */\n async listModels(): Promise<string[]> {\n try {\n const response = await fetch(`${this.config.host}/api/tags`)\n\n if (!response.ok) {\n return []\n }\n\n const data = await response.json() as { models?: { name: string }[] }\n return data.models?.map((m) => m.name) || []\n } catch {\n return []\n }\n }\n\n /**\n * Generate a response from the model\n */\n async generate(prompt: string, systemPrompt?: string): Promise<string> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), this.config.timeout)\n\n try {\n const response = await fetch(`${this.config.host}/api/generate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n prompt,\n system: systemPrompt,\n stream: false\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n\n if (!response.ok) {\n throw new Error(`Ollama error: ${response.status}`)\n }\n\n const data = await response.json() as GenerateResponse\n return data.response\n } catch (error: any) {\n if (error.name === 'AbortError') {\n throw new Error('Ollama request timed out')\n }\n throw error\n }\n }\n\n /**\n * Chat with the model (multi-turn conversation)\n */\n async chat(messages: ChatMessage[]): Promise<string> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), this.config.timeout)\n\n try {\n const response = await fetch(`${this.config.host}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.config.model,\n messages,\n stream: false\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n\n if (!response.ok) {\n throw new Error(`Ollama error: ${response.status}`)\n }\n\n const data = await response.json() as { message?: { content: string } }\n return data.message?.content || ''\n } catch (error: any) {\n if (error.name === 'AbortError') {\n throw new Error('Ollama request timed out')\n }\n throw error\n }\n }\n\n /**\n * Ask Bash Bro to suggest a command\n */\n async suggestCommand(context: string): Promise<string | null> {\n const systemPrompt = `You are Bash Bro, a helpful command-line assistant.\nGiven the context, suggest the most likely next command the user needs.\nRespond with ONLY the command, no explanation. If unsure, respond with \"none\".`\n\n try {\n const response = await this.generate(context, systemPrompt)\n const command = response.trim()\n\n if (command.toLowerCase() === 'none' || command.length > 200) {\n return null\n }\n\n return command\n } catch {\n return null\n }\n }\n\n /**\n * Show detailed info about a model\n */\n async showModel(name: string): Promise<ModelInfo | null> {\n try {\n const response = await fetch(`${this.config.host}/api/show`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name })\n })\n\n if (!response.ok) {\n return null\n }\n\n const data = await response.json() as ModelInfo\n return data\n } catch {\n return null\n }\n }\n\n /**\n * Delete a model\n */\n async deleteModel(name: string): Promise<boolean> {\n try {\n const response = await fetch(`${this.config.host}/api/delete`, {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name })\n })\n\n return response.ok\n } catch {\n return false\n }\n }\n\n /**\n * List currently running models\n */\n async listRunning(): Promise<RunningModel[]> {\n try {\n const response = await fetch(`${this.config.host}/api/ps`)\n\n if (!response.ok) {\n return []\n }\n\n const data = await response.json() as { models?: RunningModel[] }\n return data.models || []\n } catch {\n return []\n }\n }\n\n /**\n * Pull a model from the registry\n */\n async pullModel(name: string): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 300000) // 5 minutes\n\n try {\n const response = await fetch(`${this.config.host}/api/pull`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, stream: false }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n return response.ok\n } catch {\n clearTimeout(timeout)\n return false\n }\n }\n\n /**\n * Create a model from a Modelfile\n */\n async createModel(name: string, modelfile: string): Promise<boolean> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), 120000) // 2 minutes\n\n try {\n const response = await fetch(`${this.config.host}/api/create`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, modelfile, stream: false }),\n signal: controller.signal\n })\n\n clearTimeout(timeout)\n return response.ok\n } catch {\n clearTimeout(timeout)\n return false\n }\n }\n\n setModel(model: string): void {\n this.config.model = model\n }\n\n getModel(): string {\n return this.config.model\n }\n\n /**\n * Analyze command safety and provide recommendations\n */\n async analyzeCommandSafety(command: string): Promise<{\n safe: boolean\n risk: 'low' | 'medium' | 'high' | 'critical'\n explanation: string\n suggestions: string[]\n }> {\n const systemPrompt = `You are Bash Bro, a security-focused command-line assistant.\nAnalyze the given command for security risks.\nRespond with JSON only, in this format:\n{\"safe\": boolean, \"risk\": \"low|medium|high|critical\", \"explanation\": \"...\", \"suggestions\": [\"...\"]}`\n\n try {\n const response = await this.generate(\n `Analyze this command for security risks: ${command}`,\n systemPrompt\n )\n\n // Try to parse JSON from response\n const jsonMatch = response.match(/\\{[\\s\\S]*\\}/)\n if (jsonMatch) {\n return JSON.parse(jsonMatch[0])\n }\n\n // Fallback if not parseable\n return {\n safe: true,\n risk: 'low',\n explanation: 'Could not analyze command.',\n suggestions: []\n }\n } catch {\n return {\n safe: true,\n risk: 'low',\n explanation: 'Analysis unavailable.',\n suggestions: []\n }\n }\n }\n\n /**\n * Generate with a temporary model override (for adapter-specific calls)\n */\n async generateWithAdapter(modelOverride: string, prompt: string, systemPrompt?: string): Promise<string> {\n const originalModel = this.config.model\n this.config.model = modelOverride\n try {\n return await this.generate(prompt, systemPrompt)\n } finally {\n this.config.model = originalModel\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ SecretsGuard
4
+ } from "./chunk-R5I5DEXE.js";
2
5
  import {
3
6
  isAllowedForSession
4
7
  } from "./chunk-FRMAIRQ2.js";
@@ -18,7 +21,11 @@ var CommandFilter = class {
18
21
  return {
19
22
  type: "command",
20
23
  rule: `block[${i}]: ${this.policy.block[i]}`,
21
- message: `Command matches blocked pattern: ${this.policy.block[i]}`
24
+ message: `Blocked: '${command.slice(0, 60)}' matches dangerous pattern: ${this.policy.block[i]}`,
25
+ remediation: [
26
+ `If safe, run: bashbros allow "${this.extractBase(command)} *" --once`
27
+ ],
28
+ severity: "high"
22
29
  };
23
30
  }
24
31
  }
@@ -30,11 +37,19 @@ var CommandFilter = class {
30
37
  return {
31
38
  type: "command",
32
39
  rule: "allow (no match)",
33
- message: "Command not in allowlist"
40
+ message: `Blocked: '${command.slice(0, 60)}' not in allowlist`,
41
+ remediation: [
42
+ `To allow for this session: bashbros allow "${this.extractBase(command)} *" --once`,
43
+ `To allow permanently: add "${this.extractBase(command)} *" to .bashbros.yml commands.allow`
44
+ ],
45
+ severity: "medium"
34
46
  };
35
47
  }
36
48
  return null;
37
49
  }
50
+ extractBase(command) {
51
+ return command.split(/\s+/)[0] || command;
52
+ }
38
53
  globToRegex(glob) {
39
54
  const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
40
55
  return new RegExp(`^${escaped}$`, "i");
@@ -61,7 +76,9 @@ var PathSandbox = class {
61
76
  return {
62
77
  type: "path",
63
78
  rule: "symlink_escape",
64
- message: `Symlink escape detected: ${path} -> ${realPath}`
79
+ message: `Blocked: symlink escape detected: ${path} -> ${realPath}`,
80
+ remediation: ["Use the real path directly instead of the symlink"],
81
+ severity: "critical"
65
82
  };
66
83
  }
67
84
  }
@@ -70,7 +87,9 @@ var PathSandbox = class {
70
87
  return {
71
88
  type: "path",
72
89
  rule: `block: ${blocked}`,
73
- message: `Access to path is blocked: ${path}`
90
+ message: `Blocked: ${path} is a protected path`,
91
+ remediation: [`To allow for this session: bashbros allow-path "${path}" --once`],
92
+ severity: "high"
74
93
  };
75
94
  }
76
95
  }
@@ -84,7 +103,12 @@ var PathSandbox = class {
84
103
  return {
85
104
  type: "path",
86
105
  rule: "allow (outside sandbox)",
87
- message: `Path is outside allowed directories: ${path}`
106
+ message: `Blocked: ${path} is outside allowed directories`,
107
+ remediation: [
108
+ `Allowed dirs: ${this.policy.allow.join(", ")}`,
109
+ `To allow: add the path to .bashbros.yml paths.allow`
110
+ ],
111
+ severity: "medium"
88
112
  };
89
113
  }
90
114
  return null;
@@ -137,178 +161,6 @@ var PathSandbox = class {
137
161
  }
138
162
  };
139
163
 
140
- // src/policy/secrets-guard.ts
141
- var SecretsGuard = class {
142
- constructor(policy) {
143
- this.policy = policy;
144
- this.patterns = policy.patterns.map((p) => this.globToRegex(p));
145
- }
146
- patterns;
147
- check(command, paths) {
148
- if (!this.policy.enabled) {
149
- return null;
150
- }
151
- for (const path of paths) {
152
- if (this.isSecretPath(path)) {
153
- return {
154
- type: "secrets",
155
- rule: `pattern match: ${path}`,
156
- message: `Attempted access to sensitive file: ${path}`
157
- };
158
- }
159
- }
160
- const dangerousPatterns = [
161
- // Direct file access (multiple readers)
162
- /(cat|head|tail|less|more|bat)\s+.*\.env/i,
163
- /(cat|head|tail|less|more|bat)\s+.*\.pem/i,
164
- /(cat|head|tail|less|more|bat)\s+.*\.key/i,
165
- /(cat|head|tail|less|more|bat)\s+.*credentials/i,
166
- /(cat|head|tail|less|more|bat)\s+.*secret/i,
167
- /(cat|head|tail|less|more|bat)\s+.*password/i,
168
- /(cat|head|tail|less|more|bat)\s+.*token/i,
169
- // Python/Perl/Ruby file readers
170
- /python.*open\s*\(.*\.(env|pem|key)/i,
171
- /python.*-c.*open/i,
172
- /perl.*-[pne].*\.(env|pem|key)/i,
173
- /ruby.*-e.*File\.(read|open)/i,
174
- // Environment variable exposure
175
- /echo\s+\$\w*(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|API)/i,
176
- /printenv.*(KEY|SECRET|TOKEN|PASSWORD)/i,
177
- /env\s*\|\s*grep.*(KEY|SECRET|TOKEN|PASSWORD)/i,
178
- // Curl/wget with secrets
179
- /curl.*-d.*\$\w*(KEY|SECRET|TOKEN)/i,
180
- /curl.*-H.*Authorization/i,
181
- /wget.*--header.*Authorization/i,
182
- // Base64 encoding (obfuscation attempt)
183
- /base64.*\.env/i,
184
- /base64.*\.pem/i,
185
- /base64.*\.key/i,
186
- /base64\s+-d/i,
187
- // Decoding could reveal secrets
188
- // SECURITY FIX: Command substitution bypass attempts
189
- /cat\s+\$\(/i,
190
- // cat $(...)
191
- /cat\s+`/i,
192
- // cat `...`
193
- /cat\s+\$\{/i,
194
- // cat ${...}
195
- // SECURITY FIX: Variable indirection
196
- /\w+=.*\.env.*;\s*cat\s+\$/i,
197
- // VAR=.env; cat $VAR
198
- /\w+=.*secret.*;\s*cat\s+\$/i,
199
- // SECURITY FIX: Glob expansion bypass
200
- /cat\s+\*env/i,
201
- // cat *env
202
- /cat\s+\.\*env/i,
203
- // cat .*env
204
- /cat\s+\?\?env/i,
205
- // cat ??env
206
- // SECURITY FIX: Printf/echo tricks
207
- /printf\s+.*\\x/i,
208
- // Hex encoding
209
- /echo\s+-e.*\\x/i,
210
- // Echo with hex
211
- /echo\s+-e.*\\[0-7]/i,
212
- // Octal encoding
213
- // SECURITY FIX: Here-doc/here-string
214
- /cat\s*<<.*\.env/i,
215
- /cat\s*<<<.*secret/i,
216
- // Process substitution
217
- /cat\s+<\(/i,
218
- // cat <(...)
219
- // History/log access
220
- /cat.*\.bash_history/i,
221
- /cat.*\.zsh_history/i,
222
- /cat.*history/i,
223
- // AWS/cloud credentials
224
- /cat.*\.aws\/credentials/i,
225
- /cat.*\.aws\/config/i,
226
- /cat.*\.kube\/config/i,
227
- /cat.*\.docker\/config/i,
228
- // SSH keys
229
- /cat.*id_rsa/i,
230
- /cat.*id_ed25519/i,
231
- /cat.*id_ecdsa/i,
232
- /cat.*known_hosts/i,
233
- /cat.*authorized_keys/i,
234
- // GPG
235
- /cat.*\.gnupg/i,
236
- /gpg.*--export-secret/i,
237
- // Git credentials
238
- /cat.*\.git-credentials/i,
239
- /cat.*\.netrc/i,
240
- // Database files
241
- /cat.*\.pgpass/i,
242
- /cat.*\.my\.cnf/i
243
- ];
244
- for (const pattern of dangerousPatterns) {
245
- if (pattern.test(command)) {
246
- return {
247
- type: "secrets",
248
- rule: "dangerous pattern",
249
- message: "Command may expose secrets"
250
- };
251
- }
252
- }
253
- if (this.containsEncodedSecretAccess(command)) {
254
- return {
255
- type: "secrets",
256
- rule: "encoded command",
257
- message: "Command contains encoded secret access attempt"
258
- };
259
- }
260
- return null;
261
- }
262
- /**
263
- * SECURITY FIX: Detect base64/hex encoded secret access
264
- */
265
- containsEncodedSecretAccess(command) {
266
- const sensitiveBase64 = [
267
- "LmVudg==",
268
- // .env
269
- "LnBlbQ==",
270
- // .pem
271
- "LmtleQ==",
272
- // .key
273
- "aWRfcnNh",
274
- // id_rsa
275
- "Y3JlZGVudGlhbHM=",
276
- // credentials
277
- "c2VjcmV0"
278
- // secret
279
- ];
280
- for (const encoded of sensitiveBase64) {
281
- if (command.includes(encoded)) {
282
- return true;
283
- }
284
- }
285
- const sensitiveHex = [
286
- "2e656e76",
287
- // .env
288
- "2e70656d",
289
- // .pem
290
- "2e6b6579",
291
- // .key
292
- "69645f727361"
293
- // id_rsa
294
- ];
295
- for (const hex of sensitiveHex) {
296
- if (command.toLowerCase().includes(hex)) {
297
- return true;
298
- }
299
- }
300
- return false;
301
- }
302
- isSecretPath(path) {
303
- const lowerPath = path.toLowerCase();
304
- return this.patterns.some((pattern) => pattern.test(lowerPath));
305
- }
306
- globToRegex(glob) {
307
- const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
308
- return new RegExp(escaped, "i");
309
- }
310
- };
311
-
312
164
  // src/policy/rate-limiter.ts
313
165
  var RateLimiter = class {
314
166
  constructor(policy) {
@@ -326,14 +178,24 @@ var RateLimiter = class {
326
178
  return {
327
179
  type: "rate_limit",
328
180
  rule: `maxPerMinute: ${this.policy.maxPerMinute}`,
329
- message: `Rate limit exceeded: ${this.minuteWindow.length}/${this.policy.maxPerMinute} commands per minute`
181
+ message: `Rate limited: ${this.minuteWindow.length}/${this.policy.maxPerMinute} commands per minute`,
182
+ remediation: [
183
+ "Wait a few seconds before the next command",
184
+ "Or adjust: set rateLimit.maxPerMinute in .bashbros.yml"
185
+ ],
186
+ severity: "medium"
330
187
  };
331
188
  }
332
189
  if (this.hourWindow.length >= this.policy.maxPerHour) {
333
190
  return {
334
191
  type: "rate_limit",
335
192
  rule: `maxPerHour: ${this.policy.maxPerHour}`,
336
- message: `Rate limit exceeded: ${this.hourWindow.length}/${this.policy.maxPerHour} commands per hour`
193
+ message: `Rate limited: ${this.hourWindow.length}/${this.policy.maxPerHour} commands per hour`,
194
+ remediation: [
195
+ "Command throughput limit reached for this hour",
196
+ "Or adjust: set rateLimit.maxPerHour in .bashbros.yml"
197
+ ],
198
+ severity: "medium"
337
199
  };
338
200
  }
339
201
  return null;
@@ -431,8 +293,7 @@ var PolicyEngine = class {
431
293
  export {
432
294
  CommandFilter,
433
295
  PathSandbox,
434
- SecretsGuard,
435
296
  RateLimiter,
436
297
  PolicyEngine
437
298
  };
438
- //# sourceMappingURL=chunk-QWZGB4V3.js.map
299
+ //# sourceMappingURL=chunk-PAZIDRXK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/policy/command-filter.ts","../src/policy/path-sandbox.ts","../src/policy/rate-limiter.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: `Blocked: '${command.slice(0, 60)}' matches dangerous pattern: ${this.policy.block[i]}`,\r\n remediation: [\r\n `If safe, run: bashbros allow \"${this.extractBase(command)} *\" --once`\r\n ],\r\n severity: 'high'\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: `Blocked: '${command.slice(0, 60)}' not in allowlist`,\r\n remediation: [\r\n `To allow for this session: bashbros allow \"${this.extractBase(command)} *\" --once`,\r\n `To allow permanently: add \"${this.extractBase(command)} *\" to .bashbros.yml commands.allow`\r\n ],\r\n severity: 'medium'\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n private extractBase(command: string): string {\r\n return command.split(/\\s+/)[0] || command\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: `Blocked: symlink escape detected: ${path} -> ${realPath}`,\n remediation: ['Use the real path directly instead of the symlink'],\n severity: 'critical'\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: `Blocked: ${path} is a protected path`,\n remediation: [`To allow for this session: bashbros allow-path \"${path}\" --once`],\n severity: 'high'\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: `Blocked: ${path} is outside allowed directories`,\n remediation: [\n `Allowed dirs: ${this.policy.allow.join(', ')}`,\n `To allow: add the path to .bashbros.yml paths.allow`\n ],\n severity: 'medium'\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 { 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 limited: ${this.minuteWindow.length}/${this.policy.maxPerMinute} commands per minute`,\r\n remediation: [\r\n 'Wait a few seconds before the next command',\r\n 'Or adjust: set rateLimit.maxPerMinute in .bashbros.yml'\r\n ],\r\n severity: 'medium'\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 limited: ${this.hourWindow.length}/${this.policy.maxPerHour} commands per hour`,\r\n remediation: [\r\n 'Command throughput limit reached for this hour',\r\n 'Or adjust: set rateLimit.maxPerHour in .bashbros.yml'\r\n ],\r\n severity: 'medium'\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 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":[]}