From 5290c5145172ef3979b4943b30bfee3a321f189a Mon Sep 17 00:00:00 2001 From: Abhijit Balaji Date: Tue, 5 May 2026 13:57:23 -0700 Subject: [PATCH] fix(core): resolve policy engine bugs affecting tool approvals Fixes #24772, #16970. - Fixes regex null-byte mismatch in `buildParamArgsPattern` so "always allow" works. - Removes sandbox requirement from `shouldDowngradeForRedirection` so YOLO/AUTO_EDIT modes behave correctly. - Enhances `stripShellWrapper` to recognize generic shell scripts (e.g., `sh script.sh`). --- packages/core/src/policy/policy-engine.ts | 14 ++++++++------ packages/core/src/policy/utils.ts | 4 ++-- packages/core/src/utils/shell-utils.ts | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/core/src/policy/policy-engine.ts b/packages/core/src/policy/policy-engine.ts index a3b9aa0992..27a182682b 100644 --- a/packages/core/src/policy/policy-engine.ts +++ b/packages/core/src/policy/policy-engine.ts @@ -288,12 +288,14 @@ export class PolicyEngine { if (allowRedirection) return false; if (!hasRedirection(command)) return false; - // Do not downgrade (do not ask user) if in AUTO_EDIT or YOLO mode. - // These modes trust the agent's actions (YOLO) or specific task (AUTO_EDIT). - if ( - this.approvalMode === ApprovalMode.AUTO_EDIT || - this.approvalMode === ApprovalMode.YOLO - ) { + // In YOLO mode, never downgrade. + if (this.approvalMode === ApprovalMode.YOLO) { + return false; + } + + // In AUTO_EDIT mode, only bypass downgrade if sandboxing is enabled. + const sandboxEnabled = !(this.sandboxManager instanceof NoopSandboxManager); + if (this.approvalMode === ApprovalMode.AUTO_EDIT && sandboxEnabled) { return false; } diff --git a/packages/core/src/policy/utils.ts b/packages/core/src/policy/utils.ts index 3c7bd4d16b..fe34676266 100644 --- a/packages/core/src/policy/utils.ts +++ b/packages/core/src/policy/utils.ts @@ -102,10 +102,10 @@ export function buildParamArgsPattern( value: unknown, ): string { const encodedValue = JSON.stringify(value); - // We wrap the JSON string in escapeRegex and prepend/append \\0 to explicitly + // We wrap the JSON string in escapeRegex and prepend/append \\x00 to explicitly // match top-level JSON properties generated by stableStringify, preventing // argument injection bypass attacks. - return `\\\\0${escapeRegex(`"${paramName}":${encodedValue}`)}\\\\0`; + return `\\x00${escapeRegex(`"${paramName}":${encodedValue}`)}\\x00`; } /** diff --git a/packages/core/src/utils/shell-utils.ts b/packages/core/src/utils/shell-utils.ts index a14b28227f..09d2843c40 100644 --- a/packages/core/src/utils/shell-utils.ts +++ b/packages/core/src/utils/shell-utils.ts @@ -809,11 +809,11 @@ export function getCommandRoots(command: string): string[] { } export function stripShellWrapper(command: string): string { - const pattern = + const cFlagPattern = /^\s*(?:(?:(?:\S+\/)?(?:sh|bash|zsh))\s+-c|cmd\.exe\s+\/c|powershell(?:\.exe)?\s+(?:-NoProfile\s+)?-Command|pwsh(?:\.exe)?\s+(?:-NoProfile\s+)?-Command)\s+/i; - const match = command.match(pattern); - if (match) { - let newCommand = command.substring(match[0].length).trim(); + const cFlagMatch = command.match(cFlagPattern); + if (cFlagMatch) { + let newCommand = command.substring(cFlagMatch[0].length).trim(); if ( (newCommand.startsWith('"') && newCommand.endsWith('"')) || (newCommand.startsWith("'") && newCommand.endsWith("'")) @@ -822,6 +822,14 @@ export function stripShellWrapper(command: string): string { } return newCommand; } + + const scriptPattern = + /^\s*(?:(?:\S+\/)?(?:sh|bash|zsh))\s+([a-zA-Z0-9_\-./]+\.sh)\s*$/i; + const scriptMatch = command.match(scriptPattern); + if (scriptMatch) { + return scriptMatch[1]; + } + return command.trim(); }