fix(shell): handle pgrep review cases

This commit is contained in:
yiliang114 2026-07-08 22:36:14 +08:00
parent abeafce818
commit a74d45dde4
2 changed files with 94 additions and 4 deletions

View file

@ -609,7 +609,13 @@ describe('detectSelfKillCommand', () => {
it('detects kill commands using pgrep selectors for qwen-code hosts', () => {
expect(detectSelfKillCommand('kill -9 $(pgrep node)')).toBe(true);
expect(detectSelfKillCommand('kill $(pgrep -f node)')).toBe(true);
expect(detectSelfKillCommand('kill -9 $(pgrep node | head -1)')).toBe(true);
expect(detectSelfKillCommand('kill -9 `pgrep node | head -1`')).toBe(true);
expect(detectSelfKillCommand('pgrep node | xargs kill')).toBe(true);
expect(detectSelfKillCommand('pgrep node | xargs sudo kill')).toBe(true);
expect(detectSelfKillCommand('pgrep node | xargs -I {} kill -9 {}')).toBe(
true,
);
});
it('detects taskkill inline and dash-prefixed image options', () => {

View file

@ -208,6 +208,8 @@ export function splitCommands(command: string): string[] {
let currentCommand = '';
let inSingleQuotes = false;
let inDoubleQuotes = false;
let inBackticks = false;
let substitutionDepth = 0;
let i = 0;
const previousNonWhitespaceChar = (index: number): string | undefined => {
@ -235,13 +237,42 @@ export function splitCommands(command: string): string[] {
continue;
}
if (char === "'" && !inDoubleQuotes) {
if (!inSingleQuotes && char === '`') {
inBackticks = !inBackticks;
} else if (
!inSingleQuotes &&
!inBackticks &&
char === '$' &&
nextChar === '('
) {
substitutionDepth++;
currentCommand += '$(';
i += 2;
continue;
} else if (!inBackticks && substitutionDepth > 0 && char === ')') {
substitutionDepth--;
} else if (
!inBackticks &&
substitutionDepth === 0 &&
char === "'" &&
!inDoubleQuotes
) {
inSingleQuotes = !inSingleQuotes;
} else if (char === '"' && !inSingleQuotes) {
} else if (
!inBackticks &&
substitutionDepth === 0 &&
char === '"' &&
!inSingleQuotes
) {
inDoubleQuotes = !inDoubleQuotes;
}
if (!inSingleQuotes && !inDoubleQuotes) {
if (
!inSingleQuotes &&
!inDoubleQuotes &&
!inBackticks &&
substitutionDepth === 0
) {
if (
(char === '&' && nextChar === '&') ||
(char === '|' && (nextChar === '|' || nextChar === '&'))
@ -460,6 +491,28 @@ const PKILL_OPTIONS_WITH_VALUES = new Set([
'--euid',
]);
const XARGS_OPTIONS_WITH_VALUES = new Set([
'-I',
'-n',
'-P',
'-s',
'-E',
'-d',
'-L',
'-l',
'-a',
'-J',
'-R',
'--replace',
'--max-args',
'--max-procs',
'--max-chars',
'--eof',
'--delimiter',
'--max-lines',
'--arg-file',
]);
export const SHELL_SELF_KILL_REJECTION =
'Blocked: this command may terminate the running qwen-code process because it targets all node/qwen-code processes. Use task_stop for managed background shells, or kill a specific PID instead.';
@ -773,6 +826,34 @@ function pgrepSubstitutionArgs(segment: string): string[] {
continue;
}
if (char === '`') {
let end = index + 1;
let innerEscaped = false;
for (; end < segment.length; end++) {
const innerChar = segment[end]!;
if (innerEscaped) {
innerEscaped = false;
continue;
}
if (innerChar === '\\') {
innerEscaped = true;
continue;
}
if (innerChar === '`') break;
}
if (end >= segment.length) {
break;
}
const inner = segment.slice(index + 1, end).trim();
const match = inner.match(/^pgrep\b(.*)$/i);
if (match) {
args.push(match[1] ?? '');
}
index = end;
continue;
}
if (char !== '$' || segment[index + 1] !== '(') {
continue;
}
@ -813,7 +894,10 @@ function xargsInvokesKill(segment: string | undefined): boolean {
return false;
}
const command = tokens.slice(1).find((token) => !isOptionToken(token));
const commandTokens = unwrapExecutionPrefixes(
commandArguments(tokens, XARGS_OPTIONS_WITH_VALUES),
);
const command = commandTokens.find((token) => !isOptionToken(token));
return normalizeExecutableName(command ?? '') === 'kill';
}