bashbros 0.1.0
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/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/audit-MCFNGOIM.js +11 -0
- package/dist/audit-MCFNGOIM.js.map +1 -0
- package/dist/chunk-43W3RVEL.js +2910 -0
- package/dist/chunk-43W3RVEL.js.map +1 -0
- package/dist/chunk-4R4GV5V2.js +213 -0
- package/dist/chunk-4R4GV5V2.js.map +1 -0
- package/dist/chunk-7OCVIDC7.js +12 -0
- package/dist/chunk-7OCVIDC7.js.map +1 -0
- package/dist/chunk-CSRPOGHY.js +354 -0
- package/dist/chunk-CSRPOGHY.js.map +1 -0
- package/dist/chunk-DEAF6PYM.js +212 -0
- package/dist/chunk-DEAF6PYM.js.map +1 -0
- package/dist/chunk-DLP2O6PN.js +273 -0
- package/dist/chunk-DLP2O6PN.js.map +1 -0
- package/dist/chunk-GD5VNHIN.js +519 -0
- package/dist/chunk-GD5VNHIN.js.map +1 -0
- package/dist/chunk-ID2O2QTI.js +269 -0
- package/dist/chunk-ID2O2QTI.js.map +1 -0
- package/dist/chunk-J37RHCFJ.js +357 -0
- package/dist/chunk-J37RHCFJ.js.map +1 -0
- package/dist/chunk-SB4JS3GU.js +456 -0
- package/dist/chunk-SB4JS3GU.js.map +1 -0
- package/dist/chunk-SG752FZC.js +200 -0
- package/dist/chunk-SG752FZC.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2448 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-CZMIGNPF.js +13 -0
- package/dist/config-CZMIGNPF.js.map +1 -0
- package/dist/config-parser-XHE7BC7H.js +13 -0
- package/dist/config-parser-XHE7BC7H.js.map +1 -0
- package/dist/db-EHQDB5OL.js +11 -0
- package/dist/db-EHQDB5OL.js.map +1 -0
- package/dist/display-IN4NRJJS.js +18 -0
- package/dist/display-IN4NRJJS.js.map +1 -0
- package/dist/engine-PKLXW6OF.js +9 -0
- package/dist/engine-PKLXW6OF.js.map +1 -0
- package/dist/index.d.ts +1498 -0
- package/dist/index.js +552 -0
- package/dist/index.js.map +1 -0
- package/dist/moltbot-DXZFVK3X.js +11 -0
- package/dist/moltbot-DXZFVK3X.js.map +1 -0
- package/dist/ollama-HY35OHW4.js +9 -0
- package/dist/ollama-HY35OHW4.js.map +1 -0
- package/dist/risk-scorer-Y6KF2XCZ.js +9 -0
- package/dist/risk-scorer-Y6KF2XCZ.js.map +1 -0
- package/dist/static/index.html +410 -0
- package/package.json +68 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/policy/command-filter.ts
|
|
4
|
+
var CommandFilter = class {
|
|
5
|
+
constructor(policy) {
|
|
6
|
+
this.policy = policy;
|
|
7
|
+
this.allowPatterns = policy.allow.map((p) => this.globToRegex(p));
|
|
8
|
+
this.blockPatterns = policy.block.map((p) => this.globToRegex(p));
|
|
9
|
+
}
|
|
10
|
+
allowPatterns;
|
|
11
|
+
blockPatterns;
|
|
12
|
+
check(command) {
|
|
13
|
+
for (let i = 0; i < this.blockPatterns.length; i++) {
|
|
14
|
+
if (this.blockPatterns[i].test(command)) {
|
|
15
|
+
return {
|
|
16
|
+
type: "command",
|
|
17
|
+
rule: `block[${i}]: ${this.policy.block[i]}`,
|
|
18
|
+
message: `Command matches blocked pattern: ${this.policy.block[i]}`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (this.policy.allow.length === 0 || this.policy.allow.includes("*")) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const allowed = this.allowPatterns.some((pattern) => pattern.test(command));
|
|
26
|
+
if (!allowed) {
|
|
27
|
+
return {
|
|
28
|
+
type: "command",
|
|
29
|
+
rule: "allow (no match)",
|
|
30
|
+
message: "Command not in allowlist"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
globToRegex(glob) {
|
|
36
|
+
const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
37
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/policy/path-sandbox.ts
|
|
42
|
+
import { resolve } from "path";
|
|
43
|
+
import { homedir } from "os";
|
|
44
|
+
import { realpathSync, lstatSync, existsSync } from "fs";
|
|
45
|
+
var PathSandbox = class {
|
|
46
|
+
constructor(policy) {
|
|
47
|
+
this.policy = policy;
|
|
48
|
+
this.allowedPaths = policy.allow.map((p) => this.normalizePath(p));
|
|
49
|
+
this.blockedPaths = policy.block.map((p) => this.normalizePath(p));
|
|
50
|
+
}
|
|
51
|
+
allowedPaths;
|
|
52
|
+
blockedPaths;
|
|
53
|
+
check(path) {
|
|
54
|
+
const { realPath, isSymlink } = this.resolvePath(path);
|
|
55
|
+
if (isSymlink) {
|
|
56
|
+
const originalNormalized = this.normalizePath(path);
|
|
57
|
+
if (!realPath.startsWith(originalNormalized.split("/")[0])) {
|
|
58
|
+
return {
|
|
59
|
+
type: "path",
|
|
60
|
+
rule: "symlink_escape",
|
|
61
|
+
message: `Symlink escape detected: ${path} -> ${realPath}`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const blocked of this.blockedPaths) {
|
|
66
|
+
if (realPath.startsWith(blocked) || realPath === blocked) {
|
|
67
|
+
return {
|
|
68
|
+
type: "path",
|
|
69
|
+
rule: `block: ${blocked}`,
|
|
70
|
+
message: `Access to path is blocked: ${path}`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (this.policy.allow.includes("*")) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const allowed = this.allowedPaths.some(
|
|
78
|
+
(allowedPath) => realPath.startsWith(allowedPath) || realPath === allowedPath
|
|
79
|
+
);
|
|
80
|
+
if (!allowed) {
|
|
81
|
+
return {
|
|
82
|
+
type: "path",
|
|
83
|
+
rule: "allow (outside sandbox)",
|
|
84
|
+
message: `Path is outside allowed directories: ${path}`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* SECURITY FIX: Resolve symlinks to detect escape attempts
|
|
91
|
+
*/
|
|
92
|
+
resolvePath(path) {
|
|
93
|
+
const normalizedPath = this.normalizePath(path);
|
|
94
|
+
try {
|
|
95
|
+
if (existsSync(normalizedPath)) {
|
|
96
|
+
const stats = lstatSync(normalizedPath);
|
|
97
|
+
const isSymlink = stats.isSymbolicLink();
|
|
98
|
+
const realPath = realpathSync(normalizedPath);
|
|
99
|
+
return { realPath, isSymlink };
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
return { realPath: normalizedPath, isSymlink: false };
|
|
104
|
+
}
|
|
105
|
+
normalizePath(path) {
|
|
106
|
+
if (path.startsWith("~")) {
|
|
107
|
+
path = path.replace("~", homedir());
|
|
108
|
+
}
|
|
109
|
+
if (path === ".") {
|
|
110
|
+
return process.cwd();
|
|
111
|
+
}
|
|
112
|
+
return resolve(path);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if a path would escape the sandbox via symlink
|
|
116
|
+
*/
|
|
117
|
+
isSymlinkEscape(path) {
|
|
118
|
+
const { realPath, isSymlink } = this.resolvePath(path);
|
|
119
|
+
if (!isSymlink) return false;
|
|
120
|
+
for (const blocked of this.blockedPaths) {
|
|
121
|
+
if (realPath.startsWith(blocked)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!this.policy.allow.includes("*")) {
|
|
126
|
+
const inAllowed = this.allowedPaths.some(
|
|
127
|
+
(allowedPath) => realPath.startsWith(allowedPath)
|
|
128
|
+
);
|
|
129
|
+
if (!inAllowed) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/policy/secrets-guard.ts
|
|
138
|
+
var SecretsGuard = class {
|
|
139
|
+
constructor(policy) {
|
|
140
|
+
this.policy = policy;
|
|
141
|
+
this.patterns = policy.patterns.map((p) => this.globToRegex(p));
|
|
142
|
+
}
|
|
143
|
+
patterns;
|
|
144
|
+
check(command, paths) {
|
|
145
|
+
if (!this.policy.enabled) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
for (const path of paths) {
|
|
149
|
+
if (this.isSecretPath(path)) {
|
|
150
|
+
return {
|
|
151
|
+
type: "secrets",
|
|
152
|
+
rule: `pattern match: ${path}`,
|
|
153
|
+
message: `Attempted access to sensitive file: ${path}`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const dangerousPatterns = [
|
|
158
|
+
// Direct file access (multiple readers)
|
|
159
|
+
/(cat|head|tail|less|more|bat)\s+.*\.env/i,
|
|
160
|
+
/(cat|head|tail|less|more|bat)\s+.*\.pem/i,
|
|
161
|
+
/(cat|head|tail|less|more|bat)\s+.*\.key/i,
|
|
162
|
+
/(cat|head|tail|less|more|bat)\s+.*credentials/i,
|
|
163
|
+
/(cat|head|tail|less|more|bat)\s+.*secret/i,
|
|
164
|
+
/(cat|head|tail|less|more|bat)\s+.*password/i,
|
|
165
|
+
/(cat|head|tail|less|more|bat)\s+.*token/i,
|
|
166
|
+
// Python/Perl/Ruby file readers
|
|
167
|
+
/python.*open\s*\(.*\.(env|pem|key)/i,
|
|
168
|
+
/python.*-c.*open/i,
|
|
169
|
+
/perl.*-[pne].*\.(env|pem|key)/i,
|
|
170
|
+
/ruby.*-e.*File\.(read|open)/i,
|
|
171
|
+
// Environment variable exposure
|
|
172
|
+
/echo\s+\$\w*(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|API)/i,
|
|
173
|
+
/printenv.*(KEY|SECRET|TOKEN|PASSWORD)/i,
|
|
174
|
+
/env\s*\|\s*grep.*(KEY|SECRET|TOKEN|PASSWORD)/i,
|
|
175
|
+
// Curl/wget with secrets
|
|
176
|
+
/curl.*-d.*\$\w*(KEY|SECRET|TOKEN)/i,
|
|
177
|
+
/curl.*-H.*Authorization/i,
|
|
178
|
+
/wget.*--header.*Authorization/i,
|
|
179
|
+
// Base64 encoding (obfuscation attempt)
|
|
180
|
+
/base64.*\.env/i,
|
|
181
|
+
/base64.*\.pem/i,
|
|
182
|
+
/base64.*\.key/i,
|
|
183
|
+
/base64\s+-d/i,
|
|
184
|
+
// Decoding could reveal secrets
|
|
185
|
+
// SECURITY FIX: Command substitution bypass attempts
|
|
186
|
+
/cat\s+\$\(/i,
|
|
187
|
+
// cat $(...)
|
|
188
|
+
/cat\s+`/i,
|
|
189
|
+
// cat `...`
|
|
190
|
+
/cat\s+\$\{/i,
|
|
191
|
+
// cat ${...}
|
|
192
|
+
// SECURITY FIX: Variable indirection
|
|
193
|
+
/\w+=.*\.env.*;\s*cat\s+\$/i,
|
|
194
|
+
// VAR=.env; cat $VAR
|
|
195
|
+
/\w+=.*secret.*;\s*cat\s+\$/i,
|
|
196
|
+
// SECURITY FIX: Glob expansion bypass
|
|
197
|
+
/cat\s+\*env/i,
|
|
198
|
+
// cat *env
|
|
199
|
+
/cat\s+\.\*env/i,
|
|
200
|
+
// cat .*env
|
|
201
|
+
/cat\s+\?\?env/i,
|
|
202
|
+
// cat ??env
|
|
203
|
+
// SECURITY FIX: Printf/echo tricks
|
|
204
|
+
/printf\s+.*\\x/i,
|
|
205
|
+
// Hex encoding
|
|
206
|
+
/echo\s+-e.*\\x/i,
|
|
207
|
+
// Echo with hex
|
|
208
|
+
/echo\s+-e.*\\[0-7]/i,
|
|
209
|
+
// Octal encoding
|
|
210
|
+
// SECURITY FIX: Here-doc/here-string
|
|
211
|
+
/cat\s*<<.*\.env/i,
|
|
212
|
+
/cat\s*<<<.*secret/i,
|
|
213
|
+
// Process substitution
|
|
214
|
+
/cat\s+<\(/i,
|
|
215
|
+
// cat <(...)
|
|
216
|
+
// History/log access
|
|
217
|
+
/cat.*\.bash_history/i,
|
|
218
|
+
/cat.*\.zsh_history/i,
|
|
219
|
+
/cat.*history/i,
|
|
220
|
+
// AWS/cloud credentials
|
|
221
|
+
/cat.*\.aws\/credentials/i,
|
|
222
|
+
/cat.*\.aws\/config/i,
|
|
223
|
+
/cat.*\.kube\/config/i,
|
|
224
|
+
/cat.*\.docker\/config/i,
|
|
225
|
+
// SSH keys
|
|
226
|
+
/cat.*id_rsa/i,
|
|
227
|
+
/cat.*id_ed25519/i,
|
|
228
|
+
/cat.*id_ecdsa/i,
|
|
229
|
+
/cat.*known_hosts/i,
|
|
230
|
+
/cat.*authorized_keys/i,
|
|
231
|
+
// GPG
|
|
232
|
+
/cat.*\.gnupg/i,
|
|
233
|
+
/gpg.*--export-secret/i,
|
|
234
|
+
// Git credentials
|
|
235
|
+
/cat.*\.git-credentials/i,
|
|
236
|
+
/cat.*\.netrc/i,
|
|
237
|
+
// Database files
|
|
238
|
+
/cat.*\.pgpass/i,
|
|
239
|
+
/cat.*\.my\.cnf/i
|
|
240
|
+
];
|
|
241
|
+
for (const pattern of dangerousPatterns) {
|
|
242
|
+
if (pattern.test(command)) {
|
|
243
|
+
return {
|
|
244
|
+
type: "secrets",
|
|
245
|
+
rule: "dangerous pattern",
|
|
246
|
+
message: "Command may expose secrets"
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (this.containsEncodedSecretAccess(command)) {
|
|
251
|
+
return {
|
|
252
|
+
type: "secrets",
|
|
253
|
+
rule: "encoded command",
|
|
254
|
+
message: "Command contains encoded secret access attempt"
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* SECURITY FIX: Detect base64/hex encoded secret access
|
|
261
|
+
*/
|
|
262
|
+
containsEncodedSecretAccess(command) {
|
|
263
|
+
const sensitiveBase64 = [
|
|
264
|
+
"LmVudg==",
|
|
265
|
+
// .env
|
|
266
|
+
"LnBlbQ==",
|
|
267
|
+
// .pem
|
|
268
|
+
"LmtleQ==",
|
|
269
|
+
// .key
|
|
270
|
+
"aWRfcnNh",
|
|
271
|
+
// id_rsa
|
|
272
|
+
"Y3JlZGVudGlhbHM=",
|
|
273
|
+
// credentials
|
|
274
|
+
"c2VjcmV0"
|
|
275
|
+
// secret
|
|
276
|
+
];
|
|
277
|
+
for (const encoded of sensitiveBase64) {
|
|
278
|
+
if (command.includes(encoded)) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const sensitiveHex = [
|
|
283
|
+
"2e656e76",
|
|
284
|
+
// .env
|
|
285
|
+
"2e70656d",
|
|
286
|
+
// .pem
|
|
287
|
+
"2e6b6579",
|
|
288
|
+
// .key
|
|
289
|
+
"69645f727361"
|
|
290
|
+
// id_rsa
|
|
291
|
+
];
|
|
292
|
+
for (const hex of sensitiveHex) {
|
|
293
|
+
if (command.toLowerCase().includes(hex)) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
isSecretPath(path) {
|
|
300
|
+
const lowerPath = path.toLowerCase();
|
|
301
|
+
return this.patterns.some((pattern) => pattern.test(lowerPath));
|
|
302
|
+
}
|
|
303
|
+
globToRegex(glob) {
|
|
304
|
+
const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
305
|
+
return new RegExp(escaped, "i");
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// src/policy/rate-limiter.ts
|
|
310
|
+
var RateLimiter = class {
|
|
311
|
+
constructor(policy) {
|
|
312
|
+
this.policy = policy;
|
|
313
|
+
}
|
|
314
|
+
minuteWindow = [];
|
|
315
|
+
hourWindow = [];
|
|
316
|
+
check() {
|
|
317
|
+
if (!this.policy.enabled) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const now = Date.now();
|
|
321
|
+
this.cleanup(now);
|
|
322
|
+
if (this.minuteWindow.length >= this.policy.maxPerMinute) {
|
|
323
|
+
return {
|
|
324
|
+
type: "rate_limit",
|
|
325
|
+
rule: `maxPerMinute: ${this.policy.maxPerMinute}`,
|
|
326
|
+
message: `Rate limit exceeded: ${this.minuteWindow.length}/${this.policy.maxPerMinute} commands per minute`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (this.hourWindow.length >= this.policy.maxPerHour) {
|
|
330
|
+
return {
|
|
331
|
+
type: "rate_limit",
|
|
332
|
+
rule: `maxPerHour: ${this.policy.maxPerHour}`,
|
|
333
|
+
message: `Rate limit exceeded: ${this.hourWindow.length}/${this.policy.maxPerHour} commands per hour`
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
record() {
|
|
339
|
+
const now = Date.now();
|
|
340
|
+
this.minuteWindow.push(now);
|
|
341
|
+
this.hourWindow.push(now);
|
|
342
|
+
}
|
|
343
|
+
cleanup(now) {
|
|
344
|
+
const oneMinuteAgo = now - 60 * 1e3;
|
|
345
|
+
const oneHourAgo = now - 60 * 60 * 1e3;
|
|
346
|
+
this.minuteWindow = this.minuteWindow.filter((t) => t > oneMinuteAgo);
|
|
347
|
+
this.hourWindow = this.hourWindow.filter((t) => t > oneHourAgo);
|
|
348
|
+
}
|
|
349
|
+
getStats() {
|
|
350
|
+
const now = Date.now();
|
|
351
|
+
this.cleanup(now);
|
|
352
|
+
return {
|
|
353
|
+
minute: this.minuteWindow.length,
|
|
354
|
+
hour: this.hourWindow.length
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// src/session.ts
|
|
360
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
361
|
+
import { join } from "path";
|
|
362
|
+
import { homedir as homedir2 } from "os";
|
|
363
|
+
var SESSION_FILE = join(homedir2(), ".bashbros", "session-allow.json");
|
|
364
|
+
function ensureDir() {
|
|
365
|
+
const dir = join(homedir2(), ".bashbros");
|
|
366
|
+
if (!existsSync2(dir)) {
|
|
367
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function loadSession() {
|
|
371
|
+
try {
|
|
372
|
+
if (!existsSync2(SESSION_FILE)) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
const data = JSON.parse(readFileSync(SESSION_FILE, "utf-8"));
|
|
376
|
+
if (data.pid !== process.pid) {
|
|
377
|
+
const age = Date.now() - data.startTime;
|
|
378
|
+
if (age > 24 * 60 * 60 * 1e3) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return data;
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function saveSession(data) {
|
|
388
|
+
ensureDir();
|
|
389
|
+
writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), { mode: 384 });
|
|
390
|
+
}
|
|
391
|
+
function getOrCreateSession() {
|
|
392
|
+
const existing = loadSession();
|
|
393
|
+
if (existing) {
|
|
394
|
+
return existing;
|
|
395
|
+
}
|
|
396
|
+
const newSession = {
|
|
397
|
+
pid: process.pid,
|
|
398
|
+
startTime: Date.now(),
|
|
399
|
+
allowedCommands: []
|
|
400
|
+
};
|
|
401
|
+
saveSession(newSession);
|
|
402
|
+
return newSession;
|
|
403
|
+
}
|
|
404
|
+
function allowForSession(command) {
|
|
405
|
+
const session = getOrCreateSession();
|
|
406
|
+
if (!session.allowedCommands.includes(command)) {
|
|
407
|
+
session.allowedCommands.push(command);
|
|
408
|
+
saveSession(session);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function isAllowedForSession(command) {
|
|
412
|
+
const session = loadSession();
|
|
413
|
+
if (!session) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
if (session.allowedCommands.includes(command)) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
for (const allowed of session.allowedCommands) {
|
|
420
|
+
if (allowed.endsWith("*")) {
|
|
421
|
+
const prefix = allowed.slice(0, -1);
|
|
422
|
+
if (command.startsWith(prefix)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
function getSessionAllowlist() {
|
|
430
|
+
const session = loadSession();
|
|
431
|
+
return session?.allowedCommands || [];
|
|
432
|
+
}
|
|
433
|
+
function clearSessionAllowlist() {
|
|
434
|
+
const session = getOrCreateSession();
|
|
435
|
+
session.allowedCommands = [];
|
|
436
|
+
saveSession(session);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/policy/engine.ts
|
|
440
|
+
var PolicyEngine = class {
|
|
441
|
+
constructor(config) {
|
|
442
|
+
this.config = config;
|
|
443
|
+
this.commandFilter = new CommandFilter(config.commands);
|
|
444
|
+
this.pathSandbox = new PathSandbox(config.paths);
|
|
445
|
+
this.secretsGuard = new SecretsGuard(config.secrets);
|
|
446
|
+
this.rateLimiter = new RateLimiter(config.rateLimit);
|
|
447
|
+
}
|
|
448
|
+
commandFilter;
|
|
449
|
+
pathSandbox;
|
|
450
|
+
secretsGuard;
|
|
451
|
+
rateLimiter;
|
|
452
|
+
validate(command) {
|
|
453
|
+
const violations = [];
|
|
454
|
+
const rateViolation = this.rateLimiter.check();
|
|
455
|
+
if (rateViolation) {
|
|
456
|
+
violations.push(rateViolation);
|
|
457
|
+
return violations;
|
|
458
|
+
}
|
|
459
|
+
if (isAllowedForSession(command)) {
|
|
460
|
+
this.rateLimiter.record();
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
const commandViolation = this.commandFilter.check(command);
|
|
464
|
+
if (commandViolation) {
|
|
465
|
+
violations.push(commandViolation);
|
|
466
|
+
}
|
|
467
|
+
const paths = this.extractPaths(command);
|
|
468
|
+
for (const path of paths) {
|
|
469
|
+
const pathViolation = this.pathSandbox.check(path);
|
|
470
|
+
if (pathViolation) {
|
|
471
|
+
violations.push(pathViolation);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (this.config.secrets.enabled) {
|
|
475
|
+
const secretsViolation = this.secretsGuard.check(command, paths);
|
|
476
|
+
if (secretsViolation) {
|
|
477
|
+
violations.push(secretsViolation);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
this.rateLimiter.record();
|
|
481
|
+
return violations;
|
|
482
|
+
}
|
|
483
|
+
extractPaths(command) {
|
|
484
|
+
const paths = [];
|
|
485
|
+
const unquoted = command.replace(/["']/g, " ");
|
|
486
|
+
const tokens = unquoted.split(/\s+/);
|
|
487
|
+
for (const token of tokens) {
|
|
488
|
+
if (token.startsWith("-") || !token) continue;
|
|
489
|
+
if (token.startsWith("/") || token.startsWith("./") || token.startsWith("../") || token.startsWith("~/") || token.startsWith("$HOME") || token.startsWith("${HOME}") || token.includes(".env") || token.includes(".pem") || token.includes(".key") || token.includes(".ssh") || token.includes(".aws") || token.includes(".gnupg") || token.includes(".kube") || token.includes("credentials") || token.includes("secret") || token.includes("password") || token.includes("id_rsa") || token.includes("id_ed25519") || // Files with extensions that might be sensitive
|
|
490
|
+
/\.(env|pem|key|crt|pfx|p12|jks|keystore)$/i.test(token)) {
|
|
491
|
+
paths.push(token);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const varAssignments = command.match(/\w+=[^\s;]+/g) || [];
|
|
495
|
+
for (const assignment of varAssignments) {
|
|
496
|
+
const value = assignment.split("=")[1];
|
|
497
|
+
if (value && (value.includes("/") || value.includes("."))) {
|
|
498
|
+
paths.push(value);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return paths;
|
|
502
|
+
}
|
|
503
|
+
isAllowed(command) {
|
|
504
|
+
return this.validate(command).length === 0;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
export {
|
|
509
|
+
CommandFilter,
|
|
510
|
+
PathSandbox,
|
|
511
|
+
SecretsGuard,
|
|
512
|
+
RateLimiter,
|
|
513
|
+
allowForSession,
|
|
514
|
+
isAllowedForSession,
|
|
515
|
+
getSessionAllowlist,
|
|
516
|
+
clearSessionAllowlist,
|
|
517
|
+
PolicyEngine
|
|
518
|
+
};
|
|
519
|
+
//# sourceMappingURL=chunk-GD5VNHIN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/policy/command-filter.ts","../src/policy/path-sandbox.ts","../src/policy/secrets-guard.ts","../src/policy/rate-limiter.ts","../src/session.ts","../src/policy/engine.ts"],"sourcesContent":["import type { CommandPolicy, PolicyViolation } from '../types.js'\r\n\r\nexport class CommandFilter {\r\n private allowPatterns: RegExp[]\r\n private blockPatterns: RegExp[]\r\n\r\n constructor(private policy: CommandPolicy) {\r\n this.allowPatterns = policy.allow.map(p => this.globToRegex(p))\r\n this.blockPatterns = policy.block.map(p => this.globToRegex(p))\r\n }\r\n\r\n check(command: string): PolicyViolation | null {\r\n // Check block list first (higher priority)\r\n for (let i = 0; i < this.blockPatterns.length; i++) {\r\n if (this.blockPatterns[i].test(command)) {\r\n return {\r\n type: 'command',\r\n rule: `block[${i}]: ${this.policy.block[i]}`,\r\n message: `Command matches blocked pattern: ${this.policy.block[i]}`\r\n }\r\n }\r\n }\r\n\r\n // If allow list is empty or contains '*', allow by default\r\n if (this.policy.allow.length === 0 || this.policy.allow.includes('*')) {\r\n return null\r\n }\r\n\r\n // Check if command matches any allow pattern\r\n const allowed = this.allowPatterns.some(pattern => pattern.test(command))\r\n\r\n if (!allowed) {\r\n return {\r\n type: 'command',\r\n rule: 'allow (no match)',\r\n message: 'Command not in allowlist'\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n private globToRegex(glob: string): RegExp {\r\n // Escape special regex chars except *\r\n const escaped = glob\r\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n .replace(/\\*/g, '.*')\r\n\r\n return new RegExp(`^${escaped}$`, 'i')\r\n }\r\n}\r\n","import { resolve } from 'path'\nimport { homedir } from 'os'\nimport { realpathSync, lstatSync, existsSync } from 'fs'\nimport type { PathPolicy, PolicyViolation } from '../types.js'\n\nexport class PathSandbox {\n private allowedPaths: string[]\n private blockedPaths: string[]\n\n constructor(private policy: PathPolicy) {\n this.allowedPaths = policy.allow.map(p => this.normalizePath(p))\n this.blockedPaths = policy.block.map(p => this.normalizePath(p))\n }\n\n check(path: string): PolicyViolation | null {\n // SECURITY: Resolve symlinks to get real path\n const { realPath, isSymlink } = this.resolvePath(path)\n\n // Check for symlink attacks\n if (isSymlink) {\n const originalNormalized = this.normalizePath(path)\n // If symlink points outside of where it appears to be, block it\n if (!realPath.startsWith(originalNormalized.split('/')[0])) {\n return {\n type: 'path',\n rule: 'symlink_escape',\n message: `Symlink escape detected: ${path} -> ${realPath}`\n }\n }\n }\n\n // Check block list first (use real path)\n for (const blocked of this.blockedPaths) {\n if (realPath.startsWith(blocked) || realPath === blocked) {\n return {\n type: 'path',\n rule: `block: ${blocked}`,\n message: `Access to path is blocked: ${path}`\n }\n }\n }\n\n // If allow list contains '*', allow anything not blocked\n if (this.policy.allow.includes('*')) {\n return null\n }\n\n // Check if real path is within allowed directories\n const allowed = this.allowedPaths.some(\n allowedPath =>\n realPath.startsWith(allowedPath) || realPath === allowedPath\n )\n\n if (!allowed) {\n return {\n type: 'path',\n rule: 'allow (outside sandbox)',\n message: `Path is outside allowed directories: ${path}`\n }\n }\n\n return null\n }\n\n /**\n * SECURITY FIX: Resolve symlinks to detect escape attempts\n */\n private resolvePath(path: string): { realPath: string; isSymlink: boolean } {\n const normalizedPath = this.normalizePath(path)\n\n try {\n // Check if path exists and is a symlink\n if (existsSync(normalizedPath)) {\n const stats = lstatSync(normalizedPath)\n const isSymlink = stats.isSymbolicLink()\n\n // Get real path (follows symlinks)\n const realPath = realpathSync(normalizedPath)\n\n return { realPath, isSymlink }\n }\n } catch {\n // Path doesn't exist yet or can't be accessed\n }\n\n return { realPath: normalizedPath, isSymlink: false }\n }\n\n private normalizePath(path: string): string {\n // Expand ~ to home directory\n if (path.startsWith('~')) {\n path = path.replace('~', homedir())\n }\n\n // Handle . as current directory\n if (path === '.') {\n return process.cwd()\n }\n\n return resolve(path)\n }\n\n /**\n * Check if a path would escape the sandbox via symlink\n */\n isSymlinkEscape(path: string): boolean {\n const { realPath, isSymlink } = this.resolvePath(path)\n\n if (!isSymlink) return false\n\n // Check if real path is in blocked list\n for (const blocked of this.blockedPaths) {\n if (realPath.startsWith(blocked)) {\n return true\n }\n }\n\n // Check if real path escapes allowed directories\n if (!this.policy.allow.includes('*')) {\n const inAllowed = this.allowedPaths.some(\n allowedPath => realPath.startsWith(allowedPath)\n )\n if (!inAllowed) {\n return true\n }\n }\n\n return false\n }\n}\n","import type { SecretsPolicy, PolicyViolation } from '../types.js'\n\nexport class SecretsGuard {\n private patterns: RegExp[]\n\n constructor(private policy: SecretsPolicy) {\n this.patterns = policy.patterns.map(p => this.globToRegex(p))\n }\n\n check(command: string, paths: string[]): PolicyViolation | null {\n if (!this.policy.enabled) {\n return null\n }\n\n // Check command for secret file access\n for (const path of paths) {\n if (this.isSecretPath(path)) {\n return {\n type: 'secrets',\n rule: `pattern match: ${path}`,\n message: `Attempted access to sensitive file: ${path}`\n }\n }\n }\n\n // Check for common secret-leaking patterns in commands\n // SECURITY FIX: Enhanced patterns to catch bypass attempts\n const dangerousPatterns = [\n // Direct file access (multiple readers)\n /(cat|head|tail|less|more|bat)\\s+.*\\.env/i,\n /(cat|head|tail|less|more|bat)\\s+.*\\.pem/i,\n /(cat|head|tail|less|more|bat)\\s+.*\\.key/i,\n /(cat|head|tail|less|more|bat)\\s+.*credentials/i,\n /(cat|head|tail|less|more|bat)\\s+.*secret/i,\n /(cat|head|tail|less|more|bat)\\s+.*password/i,\n /(cat|head|tail|less|more|bat)\\s+.*token/i,\n\n // Python/Perl/Ruby file readers\n /python.*open\\s*\\(.*\\.(env|pem|key)/i,\n /python.*-c.*open/i,\n /perl.*-[pne].*\\.(env|pem|key)/i,\n /ruby.*-e.*File\\.(read|open)/i,\n\n // Environment variable exposure\n /echo\\s+\\$\\w*(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|API)/i,\n /printenv.*(KEY|SECRET|TOKEN|PASSWORD)/i,\n /env\\s*\\|\\s*grep.*(KEY|SECRET|TOKEN|PASSWORD)/i,\n\n // Curl/wget with secrets\n /curl.*-d.*\\$\\w*(KEY|SECRET|TOKEN)/i,\n /curl.*-H.*Authorization/i,\n /wget.*--header.*Authorization/i,\n\n // Base64 encoding (obfuscation attempt)\n /base64.*\\.env/i,\n /base64.*\\.pem/i,\n /base64.*\\.key/i,\n /base64\\s+-d/i, // Decoding could reveal secrets\n\n // SECURITY FIX: Command substitution bypass attempts\n /cat\\s+\\$\\(/i, // cat $(...)\n /cat\\s+`/i, // cat `...`\n /cat\\s+\\$\\{/i, // cat ${...}\n\n // SECURITY FIX: Variable indirection\n /\\w+=.*\\.env.*;\\s*cat\\s+\\$/i, // VAR=.env; cat $VAR\n /\\w+=.*secret.*;\\s*cat\\s+\\$/i,\n\n // SECURITY FIX: Glob expansion bypass\n /cat\\s+\\*env/i, // cat *env\n /cat\\s+\\.\\*env/i, // cat .*env\n /cat\\s+\\?\\?env/i, // cat ??env\n\n // SECURITY FIX: Printf/echo tricks\n /printf\\s+.*\\\\x/i, // Hex encoding\n /echo\\s+-e.*\\\\x/i, // Echo with hex\n /echo\\s+-e.*\\\\[0-7]/i, // Octal encoding\n\n // SECURITY FIX: Here-doc/here-string\n /cat\\s*<<.*\\.env/i,\n /cat\\s*<<<.*secret/i,\n\n // Process substitution\n /cat\\s+<\\(/i, // cat <(...)\n\n // History/log access\n /cat.*\\.bash_history/i,\n /cat.*\\.zsh_history/i,\n /cat.*history/i,\n\n // AWS/cloud credentials\n /cat.*\\.aws\\/credentials/i,\n /cat.*\\.aws\\/config/i,\n /cat.*\\.kube\\/config/i,\n /cat.*\\.docker\\/config/i,\n\n // SSH keys\n /cat.*id_rsa/i,\n /cat.*id_ed25519/i,\n /cat.*id_ecdsa/i,\n /cat.*known_hosts/i,\n /cat.*authorized_keys/i,\n\n // GPG\n /cat.*\\.gnupg/i,\n /gpg.*--export-secret/i,\n\n // Git credentials\n /cat.*\\.git-credentials/i,\n /cat.*\\.netrc/i,\n\n // Database files\n /cat.*\\.pgpass/i,\n /cat.*\\.my\\.cnf/i,\n ]\n\n for (const pattern of dangerousPatterns) {\n if (pattern.test(command)) {\n return {\n type: 'secrets',\n rule: 'dangerous pattern',\n message: 'Command may expose secrets'\n }\n }\n }\n\n // SECURITY FIX: Check for encoded commands\n if (this.containsEncodedSecretAccess(command)) {\n return {\n type: 'secrets',\n rule: 'encoded command',\n message: 'Command contains encoded secret access attempt'\n }\n }\n\n return null\n }\n\n /**\n * SECURITY FIX: Detect base64/hex encoded secret access\n */\n private containsEncodedSecretAccess(command: string): boolean {\n // Check for base64 encoded sensitive paths\n const sensitiveBase64 = [\n 'LmVudg==', // .env\n 'LnBlbQ==', // .pem\n 'LmtleQ==', // .key\n 'aWRfcnNh', // id_rsa\n 'Y3JlZGVudGlhbHM=', // credentials\n 'c2VjcmV0', // secret\n ]\n\n for (const encoded of sensitiveBase64) {\n if (command.includes(encoded)) {\n return true\n }\n }\n\n // Check for hex encoded paths\n const sensitiveHex = [\n '2e656e76', // .env\n '2e70656d', // .pem\n '2e6b6579', // .key\n '69645f727361', // id_rsa\n ]\n\n for (const hex of sensitiveHex) {\n if (command.toLowerCase().includes(hex)) {\n return true\n }\n }\n\n return false\n }\n\n private isSecretPath(path: string): boolean {\n const lowerPath = path.toLowerCase()\n\n return this.patterns.some(pattern => pattern.test(lowerPath))\n }\n\n private globToRegex(glob: string): RegExp {\n const escaped = glob\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n\n return new RegExp(escaped, 'i')\n }\n}\n","import type { RateLimitPolicy, PolicyViolation } from '../types.js'\r\n\r\nexport class RateLimiter {\r\n private minuteWindow: number[] = []\r\n private hourWindow: number[] = []\r\n\r\n constructor(private policy: RateLimitPolicy) {}\r\n\r\n check(): PolicyViolation | null {\r\n if (!this.policy.enabled) {\r\n return null\r\n }\r\n\r\n const now = Date.now()\r\n this.cleanup(now)\r\n\r\n // Check per-minute limit\r\n if (this.minuteWindow.length >= this.policy.maxPerMinute) {\r\n return {\r\n type: 'rate_limit',\r\n rule: `maxPerMinute: ${this.policy.maxPerMinute}`,\r\n message: `Rate limit exceeded: ${this.minuteWindow.length}/${this.policy.maxPerMinute} commands per minute`\r\n }\r\n }\r\n\r\n // Check per-hour limit\r\n if (this.hourWindow.length >= this.policy.maxPerHour) {\r\n return {\r\n type: 'rate_limit',\r\n rule: `maxPerHour: ${this.policy.maxPerHour}`,\r\n message: `Rate limit exceeded: ${this.hourWindow.length}/${this.policy.maxPerHour} commands per hour`\r\n }\r\n }\r\n\r\n return null\r\n }\r\n\r\n record(): void {\r\n const now = Date.now()\r\n this.minuteWindow.push(now)\r\n this.hourWindow.push(now)\r\n }\r\n\r\n private cleanup(now: number): void {\r\n const oneMinuteAgo = now - 60 * 1000\r\n const oneHourAgo = now - 60 * 60 * 1000\r\n\r\n this.minuteWindow = this.minuteWindow.filter(t => t > oneMinuteAgo)\r\n this.hourWindow = this.hourWindow.filter(t => t > oneHourAgo)\r\n }\r\n\r\n getStats(): { minute: number; hour: number } {\r\n const now = Date.now()\r\n this.cleanup(now)\r\n\r\n return {\r\n minute: this.minuteWindow.length,\r\n hour: this.hourWindow.length\r\n }\r\n }\r\n}\r\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'\r\nimport { join } from 'path'\r\nimport { homedir } from 'os'\r\n\r\n/**\r\n * Session-based allowlist for temporary command permissions.\r\n * Stored in a temp file that gets cleared on restart.\r\n */\r\n\r\nconst SESSION_FILE = join(homedir(), '.bashbros', 'session-allow.json')\r\n\r\ninterface SessionData {\r\n pid: number\r\n startTime: number\r\n allowedCommands: string[]\r\n}\r\n\r\nfunction ensureDir(): void {\r\n const dir = join(homedir(), '.bashbros')\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true, mode: 0o700 })\r\n }\r\n}\r\n\r\nfunction loadSession(): SessionData | null {\r\n try {\r\n if (!existsSync(SESSION_FILE)) {\r\n return null\r\n }\r\n\r\n const data = JSON.parse(readFileSync(SESSION_FILE, 'utf-8'))\r\n\r\n // Check if session is from current process\r\n if (data.pid !== process.pid) {\r\n // Different process - check if it's stale (older than 24 hours)\r\n const age = Date.now() - data.startTime\r\n if (age > 24 * 60 * 60 * 1000) {\r\n return null\r\n }\r\n }\r\n\r\n return data\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\nfunction saveSession(data: SessionData): void {\r\n ensureDir()\r\n writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), { mode: 0o600 })\r\n}\r\n\r\nfunction getOrCreateSession(): SessionData {\r\n const existing = loadSession()\r\n\r\n if (existing) {\r\n return existing\r\n }\r\n\r\n const newSession: SessionData = {\r\n pid: process.pid,\r\n startTime: Date.now(),\r\n allowedCommands: []\r\n }\r\n\r\n saveSession(newSession)\r\n return newSession\r\n}\r\n\r\n/**\r\n * Add a command to the session allowlist\r\n */\r\nexport function allowForSession(command: string): void {\r\n const session = getOrCreateSession()\r\n\r\n if (!session.allowedCommands.includes(command)) {\r\n session.allowedCommands.push(command)\r\n saveSession(session)\r\n }\r\n}\r\n\r\n/**\r\n * Check if a command is allowed for this session\r\n */\r\nexport function isAllowedForSession(command: string): boolean {\r\n const session = loadSession()\r\n\r\n if (!session) {\r\n return false\r\n }\r\n\r\n // Check exact match\r\n if (session.allowedCommands.includes(command)) {\r\n return true\r\n }\r\n\r\n // Check pattern match (command starts with allowed pattern)\r\n for (const allowed of session.allowedCommands) {\r\n if (allowed.endsWith('*')) {\r\n const prefix = allowed.slice(0, -1)\r\n if (command.startsWith(prefix)) {\r\n return true\r\n }\r\n }\r\n }\r\n\r\n return false\r\n}\r\n\r\n/**\r\n * Get all commands allowed for this session\r\n */\r\nexport function getSessionAllowlist(): string[] {\r\n const session = loadSession()\r\n return session?.allowedCommands || []\r\n}\r\n\r\n/**\r\n * Clear the session allowlist\r\n */\r\nexport function clearSessionAllowlist(): void {\r\n const session = getOrCreateSession()\r\n session.allowedCommands = []\r\n saveSession(session)\r\n}\r\n","import type { BashBrosConfig, PolicyViolation } from '../types.js'\r\nimport { CommandFilter } from './command-filter.js'\r\nimport { PathSandbox } from './path-sandbox.js'\r\nimport { SecretsGuard } from './secrets-guard.js'\r\nimport { RateLimiter } from './rate-limiter.js'\r\nimport { isAllowedForSession } from '../session.js'\r\n\r\nexport class PolicyEngine {\r\n private commandFilter: CommandFilter\r\n private pathSandbox: PathSandbox\r\n private secretsGuard: SecretsGuard\r\n private rateLimiter: RateLimiter\r\n\r\n constructor(private config: BashBrosConfig) {\r\n this.commandFilter = new CommandFilter(config.commands)\r\n this.pathSandbox = new PathSandbox(config.paths)\r\n this.secretsGuard = new SecretsGuard(config.secrets)\r\n this.rateLimiter = new RateLimiter(config.rateLimit)\r\n }\r\n\r\n validate(command: string): PolicyViolation[] {\r\n const violations: PolicyViolation[] = []\r\n\r\n // Check rate limit first\r\n const rateViolation = this.rateLimiter.check()\r\n if (rateViolation) {\r\n violations.push(rateViolation)\r\n return violations // Early exit on rate limit\r\n }\r\n\r\n // Check session allowlist first (temporary permissions)\r\n if (isAllowedForSession(command)) {\r\n this.rateLimiter.record()\r\n return [] // Allowed for this session\r\n }\r\n\r\n // Check command against allow/block lists\r\n const commandViolation = this.commandFilter.check(command)\r\n if (commandViolation) {\r\n violations.push(commandViolation)\r\n }\r\n\r\n // Extract paths from command and check sandbox\r\n const paths = this.extractPaths(command)\r\n for (const path of paths) {\r\n const pathViolation = this.pathSandbox.check(path)\r\n if (pathViolation) {\r\n violations.push(pathViolation)\r\n }\r\n }\r\n\r\n // Check for secrets access\r\n if (this.config.secrets.enabled) {\r\n const secretsViolation = this.secretsGuard.check(command, paths)\r\n if (secretsViolation) {\r\n violations.push(secretsViolation)\r\n }\r\n }\r\n\r\n // Record for rate limiting\r\n this.rateLimiter.record()\r\n\r\n return violations\r\n }\r\n\r\n private extractPaths(command: string): string[] {\r\n const paths: string[] = []\r\n\r\n // Remove quotes for analysis but preserve content\r\n const unquoted = command.replace(/[\"']/g, ' ')\r\n\r\n // Simple path extraction - look for file-like arguments\r\n const tokens = unquoted.split(/\\s+/)\r\n\r\n for (const token of tokens) {\r\n // Skip flags and empty tokens\r\n if (token.startsWith('-') || !token) continue\r\n\r\n // Check if it looks like a path\r\n if (\r\n token.startsWith('/') ||\r\n token.startsWith('./') ||\r\n token.startsWith('../') ||\r\n token.startsWith('~/') ||\r\n token.startsWith('$HOME') ||\r\n token.startsWith('${HOME}') ||\r\n token.includes('.env') ||\r\n token.includes('.pem') ||\r\n token.includes('.key') ||\r\n token.includes('.ssh') ||\r\n token.includes('.aws') ||\r\n token.includes('.gnupg') ||\r\n token.includes('.kube') ||\r\n token.includes('credentials') ||\r\n token.includes('secret') ||\r\n token.includes('password') ||\r\n token.includes('id_rsa') ||\r\n token.includes('id_ed25519') ||\r\n // Files with extensions that might be sensitive\r\n /\\.(env|pem|key|crt|pfx|p12|jks|keystore)$/i.test(token)\r\n ) {\r\n paths.push(token)\r\n }\r\n }\r\n\r\n // Also extract paths from variable assignments\r\n const varAssignments = command.match(/\\w+=[^\\s;]+/g) || []\r\n for (const assignment of varAssignments) {\r\n const value = assignment.split('=')[1]\r\n if (value && (value.includes('/') || value.includes('.'))) {\r\n paths.push(value)\r\n }\r\n }\r\n\r\n return paths\r\n }\r\n\r\n isAllowed(command: string): boolean {\r\n return this.validate(command).length === 0\r\n }\r\n}\r\n"],"mappings":"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","names":["existsSync","homedir"]}
|