replace shell-quote with regex-based command parsing

shell-quote is a quoting/escaping library, not a shell command parser.
Replace with a regex splitter that handles pipes, chains, subshells,
and parenthesized groups. Strips () from tokens via sanitizeToken.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Arjun 2026-02-24 12:54:57 +05:30
parent bfba0f0682
commit d9afc076cc
3 changed files with 24 additions and 37 deletions

View file

@ -35,7 +35,6 @@
"papaparse": "^5.5.3",
"pdf-parse": "^2.4.5",
"react": "^19.2.3",
"shell-quote": "^1.8.3",
"xlsx": "^0.18.5",
"yaml": "^2.8.2",
"zod": "^4.2.1"
@ -43,7 +42,6 @@
"devDependencies": {
"@types/node": "^25.0.3",
"@types/papaparse": "^5.5.2",
"@types/pdf-parse": "^1.1.5",
"@types/shell-quote": "^1.7.5"
"@types/pdf-parse": "^1.1.5"
}
}

View file

@ -1,42 +1,42 @@
import { exec, execSync, spawn, ChildProcess } from 'child_process';
import { promisify } from 'util';
import { parse } from 'shell-quote';
import { getSecurityAllowList } from '../../config/security.js';
const execPromise = promisify(exec);
const ENV_ASSIGNMENT_REGEX = /^[A-Za-z_][A-Za-z0-9_]*=/;
const COMMAND_SPLIT_REGEX = /(?:\|\||&&|;|\||\n|`|\$\(|\(|\))/;
const ENV_ASSIGNMENT_REGEX = /^[A-Za-z_][A-Za-z0-9_]*=.*/;
const WRAPPER_COMMANDS = new Set(['sudo', 'env', 'time', 'command']);
function sanitizeToken(token: string): string {
return token.trim().replace(/^['"()]+|['"()]+$/g, '');
}
export function extractCommandNames(command: string): string[] {
const discovered = new Set<string>();
const tokens = parse(command);
const segments = command.split(COMMAND_SPLIT_REGEX);
let expectCommand = true;
for (const token of tokens) {
// Operator tokens (|, &&, ;, ||, etc.) reset the "expect command" flag
if (typeof token === 'object' && token !== null && 'op' in token) {
expectCommand = true;
continue;
for (const segment of segments) {
const tokens = segment.trim().split(/\s+/).filter(Boolean);
if (!tokens.length) continue;
let index = 0;
while (index < tokens.length && ENV_ASSIGNMENT_REGEX.test(tokens[index])) {
index++;
}
if (typeof token !== 'string') continue;
if (index >= tokens.length) continue;
if (!expectCommand) continue;
const primary = sanitizeToken(tokens[index]).toLowerCase();
if (!primary) continue;
// Skip env assignments (VAR=val)
if (ENV_ASSIGNMENT_REGEX.test(token)) continue;
discovered.add(primary);
const name = token.toLowerCase();
if (!name) continue;
discovered.add(name);
if (WRAPPER_COMMANDS.has(name)) {
// Don't mark command as found yet — the next token is the real command
continue;
if (WRAPPER_COMMANDS.has(primary) && index + 1 < tokens.length) {
const wrapped = sanitizeToken(tokens[index + 1]).toLowerCase();
if (wrapped) {
discovered.add(wrapped);
}
}
expectCommand = false;
}
return Array.from(discovered);