diff --git a/apps/x/packages/core/package.json b/apps/x/packages/core/package.json index 4ea4331c..bcfac93d 100644 --- a/apps/x/packages/core/package.json +++ b/apps/x/packages/core/package.json @@ -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" } } diff --git a/apps/x/packages/core/src/application/lib/command-executor.ts b/apps/x/packages/core/src/application/lib/command-executor.ts index c8ac67c0..870f6a0c 100644 --- a/apps/x/packages/core/src/application/lib/command-executor.ts +++ b/apps/x/packages/core/src/application/lib/command-executor.ts @@ -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(); - 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); diff --git a/apps/x/pnpm-lock.yaml b/apps/x/pnpm-lock.yaml index ac5e5d55..fa8765d6 100644 --- a/apps/x/pnpm-lock.yaml +++ b/apps/x/pnpm-lock.yaml @@ -380,9 +380,6 @@ importers: react: specifier: ^19.2.3 version: 19.2.3 - shell-quote: - specifier: ^1.8.3 - version: 1.8.3 xlsx: specifier: ^0.18.5 version: 0.18.5 @@ -402,9 +399,6 @@ importers: '@types/pdf-parse': specifier: ^1.1.5 version: 1.1.5 - '@types/shell-quote': - specifier: ^1.7.5 - version: 1.7.5 packages/shared: dependencies: @@ -3352,9 +3346,6 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/shell-quote@1.7.5': - resolution: {integrity: sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -11114,8 +11105,6 @@ snapshots: dependencies: '@types/node': 25.0.3 - '@types/shell-quote@1.7.5': {} - '@types/trusted-types@2.0.7': optional: true