From 8da9d8c55fb74e7a4d3a3fe8288e458682b66d55 Mon Sep 17 00:00:00 2001 From: HCL Date: Sun, 3 May 2026 23:06:02 +0800 Subject: [PATCH] fix(tool-policy): deny write no longer silently hides apply_patch (#76749) Removing deny coupling between write and apply_patch in makeToolPolicyMatcher. Previously deny: ["write"] would also block apply_patch, contradicting the documented allow coupling (apply_patch inherits write allow). Allow inheritance is preserved; deny must be stated explicitly. Adds regression tests verifying: - deny: ["write"] leaves apply_patch accessible - deny: ["apply_patch"] still denies directly - allow: ["write"] still grants apply_patch - deny: ["write", "apply_patch"] still denies when both explicit Co-Authored-By: Claude Sonnet 4.6 --- src/agents/tool-policy-match.ts | 3 --- src/agents/tool-policy.test.ts | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/agents/tool-policy-match.ts b/src/agents/tool-policy-match.ts index 01108246b4e..d0b91c817a9 100644 --- a/src/agents/tool-policy-match.ts +++ b/src/agents/tool-policy-match.ts @@ -16,9 +16,6 @@ function makeToolPolicyMatcher(policy: SandboxToolPolicy) { if (matchesAnyGlobPattern(normalized, deny)) { return false; } - if (normalized === "apply_patch" && matchesAnyGlobPattern("write", deny)) { - return false; - } if (allow.length === 0) { return true; } diff --git a/src/agents/tool-policy.test.ts b/src/agents/tool-policy.test.ts index 98b1d756e87..912bad8829b 100644 --- a/src/agents/tool-policy.test.ts +++ b/src/agents/tool-policy.test.ts @@ -3,6 +3,7 @@ import type { OpenClawConfig } from "../config/config.js"; import { DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "../security/dangerous-tools.js"; import { isToolAllowed, resolveSandboxToolPolicyForAgent } from "./sandbox/tool-policy.js"; import type { SandboxToolPolicy } from "./sandbox/types.js"; +import { isToolAllowedByPolicyName } from "./tool-policy-match.js"; import { TOOL_POLICY_CONFORMANCE } from "./tool-policy.conformance.js"; import { applyOwnerOnlyToolPolicy, @@ -264,3 +265,23 @@ describe("resolveSandboxToolPolicyForAgent", () => { expect(resolved.deny).toEqual(["image"]); }); }); + +describe("isToolAllowedByPolicyName — apply_patch / write deny decoupling (#76749)", () => { + it("does not deny apply_patch when write is denied", () => { + expect(isToolAllowedByPolicyName("apply_patch", { deny: ["write"] })).toBe(true); + }); + + it("still denies apply_patch when apply_patch is explicitly denied", () => { + expect(isToolAllowedByPolicyName("apply_patch", { deny: ["apply_patch"] })).toBe(false); + }); + + it("still allows apply_patch via write in the allow list", () => { + expect(isToolAllowedByPolicyName("apply_patch", { allow: ["write"], deny: [] })).toBe(true); + }); + + it("denies apply_patch when both write and apply_patch are denied", () => { + expect(isToolAllowedByPolicyName("apply_patch", { deny: ["write", "apply_patch"] })).toBe( + false, + ); + }); +});