@probelabs/probe 0.6.0-rc105 → 0.6.0-rc106
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/build/agent/ProbeAgent.js +12 -1
- package/build/agent/bashCommandUtils.js +200 -0
- package/build/agent/bashDefaults.js +202 -0
- package/build/agent/bashExecutor.js +319 -0
- package/build/agent/bashPermissions.js +228 -0
- package/build/agent/index.js +1489 -145
- package/build/agent/probeTool.js +9 -0
- package/build/agent/schemaUtils.js +28 -8
- package/build/agent/tools.js +13 -1
- package/build/index.js +6 -0
- package/build/tools/bash.js +216 -0
- package/build/tools/common.js +64 -0
- package/build/tools/index.js +6 -0
- package/cjs/agent/ProbeAgent.cjs +1445 -167
- package/cjs/index.cjs +1520 -176
- package/package.json +2 -2
- package/src/agent/ProbeAgent.js +12 -1
- package/src/agent/bashCommandUtils.js +200 -0
- package/src/agent/bashDefaults.js +202 -0
- package/src/agent/bashExecutor.js +319 -0
- package/src/agent/bashPermissions.js +228 -0
- package/src/agent/index.js +83 -2
- package/src/agent/probeTool.js +9 -0
- package/src/agent/schemaUtils.js +28 -8
- package/src/agent/tools.js +13 -1
- package/src/index.js +6 -0
- package/src/tools/bash.js +216 -0
- package/src/tools/common.js +64 -0
- package/src/tools/index.js +6 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash command executor with security and timeout controls
|
|
3
|
+
* @module agent/bashExecutor
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import { resolve, join } from 'path';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { parseCommandForExecution } from './bashCommandUtils.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Execute a bash command with security controls
|
|
13
|
+
* @param {string} command - Command to execute
|
|
14
|
+
* @param {Object} options - Execution options
|
|
15
|
+
* @param {string} [options.workingDirectory] - Working directory for command execution
|
|
16
|
+
* @param {number} [options.timeout=120000] - Timeout in milliseconds
|
|
17
|
+
* @param {Object} [options.env={}] - Additional environment variables
|
|
18
|
+
* @param {number} [options.maxBuffer=10485760] - Maximum buffer size (10MB)
|
|
19
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
20
|
+
* @returns {Promise<Object>} Execution result
|
|
21
|
+
*/
|
|
22
|
+
export async function executeBashCommand(command, options = {}) {
|
|
23
|
+
const {
|
|
24
|
+
workingDirectory = process.cwd(),
|
|
25
|
+
timeout = 120000, // 2 minutes default
|
|
26
|
+
env = {},
|
|
27
|
+
maxBuffer = 10 * 1024 * 1024, // 10MB
|
|
28
|
+
debug = false
|
|
29
|
+
} = options;
|
|
30
|
+
|
|
31
|
+
// Validate working directory
|
|
32
|
+
let cwd = workingDirectory;
|
|
33
|
+
try {
|
|
34
|
+
cwd = resolve(cwd);
|
|
35
|
+
if (!existsSync(cwd)) {
|
|
36
|
+
throw new Error(`Working directory does not exist: ${cwd}`);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: `Invalid working directory: ${error.message}`,
|
|
42
|
+
stdout: '',
|
|
43
|
+
stderr: '',
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
command,
|
|
46
|
+
workingDirectory: cwd,
|
|
47
|
+
duration: 0
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
|
|
53
|
+
if (debug) {
|
|
54
|
+
console.log(`[BashExecutor] Executing command: "${command}"`);
|
|
55
|
+
console.log(`[BashExecutor] Working directory: "${cwd}"`);
|
|
56
|
+
console.log(`[BashExecutor] Timeout: ${timeout}ms`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
// Create environment
|
|
61
|
+
const processEnv = {
|
|
62
|
+
...process.env,
|
|
63
|
+
...env
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Parse command for shell execution
|
|
67
|
+
// We use shell: false for security, so we need to parse manually
|
|
68
|
+
const args = parseCommandForExecution(command);
|
|
69
|
+
if (!args || args.length === 0) {
|
|
70
|
+
resolve({
|
|
71
|
+
success: false,
|
|
72
|
+
error: 'Failed to parse command',
|
|
73
|
+
stdout: '',
|
|
74
|
+
stderr: '',
|
|
75
|
+
exitCode: 1,
|
|
76
|
+
command,
|
|
77
|
+
workingDirectory: cwd,
|
|
78
|
+
duration: Date.now() - startTime
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const [cmd, ...cmdArgs] = args;
|
|
84
|
+
|
|
85
|
+
// Spawn the process
|
|
86
|
+
const child = spawn(cmd, cmdArgs, {
|
|
87
|
+
cwd,
|
|
88
|
+
env: processEnv,
|
|
89
|
+
stdio: ['ignore', 'pipe', 'pipe'], // stdin ignored, capture stdout/stderr
|
|
90
|
+
shell: false, // For security
|
|
91
|
+
windowsHide: true
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let stdout = '';
|
|
95
|
+
let stderr = '';
|
|
96
|
+
let killed = false;
|
|
97
|
+
let timeoutHandle;
|
|
98
|
+
|
|
99
|
+
// Set timeout
|
|
100
|
+
if (timeout > 0) {
|
|
101
|
+
timeoutHandle = setTimeout(() => {
|
|
102
|
+
if (!killed) {
|
|
103
|
+
killed = true;
|
|
104
|
+
child.kill('SIGTERM');
|
|
105
|
+
|
|
106
|
+
// Force kill after 5 seconds if still running
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
if (child.exitCode === null) {
|
|
109
|
+
child.kill('SIGKILL');
|
|
110
|
+
}
|
|
111
|
+
}, 5000);
|
|
112
|
+
}
|
|
113
|
+
}, timeout);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle stdout
|
|
117
|
+
child.stdout.on('data', (data) => {
|
|
118
|
+
const chunk = data.toString();
|
|
119
|
+
if (stdout.length + chunk.length <= maxBuffer) {
|
|
120
|
+
stdout += chunk;
|
|
121
|
+
} else {
|
|
122
|
+
// Buffer overflow
|
|
123
|
+
if (!killed) {
|
|
124
|
+
killed = true;
|
|
125
|
+
child.kill('SIGTERM');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Handle stderr
|
|
131
|
+
child.stderr.on('data', (data) => {
|
|
132
|
+
const chunk = data.toString();
|
|
133
|
+
if (stderr.length + chunk.length <= maxBuffer) {
|
|
134
|
+
stderr += chunk;
|
|
135
|
+
} else {
|
|
136
|
+
// Buffer overflow
|
|
137
|
+
if (!killed) {
|
|
138
|
+
killed = true;
|
|
139
|
+
child.kill('SIGTERM');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Handle process exit
|
|
145
|
+
child.on('close', (code, signal) => {
|
|
146
|
+
if (timeoutHandle) {
|
|
147
|
+
clearTimeout(timeoutHandle);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const duration = Date.now() - startTime;
|
|
151
|
+
|
|
152
|
+
if (debug) {
|
|
153
|
+
console.log(`[BashExecutor] Command completed - Code: ${code}, Signal: ${signal}, Duration: ${duration}ms`);
|
|
154
|
+
console.log(`[BashExecutor] Stdout length: ${stdout.length}, Stderr length: ${stderr.length}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let success = true;
|
|
158
|
+
let error = '';
|
|
159
|
+
|
|
160
|
+
if (killed) {
|
|
161
|
+
success = false;
|
|
162
|
+
if (stdout.length + stderr.length > maxBuffer) {
|
|
163
|
+
error = `Command output exceeded maximum buffer size (${maxBuffer} bytes)`;
|
|
164
|
+
} else {
|
|
165
|
+
error = `Command timed out after ${timeout}ms`;
|
|
166
|
+
}
|
|
167
|
+
} else if (code !== 0) {
|
|
168
|
+
success = false;
|
|
169
|
+
error = `Command exited with code ${code}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
resolve({
|
|
173
|
+
success,
|
|
174
|
+
error,
|
|
175
|
+
stdout: stdout.trim(),
|
|
176
|
+
stderr: stderr.trim(),
|
|
177
|
+
exitCode: code,
|
|
178
|
+
signal,
|
|
179
|
+
command,
|
|
180
|
+
workingDirectory: cwd,
|
|
181
|
+
duration,
|
|
182
|
+
killed
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Handle spawn errors
|
|
187
|
+
child.on('error', (error) => {
|
|
188
|
+
if (timeoutHandle) {
|
|
189
|
+
clearTimeout(timeoutHandle);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (debug) {
|
|
193
|
+
console.log(`[BashExecutor] Spawn error:`, error);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
resolve({
|
|
197
|
+
success: false,
|
|
198
|
+
error: `Failed to execute command: ${error.message}`,
|
|
199
|
+
stdout: '',
|
|
200
|
+
stderr: '',
|
|
201
|
+
exitCode: 1,
|
|
202
|
+
command,
|
|
203
|
+
workingDirectory: cwd,
|
|
204
|
+
duration: Date.now() - startTime
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format execution result for display
|
|
213
|
+
* @param {Object} result - Execution result
|
|
214
|
+
* @param {boolean} [includeMetadata=false] - Include metadata in output
|
|
215
|
+
* @returns {string} Formatted result
|
|
216
|
+
*/
|
|
217
|
+
export function formatExecutionResult(result, includeMetadata = false) {
|
|
218
|
+
if (!result) {
|
|
219
|
+
return 'No result available';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let output = '';
|
|
223
|
+
|
|
224
|
+
// Add command info if metadata requested
|
|
225
|
+
if (includeMetadata) {
|
|
226
|
+
output += `Command: ${result.command}\n`;
|
|
227
|
+
output += `Working directory: ${result.workingDirectory}\n`;
|
|
228
|
+
output += `Duration: ${result.duration}ms\n`;
|
|
229
|
+
output += `Exit Code: ${result.exitCode}\n`;
|
|
230
|
+
if (result.signal) {
|
|
231
|
+
output += `Signal: ${result.signal}\n`;
|
|
232
|
+
}
|
|
233
|
+
output += '\n';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Add stdout if present
|
|
237
|
+
if (result.stdout) {
|
|
238
|
+
if (includeMetadata) {
|
|
239
|
+
output += '--- STDOUT ---\n';
|
|
240
|
+
}
|
|
241
|
+
output += result.stdout;
|
|
242
|
+
if (includeMetadata && result.stderr) {
|
|
243
|
+
output += '\n';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add stderr if present
|
|
248
|
+
if (result.stderr) {
|
|
249
|
+
if (includeMetadata) {
|
|
250
|
+
if (result.stdout) output += '\n';
|
|
251
|
+
output += '--- STDERR ---\n';
|
|
252
|
+
} else if (result.stdout) {
|
|
253
|
+
output += '\n--- STDERR ---\n';
|
|
254
|
+
}
|
|
255
|
+
output += result.stderr;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Add error message if failed and no stderr
|
|
259
|
+
if (!result.success && result.error && !result.stderr) {
|
|
260
|
+
if (output) output += '\n';
|
|
261
|
+
output += `Error: ${result.error}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Add exit code for failed commands
|
|
265
|
+
if (!result.success && result.exitCode !== undefined && result.exitCode !== 0) {
|
|
266
|
+
if (output) output += '\n';
|
|
267
|
+
output += `Exit code: ${result.exitCode}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return output || (result.success ? 'Command completed successfully (no output)' : 'Command failed (no output)');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Validate execution options
|
|
275
|
+
* @param {Object} options - Options to validate
|
|
276
|
+
* @returns {Object} Validation result
|
|
277
|
+
*/
|
|
278
|
+
export function validateExecutionOptions(options = {}) {
|
|
279
|
+
const errors = [];
|
|
280
|
+
const warnings = [];
|
|
281
|
+
|
|
282
|
+
// Check timeout
|
|
283
|
+
if (options.timeout !== undefined) {
|
|
284
|
+
if (typeof options.timeout !== 'number' || options.timeout < 0) {
|
|
285
|
+
errors.push('timeout must be a non-negative number');
|
|
286
|
+
} else if (options.timeout > 600000) { // 10 minutes
|
|
287
|
+
warnings.push('timeout is very high (>10 minutes)');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check maxBuffer
|
|
292
|
+
if (options.maxBuffer !== undefined) {
|
|
293
|
+
if (typeof options.maxBuffer !== 'number' || options.maxBuffer < 1024) {
|
|
294
|
+
errors.push('maxBuffer must be at least 1024 bytes');
|
|
295
|
+
} else if (options.maxBuffer > 100 * 1024 * 1024) { // 100MB
|
|
296
|
+
warnings.push('maxBuffer is very high (>100MB)');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check working directory
|
|
301
|
+
if (options.workingDirectory) {
|
|
302
|
+
if (typeof options.workingDirectory !== 'string') {
|
|
303
|
+
errors.push('workingDirectory must be a string');
|
|
304
|
+
} else if (!existsSync(options.workingDirectory)) {
|
|
305
|
+
errors.push(`workingDirectory does not exist: ${options.workingDirectory}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check environment
|
|
310
|
+
if (options.env && typeof options.env !== 'object') {
|
|
311
|
+
errors.push('env must be an object');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
valid: errors.length === 0,
|
|
316
|
+
errors,
|
|
317
|
+
warnings
|
|
318
|
+
};
|
|
319
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified bash command permission checker (aligned with executor capabilities)
|
|
3
|
+
* @module agent/bashPermissions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DEFAULT_ALLOW_PATTERNS, DEFAULT_DENY_PATTERNS } from './bashDefaults.js';
|
|
7
|
+
import { parseCommand, isComplexCommand } from './bashCommandUtils.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if a pattern matches a parsed command
|
|
11
|
+
* @param {Object} parsedCommand - Parsed command with command and args
|
|
12
|
+
* @param {string} pattern - Pattern to match against (e.g., "git:status", "npm:*")
|
|
13
|
+
* @returns {boolean} True if pattern matches
|
|
14
|
+
*/
|
|
15
|
+
function matchesPattern(parsedCommand, pattern) {
|
|
16
|
+
if (!parsedCommand || !pattern) return false;
|
|
17
|
+
|
|
18
|
+
const { command, args } = parsedCommand;
|
|
19
|
+
if (!command) return false;
|
|
20
|
+
|
|
21
|
+
// Split pattern into parts separated by ':'
|
|
22
|
+
const patternParts = pattern.split(':');
|
|
23
|
+
const commandName = patternParts[0];
|
|
24
|
+
|
|
25
|
+
// Check if command name matches (with wildcard support)
|
|
26
|
+
if (commandName === '*') {
|
|
27
|
+
// Wildcard matches any command
|
|
28
|
+
return true;
|
|
29
|
+
} else if (commandName !== command) {
|
|
30
|
+
// Command name doesn't match
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// If only command name specified, it matches
|
|
35
|
+
if (patternParts.length === 1) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check arguments
|
|
40
|
+
for (let i = 1; i < patternParts.length; i++) {
|
|
41
|
+
const patternArg = patternParts[i];
|
|
42
|
+
const argIndex = i - 1;
|
|
43
|
+
|
|
44
|
+
if (patternArg === '*') {
|
|
45
|
+
// Wildcard matches any argument (or no argument)
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (argIndex >= args.length) {
|
|
50
|
+
// Not enough arguments to match pattern
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const actualArg = args[argIndex];
|
|
55
|
+
if (patternArg !== actualArg) {
|
|
56
|
+
// Argument doesn't match
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if any pattern in a list matches the command
|
|
66
|
+
* @param {Object} parsedCommand - Parsed command
|
|
67
|
+
* @param {string[]} patterns - Array of patterns to check
|
|
68
|
+
* @returns {boolean} True if any pattern matches
|
|
69
|
+
*/
|
|
70
|
+
function matchesAnyPattern(parsedCommand, patterns) {
|
|
71
|
+
if (!patterns || patterns.length === 0) return false;
|
|
72
|
+
return patterns.some(pattern => matchesPattern(parsedCommand, pattern));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Bash permission checker for simple commands only
|
|
77
|
+
* Rejects complex shell constructs for security and alignment with executor
|
|
78
|
+
*/
|
|
79
|
+
export class BashPermissionChecker {
|
|
80
|
+
/**
|
|
81
|
+
* Create a permission checker
|
|
82
|
+
* @param {Object} config - Configuration options
|
|
83
|
+
* @param {string[]} [config.allow] - Additional allow patterns
|
|
84
|
+
* @param {string[]} [config.deny] - Additional deny patterns
|
|
85
|
+
* @param {boolean} [config.disableDefaultAllow] - Disable default allow list
|
|
86
|
+
* @param {boolean} [config.disableDefaultDeny] - Disable default deny list
|
|
87
|
+
* @param {boolean} [config.debug] - Enable debug logging
|
|
88
|
+
*/
|
|
89
|
+
constructor(config = {}) {
|
|
90
|
+
this.debug = config.debug || false;
|
|
91
|
+
|
|
92
|
+
// Build allow patterns
|
|
93
|
+
this.allowPatterns = [];
|
|
94
|
+
if (!config.disableDefaultAllow) {
|
|
95
|
+
this.allowPatterns.push(...DEFAULT_ALLOW_PATTERNS);
|
|
96
|
+
if (this.debug) {
|
|
97
|
+
console.log(`[BashPermissions] Added ${DEFAULT_ALLOW_PATTERNS.length} default allow patterns`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (config.allow && Array.isArray(config.allow)) {
|
|
101
|
+
this.allowPatterns.push(...config.allow);
|
|
102
|
+
if (this.debug) {
|
|
103
|
+
console.log(`[BashPermissions] Added ${config.allow.length} custom allow patterns:`, config.allow);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Build deny patterns
|
|
108
|
+
this.denyPatterns = [];
|
|
109
|
+
if (!config.disableDefaultDeny) {
|
|
110
|
+
this.denyPatterns.push(...DEFAULT_DENY_PATTERNS);
|
|
111
|
+
if (this.debug) {
|
|
112
|
+
console.log(`[BashPermissions] Added ${DEFAULT_DENY_PATTERNS.length} default deny patterns`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (config.deny && Array.isArray(config.deny)) {
|
|
116
|
+
this.denyPatterns.push(...config.deny);
|
|
117
|
+
if (this.debug) {
|
|
118
|
+
console.log(`[BashPermissions] Added ${config.deny.length} custom deny patterns:`, config.deny);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (this.debug) {
|
|
123
|
+
console.log(`[BashPermissions] Total patterns - Allow: ${this.allowPatterns.length}, Deny: ${this.denyPatterns.length}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if a simple command is allowed (rejects complex commands for security)
|
|
129
|
+
* @param {string} command - Command to check
|
|
130
|
+
* @returns {Object} Permission result
|
|
131
|
+
*/
|
|
132
|
+
check(command) {
|
|
133
|
+
if (!command || typeof command !== 'string') {
|
|
134
|
+
return {
|
|
135
|
+
allowed: false,
|
|
136
|
+
reason: 'Invalid or empty command',
|
|
137
|
+
command: command
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// First check if this is a complex command - reject immediately for security
|
|
142
|
+
if (isComplexCommand(command)) {
|
|
143
|
+
return {
|
|
144
|
+
allowed: false,
|
|
145
|
+
reason: 'Complex shell commands with pipes, operators, or redirections are not supported for security reasons',
|
|
146
|
+
command: command,
|
|
147
|
+
isComplex: true
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Parse the simple command
|
|
152
|
+
const parsed = parseCommand(command);
|
|
153
|
+
|
|
154
|
+
if (parsed.error) {
|
|
155
|
+
return {
|
|
156
|
+
allowed: false,
|
|
157
|
+
reason: parsed.error,
|
|
158
|
+
command: command
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!parsed.command) {
|
|
163
|
+
return {
|
|
164
|
+
allowed: false,
|
|
165
|
+
reason: 'No valid command found',
|
|
166
|
+
command: command
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (this.debug) {
|
|
171
|
+
console.log(`[BashPermissions] Checking simple command: "${command}"`);
|
|
172
|
+
console.log(`[BashPermissions] Parsed: ${parsed.command} with args: [${parsed.args.join(', ')}]`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check deny patterns first (deny takes precedence)
|
|
176
|
+
if (matchesAnyPattern(parsed, this.denyPatterns)) {
|
|
177
|
+
const matchedPatterns = this.denyPatterns.filter(pattern => matchesPattern(parsed, pattern));
|
|
178
|
+
return {
|
|
179
|
+
allowed: false,
|
|
180
|
+
reason: `Command matches deny pattern: ${matchedPatterns[0]}`,
|
|
181
|
+
command: command,
|
|
182
|
+
parsed: parsed,
|
|
183
|
+
matchedPatterns: matchedPatterns
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check allow patterns
|
|
188
|
+
if (this.allowPatterns.length > 0) {
|
|
189
|
+
if (!matchesAnyPattern(parsed, this.allowPatterns)) {
|
|
190
|
+
return {
|
|
191
|
+
allowed: false,
|
|
192
|
+
reason: 'Command not in allow list',
|
|
193
|
+
command: command,
|
|
194
|
+
parsed: parsed
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Command passed all checks
|
|
200
|
+
const result = {
|
|
201
|
+
allowed: true,
|
|
202
|
+
command: command,
|
|
203
|
+
parsed: parsed,
|
|
204
|
+
isComplex: false
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (this.debug) {
|
|
208
|
+
console.log(`[BashPermissions] ALLOWED - command passed all checks`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get configuration summary
|
|
216
|
+
* @returns {Object} Configuration info
|
|
217
|
+
*/
|
|
218
|
+
getConfig() {
|
|
219
|
+
return {
|
|
220
|
+
allowPatterns: this.allowPatterns.length,
|
|
221
|
+
denyPatterns: this.denyPatterns.length,
|
|
222
|
+
totalPatterns: this.allowPatterns.length + this.denyPatterns.length
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Export utility functions for testing
|
|
228
|
+
export { parseCommand, matchesPattern, matchesAnyPattern };
|