VYPR
Moderate severityNVD Advisory· Published Mar 23, 2026· Updated Mar 25, 2026

OpenClaw < 2026.3.7 - Sandbox Escape via /acp spawn Command

CVE-2026-27646

Description

OpenClaw versions prior to 2026.3.7 contain a sandbox escape vulnerability in the /acp spawn command that allows authorized sandboxed sessions to initialize host-side ACP runtime. Attackers can bypass sandbox restrictions by invoking the /acp spawn slash-command to cross from sandboxed chat context into host-side ACP session initialization when ACP is enabled.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.3.72026.3.7

Affected products

1

Patches

1
61000b8e4ded

fix(acp): block sandboxed slash spawns

https://github.com/openclaw/openclawPeter SteinbergerMar 8, 2026via ghsa
5 files changed · +56 15
  • CHANGELOG.md+1 0 modified
    @@ -158,6 +158,7 @@ Docs: https://docs.openclaw.ai
     - Discord/thread session lifecycle: reset thread-scoped sessions when a thread is archived so reopening a thread starts fresh without deleting transcript history. Thanks @thewilloftheshadow.
     - Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow.
     - Discord/typing cleanup: stop typing indicators after silent/NO_REPLY runs by marking the run complete before dispatch idle cleanup. Thanks @thewilloftheshadow.
    +- ACP/sandbox spawn parity: block `/acp spawn` from sandboxed requester sessions with the same host-runtime guard already enforced for `sessions_spawn({ runtime: "acp" })`, preserving non-sandbox ACP flows while closing the command-path policy gap. Thanks @patte.
     - Discord/config SecretRef typing: align Discord account token config typing with SecretInput so SecretRef tokens typecheck. (#32490) Thanks @scoootscooob.
     - Discord/voice messages: request upload slots with JSON fetch calls so voice message uploads no longer fail with content-type errors. Thanks @thewilloftheshadow.
     - Discord/voice decoder fallback: drop the native Opus dependency and use opusscript for voice decoding to avoid native-opus installs. Thanks @thewilloftheshadow.
    
  • docs/tools/acp-agents.md+1 1 modified
    @@ -252,7 +252,7 @@ ACP sessions currently run on the host runtime, not inside the OpenClaw sandbox.
     
     Current limitations:
     
    -- If the requester session is sandboxed, ACP spawns are blocked.
    +- If the requester session is sandboxed, ACP spawns are blocked for both `sessions_spawn({ runtime: "acp" })` and `/acp spawn`.
       - Error: `Sandboxed sessions cannot spawn ACP sessions because runtime="acp" runs on the host. Use runtime="subagent" from sandboxed sessions.`
     - `sessions_spawn` with `runtime: "acp"` does not support `sandbox: "require"`.
       - Error: `sessions_spawn sandbox="require" is unsupported for runtime="acp" because ACP sessions run outside the sandbox. Use runtime="subagent" or sandbox="inherit".`
    
  • src/agents/acp-spawn.ts+27 14 modified
    @@ -81,6 +81,27 @@ export const ACP_SPAWN_ACCEPTED_NOTE =
     export const ACP_SPAWN_SESSION_ACCEPTED_NOTE =
       "thread-bound ACP session stays active after this task; continue in-thread for follow-ups.";
     
    +export function resolveAcpSpawnRuntimePolicyError(params: {
    +  cfg: OpenClawConfig;
    +  requesterSessionKey?: string;
    +  requesterSandboxed?: boolean;
    +  sandbox?: SpawnAcpSandboxMode;
    +}): string | undefined {
    +  const sandboxMode = params.sandbox === "require" ? "require" : "inherit";
    +  const requesterRuntime = resolveSandboxRuntimeStatus({
    +    cfg: params.cfg,
    +    sessionKey: params.requesterSessionKey,
    +  });
    +  const requesterSandboxed = params.requesterSandboxed === true || requesterRuntime.sandboxed;
    +  if (requesterSandboxed) {
    +    return 'Sandboxed sessions cannot spawn ACP sessions because runtime="acp" runs on the host. Use runtime="subagent" from sandboxed sessions.';
    +  }
    +  if (sandboxMode === "require") {
    +    return 'sessions_spawn sandbox="require" is unsupported for runtime="acp" because ACP sessions run outside the sandbox. Use runtime="subagent" or sandbox="inherit".';
    +  }
    +  return undefined;
    +}
    +
     type PreparedAcpThreadBinding = {
       channel: string;
       accountId: string;
    @@ -242,7 +263,6 @@ export async function spawnAcpDirect(
           error: "ACP is disabled by policy (`acp.enabled=false`).",
         };
       }
    -  const sandboxMode = params.sandbox === "require" ? "require" : "inherit";
       const streamToParentRequested = params.streamTo === "parent";
       const parentSessionKey = ctx.agentSessionKey?.trim();
       if (streamToParentRequested && !parentSessionKey) {
    @@ -251,23 +271,16 @@ export async function spawnAcpDirect(
           error: 'sessions_spawn streamTo="parent" requires an active requester session context.',
         };
       }
    -  const requesterRuntime = resolveSandboxRuntimeStatus({
    +  const runtimePolicyError = resolveAcpSpawnRuntimePolicyError({
         cfg,
    -    sessionKey: ctx.agentSessionKey,
    +    requesterSessionKey: ctx.agentSessionKey,
    +    requesterSandboxed: ctx.sandboxed,
    +    sandbox: params.sandbox,
       });
    -  const requesterSandboxed = ctx.sandboxed === true || requesterRuntime.sandboxed;
    -  if (requesterSandboxed) {
    -    return {
    -      status: "forbidden",
    -      error:
    -        'Sandboxed sessions cannot spawn ACP sessions because runtime="acp" runs on the host. Use runtime="subagent" from sandboxed sessions.',
    -    };
    -  }
    -  if (sandboxMode === "require") {
    +  if (runtimePolicyError) {
         return {
           status: "forbidden",
    -      error:
    -        'sessions_spawn sandbox="require" is unsupported for runtime="acp" because ACP sessions run outside the sandbox. Use runtime="subagent" or sandbox="inherit".',
    +      error: runtimePolicyError,
         };
       }
     
    
  • src/auto-reply/reply/commands-acp/lifecycle.ts+8 0 modified
    @@ -15,6 +15,7 @@ import {
       resolveAcpSessionCwd,
       resolveAcpThreadSessionDetailLines,
     } from "../../../acp/runtime/session-identifiers.js";
    +import { resolveAcpSpawnRuntimePolicyError } from "../../../agents/acp-spawn.js";
     import {
       resolveThreadBindingIntroText,
       resolveThreadBindingThreadName,
    @@ -253,6 +254,13 @@ export async function handleAcpSpawnAction(
       }
     
       const spawn = parsed.value;
    +  const runtimePolicyError = resolveAcpSpawnRuntimePolicyError({
    +    cfg: params.cfg,
    +    requesterSessionKey: params.sessionKey,
    +  });
    +  if (runtimePolicyError) {
    +    return stopWithText(`⚠️ ${runtimePolicyError}`);
    +  }
       const agentPolicyError = resolveAcpAgentPolicyError(params.cfg, spawn.agentId);
       if (agentPolicyError) {
         return stopWithText(
    
  • src/auto-reply/reply/commands-acp.test.ts+19 0 modified
    @@ -592,6 +592,25 @@ describe("/acp command", () => {
         );
       });
     
    +  it("forbids /acp spawn from sandboxed requester sessions", async () => {
    +    const cfg = {
    +      ...baseCfg,
    +      agents: {
    +        defaults: {
    +          sandbox: { mode: "all" },
    +        },
    +      },
    +    } satisfies OpenClawConfig;
    +
    +    const result = await runDiscordAcpCommand("/acp spawn codex", cfg);
    +
    +    expect(result?.reply?.text).toContain("Sandboxed sessions cannot spawn ACP sessions");
    +    expect(hoisted.requireAcpRuntimeBackendMock).not.toHaveBeenCalled();
    +    expect(hoisted.ensureSessionMock).not.toHaveBeenCalled();
    +    expect(hoisted.sessionBindingBindMock).not.toHaveBeenCalled();
    +    expect(hoisted.callGatewayMock).not.toHaveBeenCalled();
    +  });
    +
       it("cancels the ACP session bound to the current thread", async () => {
         mockBoundThreadSession({ state: "running" });
         const result = await runThreadAcpCommand("/acp cancel", baseCfg);
    

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

6

News mentions

0

No linked articles in our index yet.