mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
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:
parent
bfba0f0682
commit
d9afc076cc
3 changed files with 24 additions and 37 deletions
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
11
apps/x/pnpm-lock.yaml
generated
11
apps/x/pnpm-lock.yaml
generated
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue