fix: align shell allowlist handling (#11510) (#11813)

This commit is contained in:
cornmander
2025-10-23 16:55:01 -04:00
committed by GitHub
parent 48ff9e1555
commit 5e70a7dd46
3 changed files with 76 additions and 5 deletions
+12
View File
@@ -480,6 +480,18 @@ describe('ShellTool', () => {
invocation.shouldConfirmExecute(new AbortController().signal),
).rejects.toThrow('wc');
});
it('should require all segments of a chained command to be allowlisted', async () => {
(mockConfig.getAllowedTools as Mock).mockReturnValue([
'ShellTool(echo)',
]);
const invocation = shellTool.build({ command: 'echo "foo" && ls -l' });
await expect(
invocation.shouldConfirmExecute(new AbortController().signal),
).rejects.toThrow(
'Command "echo "foo" && ls -l" is not in the list of allowed tools for non-interactive mode.',
);
});
});
});
+13 -5
View File
@@ -9,6 +9,7 @@ import path from 'node:path';
import os, { EOL } from 'node:os';
import crypto from 'node:crypto';
import type { Config } from '../config/config.js';
import type { AnyToolInvocation } from '../index.js';
import { ToolErrorType } from './tool-error.js';
import type {
ToolInvocation,
@@ -36,10 +37,9 @@ import {
getCommandRoots,
initializeShellParsers,
isCommandAllowed,
SHELL_TOOL_NAMES,
isShellInvocationAllowlisted,
stripShellWrapper,
} from '../utils/shell-utils.js';
import { doesToolInvocationMatch } from '../utils/tool-utils.js';
import { SHELL_TOOL_NAME } from './tool-names.js';
export const OUTPUT_UPDATE_INTERVAL_MS = 1000;
@@ -90,9 +90,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
!this.config.isInteractive() &&
this.config.getApprovalMode() !== ApprovalMode.YOLO
) {
const allowedTools = this.config.getAllowedTools() || [];
const [SHELL_TOOL_NAME] = SHELL_TOOL_NAMES;
if (doesToolInvocationMatch(SHELL_TOOL_NAME, command, allowedTools)) {
if (this.isInvocationAllowlisted(command)) {
// If it's an allowed shell command, we don't need to confirm execution.
return false;
}
@@ -324,6 +322,16 @@ export class ShellToolInvocation extends BaseToolInvocation<
}
}
}
private isInvocationAllowlisted(command: string): boolean {
const allowedTools = this.config.getAllowedTools() || [];
if (allowedTools.length === 0) {
return false;
}
const invocation = { params: { command } } as unknown as AnyToolInvocation;
return isShellInvocationAllowlisted(invocation, allowedTools);
}
}
function getShellToolDescription(): string {