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`).
This commit is contained in:
Abhijit Balaji
2026-05-05 13:57:23 -07:00
parent 3627f4777f
commit 5290c51451
3 changed files with 22 additions and 12 deletions
+8 -6
View File
@@ -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;
}
+2 -2
View File
@@ -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`;
}
/**
+12 -4
View File
@@ -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();
}