@sparkleideas/security 3.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -0
- package/__tests__/acceptance/security-compliance.test.ts +674 -0
- package/__tests__/credential-generator.test.ts +310 -0
- package/__tests__/fixtures/configurations.ts +419 -0
- package/__tests__/fixtures/index.ts +21 -0
- package/__tests__/helpers/create-mock.ts +469 -0
- package/__tests__/helpers/index.ts +32 -0
- package/__tests__/input-validator.test.ts +381 -0
- package/__tests__/integration/security-flow.test.ts +606 -0
- package/__tests__/password-hasher.test.ts +239 -0
- package/__tests__/path-validator.test.ts +302 -0
- package/__tests__/safe-executor.test.ts +292 -0
- package/__tests__/token-generator.test.ts +371 -0
- package/__tests__/unit/credential-generator.test.ts +182 -0
- package/__tests__/unit/password-hasher.test.ts +359 -0
- package/__tests__/unit/path-validator.test.ts +509 -0
- package/__tests__/unit/safe-executor.test.ts +667 -0
- package/__tests__/unit/token-generator.test.ts +310 -0
- package/package.json +28 -0
- package/src/CVE-REMEDIATION.ts +251 -0
- package/src/application/index.ts +10 -0
- package/src/application/services/security-application-service.ts +193 -0
- package/src/credential-generator.ts +368 -0
- package/src/domain/entities/security-context.ts +173 -0
- package/src/domain/index.ts +17 -0
- package/src/domain/services/security-domain-service.ts +296 -0
- package/src/index.ts +271 -0
- package/src/input-validator.ts +466 -0
- package/src/password-hasher.ts +270 -0
- package/src/path-validator.ts +525 -0
- package/src/safe-executor.ts +525 -0
- package/src/token-generator.ts +463 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe Executor - HIGH-1 Remediation
|
|
3
|
+
*
|
|
4
|
+
* Fixes command injection vulnerabilities by:
|
|
5
|
+
* - Using execFile instead of exec with shell
|
|
6
|
+
* - Validating all command arguments
|
|
7
|
+
* - Implementing command allowlist
|
|
8
|
+
* - Sanitizing command inputs
|
|
9
|
+
*
|
|
10
|
+
* Security Properties:
|
|
11
|
+
* - No shell interpretation
|
|
12
|
+
* - Argument validation
|
|
13
|
+
* - Command allowlist enforcement
|
|
14
|
+
* - Timeout controls
|
|
15
|
+
* - Resource limits
|
|
16
|
+
*
|
|
17
|
+
* @module v3/security/safe-executor
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execFile, spawn, ChildProcess } from 'child_process';
|
|
21
|
+
import { promisify } from 'util';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
|
|
24
|
+
const execFileAsync = promisify(execFile);
|
|
25
|
+
|
|
26
|
+
export interface ExecutorConfig {
|
|
27
|
+
/**
|
|
28
|
+
* Allowed commands (allowlist).
|
|
29
|
+
* Only commands in this list can be executed.
|
|
30
|
+
*/
|
|
31
|
+
allowedCommands: string[];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Blocked argument patterns (regex strings).
|
|
35
|
+
* Arguments matching these patterns are rejected.
|
|
36
|
+
*/
|
|
37
|
+
blockedPatterns?: string[];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Maximum execution timeout in milliseconds.
|
|
41
|
+
* Default: 30000 (30 seconds)
|
|
42
|
+
*/
|
|
43
|
+
timeout?: number;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Maximum buffer size for stdout/stderr.
|
|
47
|
+
* Default: 10MB
|
|
48
|
+
*/
|
|
49
|
+
maxBuffer?: number;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Working directory for command execution.
|
|
53
|
+
* Default: process.cwd()
|
|
54
|
+
*/
|
|
55
|
+
cwd?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Environment variables to include.
|
|
59
|
+
* Default: process.env
|
|
60
|
+
*/
|
|
61
|
+
env?: NodeJS.ProcessEnv;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether to allow sudo commands.
|
|
65
|
+
* Default: false
|
|
66
|
+
*/
|
|
67
|
+
allowSudo?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ExecutionResult {
|
|
71
|
+
stdout: string;
|
|
72
|
+
stderr: string;
|
|
73
|
+
exitCode: number;
|
|
74
|
+
command: string;
|
|
75
|
+
args: string[];
|
|
76
|
+
duration: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface StreamingExecutor {
|
|
80
|
+
process: ChildProcess;
|
|
81
|
+
stdout: NodeJS.ReadableStream | null;
|
|
82
|
+
stderr: NodeJS.ReadableStream | null;
|
|
83
|
+
promise: Promise<ExecutionResult>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class SafeExecutorError extends Error {
|
|
87
|
+
constructor(
|
|
88
|
+
message: string,
|
|
89
|
+
public readonly code: string,
|
|
90
|
+
public readonly command?: string,
|
|
91
|
+
public readonly args?: string[],
|
|
92
|
+
) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = 'SafeExecutorError';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Default blocked argument patterns.
|
|
100
|
+
* These patterns indicate potential command injection attempts.
|
|
101
|
+
*/
|
|
102
|
+
const DEFAULT_BLOCKED_PATTERNS = [
|
|
103
|
+
// Shell metacharacters
|
|
104
|
+
';',
|
|
105
|
+
'&&',
|
|
106
|
+
'||',
|
|
107
|
+
'|',
|
|
108
|
+
'`',
|
|
109
|
+
'$(',
|
|
110
|
+
'${',
|
|
111
|
+
// Redirection
|
|
112
|
+
'>',
|
|
113
|
+
'<',
|
|
114
|
+
'>>',
|
|
115
|
+
// Background execution
|
|
116
|
+
'&',
|
|
117
|
+
// Newlines (command chaining)
|
|
118
|
+
'\n',
|
|
119
|
+
'\r',
|
|
120
|
+
// Null byte injection
|
|
121
|
+
'\0',
|
|
122
|
+
// Command substitution
|
|
123
|
+
'$()',
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Commands that are inherently dangerous and should never be allowed.
|
|
128
|
+
*/
|
|
129
|
+
const DANGEROUS_COMMANDS = [
|
|
130
|
+
'rm',
|
|
131
|
+
'rmdir',
|
|
132
|
+
'del',
|
|
133
|
+
'format',
|
|
134
|
+
'mkfs',
|
|
135
|
+
'dd',
|
|
136
|
+
'chmod',
|
|
137
|
+
'chown',
|
|
138
|
+
'kill',
|
|
139
|
+
'killall',
|
|
140
|
+
'pkill',
|
|
141
|
+
'reboot',
|
|
142
|
+
'shutdown',
|
|
143
|
+
'init',
|
|
144
|
+
'poweroff',
|
|
145
|
+
'halt',
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Safe command executor that prevents command injection.
|
|
150
|
+
*
|
|
151
|
+
* This class replaces unsafe exec() and spawn({shell: true}) calls
|
|
152
|
+
* with validated execFile() calls.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const executor = new SafeExecutor({
|
|
157
|
+
* allowedCommands: ['git', 'npm', 'node']
|
|
158
|
+
* });
|
|
159
|
+
*
|
|
160
|
+
* const result = await executor.execute('git', ['status']);
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export class SafeExecutor {
|
|
164
|
+
private readonly config: Required<ExecutorConfig>;
|
|
165
|
+
private readonly blockedPatterns: RegExp[];
|
|
166
|
+
|
|
167
|
+
constructor(config: ExecutorConfig) {
|
|
168
|
+
this.config = {
|
|
169
|
+
allowedCommands: config.allowedCommands,
|
|
170
|
+
blockedPatterns: config.blockedPatterns ?? DEFAULT_BLOCKED_PATTERNS,
|
|
171
|
+
timeout: config.timeout ?? 30000,
|
|
172
|
+
maxBuffer: config.maxBuffer ?? 10 * 1024 * 1024, // 10MB
|
|
173
|
+
cwd: config.cwd ?? process.cwd(),
|
|
174
|
+
env: config.env ?? process.env,
|
|
175
|
+
allowSudo: config.allowSudo ?? false,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Compile blocked patterns for performance
|
|
179
|
+
this.blockedPatterns = this.config.blockedPatterns.map(
|
|
180
|
+
pattern => new RegExp(this.escapeRegExp(pattern), 'i')
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
this.validateConfig();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Escapes special regex characters.
|
|
188
|
+
*/
|
|
189
|
+
private escapeRegExp(str: string): string {
|
|
190
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validates executor configuration.
|
|
195
|
+
*/
|
|
196
|
+
private validateConfig(): void {
|
|
197
|
+
if (this.config.allowedCommands.length === 0) {
|
|
198
|
+
throw new SafeExecutorError(
|
|
199
|
+
'At least one allowed command must be specified',
|
|
200
|
+
'EMPTY_ALLOWLIST'
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check for dangerous commands in allowlist
|
|
205
|
+
const dangerousAllowed = this.config.allowedCommands.filter(
|
|
206
|
+
cmd => DANGEROUS_COMMANDS.includes(path.basename(cmd))
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (dangerousAllowed.length > 0) {
|
|
210
|
+
throw new SafeExecutorError(
|
|
211
|
+
`Dangerous commands cannot be allowed: ${dangerousAllowed.join(', ')}`,
|
|
212
|
+
'DANGEROUS_COMMAND_ALLOWED'
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Validates a command against the allowlist.
|
|
219
|
+
*
|
|
220
|
+
* @param command - Command to validate
|
|
221
|
+
* @throws SafeExecutorError if command is not allowed
|
|
222
|
+
*/
|
|
223
|
+
private validateCommand(command: string): void {
|
|
224
|
+
const basename = path.basename(command);
|
|
225
|
+
|
|
226
|
+
// Check if command is allowed
|
|
227
|
+
const isAllowed = this.config.allowedCommands.some(allowed => {
|
|
228
|
+
const allowedBasename = path.basename(allowed);
|
|
229
|
+
return command === allowed || basename === allowedBasename;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (!isAllowed) {
|
|
233
|
+
throw new SafeExecutorError(
|
|
234
|
+
`Command not in allowlist: ${command}`,
|
|
235
|
+
'COMMAND_NOT_ALLOWED',
|
|
236
|
+
command
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for sudo
|
|
241
|
+
if (!this.config.allowSudo && (command === 'sudo' || basename === 'sudo')) {
|
|
242
|
+
throw new SafeExecutorError(
|
|
243
|
+
'Sudo commands are not allowed',
|
|
244
|
+
'SUDO_NOT_ALLOWED',
|
|
245
|
+
command
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Validates command arguments for injection patterns.
|
|
252
|
+
*
|
|
253
|
+
* @param args - Arguments to validate
|
|
254
|
+
* @throws SafeExecutorError if arguments contain dangerous patterns
|
|
255
|
+
*/
|
|
256
|
+
private validateArguments(args: string[]): void {
|
|
257
|
+
for (const arg of args) {
|
|
258
|
+
// Check for null bytes
|
|
259
|
+
if (arg.includes('\0')) {
|
|
260
|
+
throw new SafeExecutorError(
|
|
261
|
+
'Null byte detected in argument',
|
|
262
|
+
'NULL_BYTE_INJECTION',
|
|
263
|
+
undefined,
|
|
264
|
+
args
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check against blocked patterns
|
|
269
|
+
for (const pattern of this.blockedPatterns) {
|
|
270
|
+
if (pattern.test(arg)) {
|
|
271
|
+
throw new SafeExecutorError(
|
|
272
|
+
`Dangerous pattern detected in argument: ${arg}`,
|
|
273
|
+
'DANGEROUS_PATTERN',
|
|
274
|
+
undefined,
|
|
275
|
+
args
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check for command chaining attempts
|
|
281
|
+
if (/^-.*[;&|]/.test(arg)) {
|
|
282
|
+
throw new SafeExecutorError(
|
|
283
|
+
`Potential command chaining in argument: ${arg}`,
|
|
284
|
+
'COMMAND_CHAINING',
|
|
285
|
+
undefined,
|
|
286
|
+
args
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Sanitizes a single argument.
|
|
294
|
+
*
|
|
295
|
+
* @param arg - Argument to sanitize
|
|
296
|
+
* @returns Sanitized argument
|
|
297
|
+
*/
|
|
298
|
+
sanitizeArgument(arg: string): string {
|
|
299
|
+
// Remove null bytes
|
|
300
|
+
let sanitized = arg.replace(/\0/g, '');
|
|
301
|
+
|
|
302
|
+
// Remove shell metacharacters
|
|
303
|
+
sanitized = sanitized.replace(/[;&|`$(){}><\n\r]/g, '');
|
|
304
|
+
|
|
305
|
+
return sanitized;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Executes a command safely.
|
|
310
|
+
*
|
|
311
|
+
* @param command - Command to execute (must be in allowlist)
|
|
312
|
+
* @param args - Command arguments
|
|
313
|
+
* @returns Execution result
|
|
314
|
+
* @throws SafeExecutorError on validation failure or execution error
|
|
315
|
+
*/
|
|
316
|
+
async execute(command: string, args: string[] = []): Promise<ExecutionResult> {
|
|
317
|
+
const startTime = Date.now();
|
|
318
|
+
|
|
319
|
+
// Validate command
|
|
320
|
+
this.validateCommand(command);
|
|
321
|
+
|
|
322
|
+
// Validate arguments
|
|
323
|
+
this.validateArguments(args);
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
// Execute command WITHOUT shell
|
|
327
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
328
|
+
cwd: this.config.cwd,
|
|
329
|
+
env: this.config.env,
|
|
330
|
+
timeout: this.config.timeout,
|
|
331
|
+
maxBuffer: this.config.maxBuffer,
|
|
332
|
+
shell: false, // CRITICAL: Never use shell
|
|
333
|
+
windowsHide: true,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
stdout: stdout.toString(),
|
|
338
|
+
stderr: stderr.toString(),
|
|
339
|
+
exitCode: 0,
|
|
340
|
+
command,
|
|
341
|
+
args,
|
|
342
|
+
duration: Date.now() - startTime,
|
|
343
|
+
};
|
|
344
|
+
} catch (error: any) {
|
|
345
|
+
// Handle execution errors
|
|
346
|
+
if (error.killed) {
|
|
347
|
+
throw new SafeExecutorError(
|
|
348
|
+
'Command execution timed out',
|
|
349
|
+
'TIMEOUT',
|
|
350
|
+
command,
|
|
351
|
+
args
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (error.code === 'ENOENT') {
|
|
356
|
+
throw new SafeExecutorError(
|
|
357
|
+
`Command not found: ${command}`,
|
|
358
|
+
'COMMAND_NOT_FOUND',
|
|
359
|
+
command,
|
|
360
|
+
args
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Return result with non-zero exit code
|
|
365
|
+
return {
|
|
366
|
+
stdout: error.stdout?.toString() ?? '',
|
|
367
|
+
stderr: error.stderr?.toString() ?? error.message,
|
|
368
|
+
exitCode: error.code ?? 1,
|
|
369
|
+
command,
|
|
370
|
+
args,
|
|
371
|
+
duration: Date.now() - startTime,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Executes a command with streaming output.
|
|
378
|
+
*
|
|
379
|
+
* @param command - Command to execute
|
|
380
|
+
* @param args - Command arguments
|
|
381
|
+
* @returns Streaming executor with process handles
|
|
382
|
+
*/
|
|
383
|
+
executeStreaming(command: string, args: string[] = []): StreamingExecutor {
|
|
384
|
+
const startTime = Date.now();
|
|
385
|
+
|
|
386
|
+
// Validate command
|
|
387
|
+
this.validateCommand(command);
|
|
388
|
+
|
|
389
|
+
// Validate arguments
|
|
390
|
+
this.validateArguments(args);
|
|
391
|
+
|
|
392
|
+
// Spawn process WITHOUT shell
|
|
393
|
+
const childProcess = spawn(command, args, {
|
|
394
|
+
cwd: this.config.cwd,
|
|
395
|
+
env: this.config.env,
|
|
396
|
+
timeout: this.config.timeout,
|
|
397
|
+
shell: false, // CRITICAL: Never use shell
|
|
398
|
+
windowsHide: true,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const promise = new Promise<ExecutionResult>((resolve, reject) => {
|
|
402
|
+
let stdout = '';
|
|
403
|
+
let stderr = '';
|
|
404
|
+
|
|
405
|
+
childProcess.stdout?.on('data', (data: Buffer) => {
|
|
406
|
+
stdout += data.toString();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
childProcess.stderr?.on('data', (data: Buffer) => {
|
|
410
|
+
stderr += data.toString();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
childProcess.on('close', (code) => {
|
|
414
|
+
resolve({
|
|
415
|
+
stdout,
|
|
416
|
+
stderr,
|
|
417
|
+
exitCode: code ?? 0,
|
|
418
|
+
command,
|
|
419
|
+
args,
|
|
420
|
+
duration: Date.now() - startTime,
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
childProcess.on('error', (error) => {
|
|
425
|
+
reject(new SafeExecutorError(
|
|
426
|
+
error.message,
|
|
427
|
+
'EXECUTION_ERROR',
|
|
428
|
+
command,
|
|
429
|
+
args
|
|
430
|
+
));
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
process: childProcess,
|
|
436
|
+
stdout: childProcess.stdout,
|
|
437
|
+
stderr: childProcess.stderr,
|
|
438
|
+
promise,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Adds a command to the allowlist at runtime.
|
|
444
|
+
*
|
|
445
|
+
* @param command - Command to add
|
|
446
|
+
*/
|
|
447
|
+
allowCommand(command: string): void {
|
|
448
|
+
const basename = path.basename(command);
|
|
449
|
+
|
|
450
|
+
if (DANGEROUS_COMMANDS.includes(basename)) {
|
|
451
|
+
throw new SafeExecutorError(
|
|
452
|
+
`Cannot allow dangerous command: ${command}`,
|
|
453
|
+
'DANGEROUS_COMMAND'
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!this.config.allowedCommands.includes(command)) {
|
|
458
|
+
this.config.allowedCommands.push(command);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Checks if a command is allowed.
|
|
464
|
+
*
|
|
465
|
+
* @param command - Command to check
|
|
466
|
+
* @returns True if command is allowed
|
|
467
|
+
*/
|
|
468
|
+
isCommandAllowed(command: string): boolean {
|
|
469
|
+
const basename = path.basename(command);
|
|
470
|
+
return this.config.allowedCommands.some(allowed => {
|
|
471
|
+
const allowedBasename = path.basename(allowed);
|
|
472
|
+
return command === allowed || basename === allowedBasename;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Returns the current allowlist.
|
|
478
|
+
*/
|
|
479
|
+
getAllowedCommands(): readonly string[] {
|
|
480
|
+
return [...this.config.allowedCommands];
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Factory function to create a safe executor for common development tasks.
|
|
486
|
+
*
|
|
487
|
+
* @returns Configured SafeExecutor for git, npm, and node
|
|
488
|
+
*/
|
|
489
|
+
export function createDevelopmentExecutor(): SafeExecutor {
|
|
490
|
+
return new SafeExecutor({
|
|
491
|
+
allowedCommands: [
|
|
492
|
+
'git',
|
|
493
|
+
'npm',
|
|
494
|
+
'npx',
|
|
495
|
+
'node',
|
|
496
|
+
'tsc',
|
|
497
|
+
'vitest',
|
|
498
|
+
'eslint',
|
|
499
|
+
'prettier',
|
|
500
|
+
],
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Factory function to create a read-only executor.
|
|
506
|
+
* Only allows commands that read without modifying.
|
|
507
|
+
*
|
|
508
|
+
* @returns Configured SafeExecutor for read operations
|
|
509
|
+
*/
|
|
510
|
+
export function createReadOnlyExecutor(): SafeExecutor {
|
|
511
|
+
return new SafeExecutor({
|
|
512
|
+
allowedCommands: [
|
|
513
|
+
'git',
|
|
514
|
+
'cat',
|
|
515
|
+
'head',
|
|
516
|
+
'tail',
|
|
517
|
+
'ls',
|
|
518
|
+
'find',
|
|
519
|
+
'grep',
|
|
520
|
+
'which',
|
|
521
|
+
'echo',
|
|
522
|
+
],
|
|
523
|
+
timeout: 10000,
|
|
524
|
+
});
|
|
525
|
+
}
|