diff options
| author | Yuki Okita <[email protected]> | 2025-07-07 14:03:36 +0900 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-07 05:03:36 +0000 |
| commit | 87a44ec4689b352a16467701e4bff9894c5afb84 (patch) | |
| tree | c91bc4f989c3fb20d9afb7bb7fae7a2e72fdac6b /packages/core/src/tools/shell.ts | |
| parent | 20825e41148a58998a6b1e5869149eac8d09cfbd (diff) | |
feat(core): improve error messages in isCommandAllowed (#3349)
Diffstat (limited to 'packages/core/src/tools/shell.ts')
| -rw-r--r-- | packages/core/src/tools/shell.ts | 54 |
1 files changed, 42 insertions, 12 deletions
diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index f0a30a9c..4954e055 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -112,12 +112,23 @@ Process Group PGID: Process group started or \`(none)\``, * the tool's configuration including allowlists and blocklists. * * @param command The shell command string to validate - * @returns True if the command is allowed to execute, false otherwise + * @returns An object with 'allowed' boolean and optional 'reason' string if not allowed */ - isCommandAllowed(command: string): boolean { + isCommandAllowed(command: string): { allowed: boolean; reason?: string } { // 0. Disallow command substitution - if (command.includes('$(') || command.includes('`')) { - return false; + if (command.includes('$(')) { + return { + allowed: false, + reason: + 'Command substitution using $() is not allowed for security reasons', + }; + } + if (command.includes('`')) { + return { + allowed: false, + reason: + 'Command substitution using backticks is not allowed for security reasons', + }; } const SHELL_TOOL_NAMES = [ShellTool.name, ShellTool.Name]; @@ -157,7 +168,10 @@ Process Group PGID: Process group started or \`(none)\``, // 1. Check if the shell tool is globally disabled. if (SHELL_TOOL_NAMES.some((name) => excludeTools.includes(name))) { - return false; + return { + allowed: false, + reason: 'Shell tool is globally disabled in configuration', + }; } const blockedCommands = new Set(extractCommands(excludeTools)); @@ -170,35 +184,51 @@ Process Group PGID: Process group started or \`(none)\``, const commandsToValidate = command.split(/&&|\|\||\||;/).map(normalize); + const blockedCommandsArr = [...blockedCommands]; + for (const cmd of commandsToValidate) { // 2. Check if the command is on the blocklist. - const isBlocked = [...blockedCommands].some((blocked) => + const isBlocked = blockedCommandsArr.some((blocked) => isPrefixedBy(cmd, blocked), ); if (isBlocked) { - return false; + return { + allowed: false, + reason: `Command '${cmd}' is blocked by configuration`, + }; } // 3. If in strict allow-list mode, check if the command is permitted. const isStrictAllowlist = hasSpecificAllowedCommands && !isWildcardAllowed; + const allowedCommandsArr = [...allowedCommands]; if (isStrictAllowlist) { - const isAllowed = [...allowedCommands].some((allowed) => + const isAllowed = allowedCommandsArr.some((allowed) => isPrefixedBy(cmd, allowed), ); if (!isAllowed) { - return false; + return { + allowed: false, + reason: `Command '${cmd}' is not in the allowed commands list`, + }; } } } // 4. If all checks pass, the command is allowed. - return true; + return { allowed: true }; } validateToolParams(params: ShellToolParams): string | null { - if (!this.isCommandAllowed(params.command)) { - return `Command is not allowed: ${params.command}`; + const commandCheck = this.isCommandAllowed(params.command); + if (!commandCheck.allowed) { + if (!commandCheck.reason) { + console.error( + 'Unexpected: isCommandAllowed returned false without a reason', + ); + return `Command is not allowed: ${params.command}`; + } + return commandCheck.reason; } if ( !SchemaValidator.validate( |
