High severityNVD Advisory· Published Mar 19, 2026· Updated Mar 20, 2026
OpenClaw < 2026.2.23 - Sandbox Bypass in apply_patch Tool via Workspace-Only Check Bypass
CVE-2026-32007
Description
OpenClaw versions prior to 2026.2.23 contain a path traversal vulnerability in the experimental apply_patch tool that allows attackers with sandbox access to modify files outside the workspace directory by exploiting inconsistent enforcement of workspace-only checks on mounted paths. Attackers can use apply_patch operations on writable mounts outside the workspace root to access and modify arbitrary files on the system.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.2.23 | 2026.2.23 |
Affected products
1Patches
16634030be31efix: enforce apply_patch workspaceOnly in sandbox mounts
3 files changed · +83 −0
CHANGELOG.md+1 −0 modified@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai - Security/Commands: enforce sender-only matching for `commands.allowFrom` by blocking conversation-shaped `From` identities (`channel:`, `group:`, `thread:`, `@g.us`) while preserving direct-message fallback when sender fields are missing. Ships in the next npm release. Thanks @jiseoung. - Config/Kilo Gateway: Kilo provider flow now surfaces an updated list of models. (#24921) thanks @gumadeiras. +- Security/Sandbox: enforce `tools.exec.applyPatch.workspaceOnly` and `tools.fs.workspaceOnly` for `apply_patch` in sandbox-mounted paths so writes/deletes cannot escape the workspace boundary via mounts like `/agent` unless explicitly opted out (`tools.exec.applyPatch.workspaceOnly=false`). This ships in the next npm release. Thanks @tdjackey for reporting. - Security/Config writes: block reserved prototype keys in account-id normalization and route account config resolution through own-key lookups, hardening `/allowlist` and account-scoped config paths against prototype-chain pollution. - Security/Exec: harden `safeBins` long-option validation by rejecting unknown/ambiguous GNU long-option abbreviations and denying sort filesystem-dependent flags (`--random-source`, `--temporary-directory`, `-T`), closing safe-bin denylist bypasses. Thanks @jiseoung. - Security/Channels: unify dangerous name-matching policy checks (`dangerouslyAllowNameMatching`) across core and extension channels, share mutable-allowlist detectors between `openclaw doctor` and `openclaw security audit`, and scan all configured accounts (not only the default account) in channel security audit findings.
src/agents/apply-patch.ts+8 −0 modified@@ -260,6 +260,14 @@ async function resolvePatchPath( filePath, cwd: options.cwd, }); + if (options.workspaceOnly !== false) { + await assertSandboxPath({ + filePath: resolved.hostPath, + cwd: options.cwd, + root: options.cwd, + allowFinalSymlink: purpose === "unlink", + }); + } return { resolved: resolved.hostPath, display: resolved.relativePath || resolved.hostPath,
src/agents/pi-tools.sandbox-mounted-paths.workspace-only.test.ts+74 −0 modified@@ -73,6 +73,10 @@ function createSandbox(params: { }); } +type ToolWithExecute = { + execute: (toolCallId: string, args: unknown, signal?: AbortSignal) => Promise<unknown>; +}; + async function withUnsafeMountedSandboxHarness( run: (ctx: { sandboxRoot: string; agentRoot: string; sandbox: SandboxContext }) => Promise<void>, ) { @@ -131,4 +135,74 @@ describe("tools.fs.workspaceOnly", () => { expect(await fs.readFile(path.join(agentRoot, "secret.txt"), "utf8")).toBe("shh"); }); }); + + it("enforces apply_patch workspace-only in sandbox mounts by default", async () => { + await withUnsafeMountedSandboxHarness(async ({ sandboxRoot, agentRoot, sandbox }) => { + const cfg: OpenClawConfig = { + tools: { + allow: ["read", "exec"], + exec: { applyPatch: { enabled: true } }, + }, + }; + const tools = createOpenClawCodingTools({ + sandbox, + workspaceDir: sandboxRoot, + config: cfg, + modelProvider: "openai", + modelId: "gpt-5.2", + }); + const applyPatchTool = tools.find((t) => t.name === "apply_patch") as + | ToolWithExecute + | undefined; + if (!applyPatchTool) { + throw new Error("apply_patch tool missing"); + } + + const patch = `*** Begin Patch +*** Add File: /agent/pwned.txt ++owned-by-apply-patch +*** End Patch`; + + await expect(applyPatchTool.execute("t1", { input: patch })).rejects.toThrow( + /Path escapes sandbox root/i, + ); + await expect(fs.stat(path.join(agentRoot, "pwned.txt"))).rejects.toMatchObject({ + code: "ENOENT", + }); + }); + }); + + it("allows apply_patch outside workspace root when explicitly disabled", async () => { + await withUnsafeMountedSandboxHarness(async ({ sandboxRoot, agentRoot, sandbox }) => { + const cfg: OpenClawConfig = { + tools: { + allow: ["read", "exec"], + exec: { applyPatch: { enabled: true, workspaceOnly: false } }, + }, + }; + const tools = createOpenClawCodingTools({ + sandbox, + workspaceDir: sandboxRoot, + config: cfg, + modelProvider: "openai", + modelId: "gpt-5.2", + }); + const applyPatchTool = tools.find((t) => t.name === "apply_patch") as + | ToolWithExecute + | undefined; + if (!applyPatchTool) { + throw new Error("apply_patch tool missing"); + } + + const patch = `*** Begin Patch +*** Add File: /agent/pwned.txt ++owned-by-apply-patch +*** End Patch`; + + await applyPatchTool.execute("t2", { input: patch }); + expect(await fs.readFile(path.join(agentRoot, "pwned.txt"), "utf8")).toBe( + "owned-by-apply-patch\n", + ); + }); + }); });
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/openclaw/openclaw/commit/6634030be31e1a1842967df046c2f2e47490e6bfghsapatchWEB
- github.com/advisories/GHSA-h9xm-j4qg-fvpgghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-h9xm-j4qg-fvpgghsathird-party-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-32007ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-sandbox-bypass-in-apply-patch-tool-via-workspace-only-check-bypassghsathird-party-advisoryWEB
News mentions
0No linked articles in our index yet.