VYPR
Moderate severityNVD Advisory· Published Mar 19, 2026· Updated Mar 20, 2026

OpenClaw - Sandbox Network Isolation Bypass via docker.network=container Parameter

CVE-2026-32038

Description

OpenClaw before 2026.2.24 contains a sandbox network isolation bypass vulnerability that allows trusted operators to join another container's network namespace. Attackers can configure the docker.network parameter with container:<id> values to reach services in target container namespaces and bypass network hardening controls.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.2.242026.2.24

Affected products

1

Patches

2
5552f9073

refactor(sandbox): centralize network mode policy helpers

https://github.com/openclaw/openclawPeter SteinbergerFeb 24, 2026via ghsa
7 files changed · +78 19
  • src/agents/sandbox/network-mode.ts+28 0 added
    @@ -0,0 +1,28 @@
    +export type NetworkModeBlockReason = "host" | "container_namespace_join";
    +
    +export function normalizeNetworkMode(network: string | undefined): string | undefined {
    +  const normalized = network?.trim().toLowerCase();
    +  return normalized || undefined;
    +}
    +
    +export function getBlockedNetworkModeReason(params: {
    +  network: string | undefined;
    +  allowContainerNamespaceJoin?: boolean;
    +}): NetworkModeBlockReason | null {
    +  const normalized = normalizeNetworkMode(params.network);
    +  if (!normalized) {
    +    return null;
    +  }
    +  if (normalized === "host") {
    +    return "host";
    +  }
    +  if (normalized.startsWith("container:") && params.allowContainerNamespaceJoin !== true) {
    +    return "container_namespace_join";
    +  }
    +  return null;
    +}
    +
    +export function isDangerousNetworkMode(network: string | undefined): boolean {
    +  const normalized = normalizeNetworkMode(network);
    +  return normalized === "host" || normalized?.startsWith("container:") === true;
    +}
    
  • src/agents/sandbox/validate-sandbox-security.ts+7 8 modified
    @@ -11,6 +11,7 @@ import {
       normalizeSandboxHostPath,
       resolveSandboxHostPathViaExistingAncestor,
     } from "./host-paths.js";
    +import { getBlockedNetworkModeReason } from "./network-mode.js";
     
     // Targeted denylist: host paths that should never be exposed inside sandbox containers.
     // Exported for reuse in security audit collectors.
    @@ -31,7 +32,6 @@ export const BLOCKED_HOST_PATHS = [
       "/run/docker.sock",
     ];
     
    -const BLOCKED_NETWORK_MODES = new Set(["host"]);
     const BLOCKED_SECCOMP_PROFILES = new Set(["unconfined"]);
     const BLOCKED_APPARMOR_PROFILES = new Set(["unconfined"]);
     const RESERVED_CONTAINER_TARGET_PATHS = ["/workspace", SANDBOX_AGENT_WORKSPACE_MOUNT];
    @@ -284,20 +284,19 @@ export function validateNetworkMode(
       network: string | undefined,
       options?: ValidateNetworkModeOptions,
     ): void {
    -  const normalized = network?.trim().toLowerCase();
    -  if (!normalized) {
    -    return;
    -  }
    -
    -  if (BLOCKED_NETWORK_MODES.has(normalized)) {
    +  const blockedReason = getBlockedNetworkModeReason({
    +    network,
    +    allowContainerNamespaceJoin: options?.allowContainerNamespaceJoin,
    +  });
    +  if (blockedReason === "host") {
         throw new Error(
           `Sandbox security: network mode "${network}" is blocked. ` +
             'Network "host" mode bypasses container network isolation. ' +
             'Use "bridge" or "none" instead.',
         );
       }
     
    -  if (normalized.startsWith("container:") && options?.allowContainerNamespaceJoin !== true) {
    +  if (blockedReason === "container_namespace_join") {
         throw new Error(
           `Sandbox security: network mode "${network}" is blocked by default. ` +
             'Network "container:*" joins another container namespace and bypasses sandbox network isolation. ' +
    
  • src/config/config.sandbox-docker.test.ts+20 1 modified
    @@ -1,5 +1,8 @@
     import { describe, expect, it } from "vitest";
    -import { resolveSandboxBrowserConfig } from "../agents/sandbox/config.js";
    +import {
    +  resolveSandboxBrowserConfig,
    +  resolveSandboxDockerConfig,
    +} from "../agents/sandbox/config.js";
     import { validateConfigObject } from "./config.js";
     
     describe("sandbox docker config", () => {
    @@ -84,6 +87,22 @@ describe("sandbox docker config", () => {
         expect(res.ok).toBe(true);
       });
     
    +  it("uses agent override precedence for dangerouslyAllowContainerNamespaceJoin", () => {
    +    const inherited = resolveSandboxDockerConfig({
    +      scope: "agent",
    +      globalDocker: { dangerouslyAllowContainerNamespaceJoin: true },
    +      agentDocker: {},
    +    });
    +    expect(inherited.dangerouslyAllowContainerNamespaceJoin).toBe(true);
    +
    +    const overridden = resolveSandboxDockerConfig({
    +      scope: "agent",
    +      globalDocker: { dangerouslyAllowContainerNamespaceJoin: true },
    +      agentDocker: { dangerouslyAllowContainerNamespaceJoin: false },
    +    });
    +    expect(overridden.dangerouslyAllowContainerNamespaceJoin).toBe(false);
    +  });
    +
       it("rejects seccomp unconfined via Zod schema validation", () => {
         const res = validateConfigObject({
           agents: {
    
  • src/config/schema.help.ts+4 0 modified
    @@ -299,6 +299,10 @@ export const FIELD_HELP: Record<string, string> = {
       "agents.defaults.sandbox.browser.network":
         "Docker network for sandbox browser containers (default: openclaw-sandbox-browser). Avoid bridge if you need stricter isolation.",
       "agents.list[].sandbox.browser.network": "Per-agent override for sandbox browser Docker network.",
    +  "agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin":
    +    "DANGEROUS break-glass override that allows sandbox Docker network mode container:<id>. This joins another container namespace and weakens sandbox isolation.",
    +  "agents.list[].sandbox.docker.dangerouslyAllowContainerNamespaceJoin":
    +    "Per-agent DANGEROUS override for container namespace joins in sandbox Docker network mode.",
       "agents.defaults.sandbox.browser.cdpSourceRange":
         "Optional CIDR allowlist for container-edge CDP ingress (for example 172.21.0.1/32).",
       "agents.list[].sandbox.browser.cdpSourceRange":
    
  • src/config/schema.labels.ts+4 0 modified
    @@ -405,6 +405,8 @@ export const FIELD_LABELS: Record<string, string> = {
       "agents.defaults.heartbeat.suppressToolErrorWarnings": "Heartbeat Suppress Tool Error Warnings",
       "agents.defaults.sandbox.browser.network": "Sandbox Browser Network",
       "agents.defaults.sandbox.browser.cdpSourceRange": "Sandbox Browser CDP Source Port Range",
    +  "agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin":
    +    "Sandbox Docker Allow Container Namespace Join",
       commands: "Commands",
       "commands.native": "Native Commands",
       "commands.nativeSkills": "Native Skill Commands",
    @@ -713,6 +715,8 @@ export const FIELD_LABELS: Record<string, string> = {
         "Agent Heartbeat Suppress Tool Error Warnings",
       "agents.list[].sandbox.browser.network": "Agent Sandbox Browser Network",
       "agents.list[].sandbox.browser.cdpSourceRange": "Agent Sandbox Browser CDP Source Port Range",
    +  "agents.list[].sandbox.docker.dangerouslyAllowContainerNamespaceJoin":
    +    "Agent Sandbox Docker Allow Container Namespace Join",
       "discovery.mdns.mode": "mDNS Discovery Mode",
       plugins: "Plugins",
       "plugins.enabled": "Enable Plugins",
    
  • src/config/zod-schema.agent-runtime.ts+12 8 modified
    @@ -1,4 +1,5 @@
     import { z } from "zod";
    +import { getBlockedNetworkModeReason } from "../agents/sandbox/network-mode.js";
     import { parseDurationMs } from "../cli/parse-duration.js";
     import { AgentModelSchema } from "./zod-schema.agent-model.js";
     import {
    @@ -154,16 +155,19 @@ export const SandboxDockerSchema = z
             }
           }
         }
    -    const network = data.network?.trim().toLowerCase();
    -    if (network === "host") {
    +    const blockedNetworkReason = getBlockedNetworkModeReason({
    +      network: data.network,
    +      allowContainerNamespaceJoin: data.dangerouslyAllowContainerNamespaceJoin === true,
    +    });
    +    if (blockedNetworkReason === "host") {
           ctx.addIssue({
             code: z.ZodIssueCode.custom,
             path: ["network"],
             message:
               'Sandbox security: network mode "host" is blocked. Use "bridge" or "none" instead.',
           });
         }
    -    if (network?.startsWith("container:") && data.dangerouslyAllowContainerNamespaceJoin !== true) {
    +    if (blockedNetworkReason === "container_namespace_join") {
           ctx.addIssue({
             code: z.ZodIssueCode.custom,
             path: ["network"],
    @@ -476,11 +480,11 @@ export const AgentSandboxSchema = z
       })
       .strict()
       .superRefine((data, ctx) => {
    -    const browserNetwork = data.browser?.network?.trim().toLowerCase();
    -    if (
    -      browserNetwork?.startsWith("container:") &&
    -      data.docker?.dangerouslyAllowContainerNamespaceJoin !== true
    -    ) {
    +    const blockedBrowserNetworkReason = getBlockedNetworkModeReason({
    +      network: data.browser?.network,
    +      allowContainerNamespaceJoin: data.docker?.dangerouslyAllowContainerNamespaceJoin === true,
    +    });
    +    if (blockedBrowserNetworkReason === "container_namespace_join") {
           ctx.addIssue({
             code: z.ZodIssueCode.custom,
             path: ["browser", "network"],
    
  • src/security/audit-extra.sync.ts+3 2 modified
    @@ -3,6 +3,7 @@ import {
       resolveSandboxConfigForAgent,
       resolveSandboxToolPolicyForAgent,
     } from "../agents/sandbox.js";
    +import { isDangerousNetworkMode, normalizeNetworkMode } from "../agents/sandbox/network-mode.js";
     /**
      * Synchronous security audit collector functions.
      *
    @@ -830,8 +831,8 @@ export function collectSandboxDangerousConfigFindings(cfg: OpenClawConfig): Secu
         }
     
         const network = typeof docker.network === "string" ? docker.network : undefined;
    -    const normalizedNetwork = network?.trim().toLowerCase();
    -    if (normalizedNetwork === "host" || normalizedNetwork?.startsWith("container:")) {
    +    const normalizedNetwork = normalizeNetworkMode(network);
    +    if (isDangerousNetworkMode(network)) {
           const modeLabel = normalizedNetwork === "host" ? '"host"' : `"${network}"`;
           const detail =
             normalizedNetwork === "host"
    
14b6eea6e

feat(sandbox): block container namespace joins by default

https://github.com/openclaw/openclawPeter SteinbergerFeb 24, 2026via ghsa
17 files changed · +253 18
  • CHANGELOG.md+4 0 modified
    @@ -9,6 +9,10 @@ Docs: https://docs.openclaw.ai
     - Auto-reply/Abort shortcuts: expand standalone stop phrases (`stop openclaw`, `stop action`, `stop run`, `stop agent`, `please stop`, and related variants), accept trailing punctuation (for example `STOP OPENCLAW!!!`), and add multilingual stop keywords (including ES/FR/ZH/HI/AR/JP/DE/PT/RU forms) so emergency stop messages are caught more reliably. (#25103) Thanks @steipete and @vincentkoc.
     - Security/Audit: add `security.trust_model.multi_user_heuristic` to flag likely shared-user ingress and clarify the personal-assistant trust model, with hardening guidance for intentional multi-user setups (`sandbox.mode="all"`, workspace-scoped FS, reduced tool surface, no personal/private identities on shared runtimes).
     
    +### Breaking
    +
    +- **BREAKING:** Security/Sandbox: block Docker `network: "container:<id>"` namespace-join mode by default for sandbox and sandbox-browser containers. To keep that behavior intentionally, set `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true` (break-glass). Thanks @tdjackey for reporting.
    +
     ### Fixes
     
     - Routing/Session isolation: harden followup routing so explicit cross-channel origin replies never fall back to the active dispatcher on route failure, preserve queued overflow summary routing metadata (`channel`/`to`/`thread`) across followup drain, and prefer originating channel context over internal provider tags for embedded followup runs. This prevents webchat/control-ui context from hijacking Discord-targeted replies in shared sessions. (#25864) Thanks @Gamedesigner.
    
  • docs/cli/security.md+1 0 modified
    @@ -32,6 +32,7 @@ For webhook ingress, it warns when `hooks.defaultSessionKey` is unset, when requ
     It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries, when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed extension plugin tools may be reachable under permissive tool policy.
     It also flags `gateway.allowRealIpFallback=true` (header-spoofing risk if proxies are misconfigured) and `discovery.mdns.mode="full"` (metadata leakage via mDNS TXT records).
     It also warns when sandbox browser uses Docker `bridge` network without `sandbox.browser.cdpSourceRange`.
    +It also flags dangerous sandbox Docker network modes (including `host` and `container:*` namespace joins).
     It also warns when existing sandbox browser Docker containers have missing/stale hash labels (for example pre-migration containers missing `openclaw.browserConfigEpoch`) and recommends `openclaw sandbox recreate --browser --all`.
     It also warns when npm-based plugin/hook install records are unpinned, missing integrity metadata, or drift from currently installed package versions.
     It warns when channel allowlists rely on mutable names/emails/tags instead of stable IDs (Discord, Slack, Google Chat, MS Teams, Mattermost, IRC scopes where applicable).
    
  • docs/gateway/configuration-reference.md+3 1 modified
    @@ -1017,7 +1017,9 @@ Optional **Docker sandboxing** for the embedded agent. See [Sandboxing](/gateway
     
     **`setupCommand`** runs once after container creation (via `sh -lc`). Needs network egress, writable root, root user.
     
    -**Containers default to `network: "none"`** — set to `"bridge"` if the agent needs outbound access.
    +**Containers default to `network: "none"`** — set to `"bridge"` (or a custom bridge network) if the agent needs outbound access.
    +`"host"` is blocked. `"container:<id>"` is blocked by default unless you explicitly set
    +`sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true` (break-glass).
     
     **Inbound attachments** are staged into `media/inbound/*` in the active workspace.
     
    
  • docs/gateway/sandboxing.md+7 0 modified
    @@ -138,6 +138,12 @@ scripts/sandbox-browser-setup.sh
     By default, sandbox containers run with **no network**.
     Override with `agents.defaults.sandbox.docker.network`.
     
    +Security defaults:
    +
    +- `network: "host"` is blocked.
    +- `network: "container:<id>"` is blocked by default (namespace join bypass risk).
    +- Break-glass override: `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true`.
    +
     Docker installs and the containerized gateway live here:
     [Docker](/install/docker)
     
    @@ -154,6 +160,7 @@ Paths:
     Common pitfalls:
     
     - Default `docker.network` is `"none"` (no egress), so package installs will fail.
    +- `docker.network: "container:<id>"` requires `dangerouslyAllowContainerNamespaceJoin: true` and is break-glass only.
     - `readOnlyRoot: true` prevents writes; set `readOnlyRoot: false` or bake a custom image.
     - `user` must be root for package installs (omit `user` or set `user: "0:0"`).
     - Sandbox exec does **not** inherit host `process.env`. Use
    
  • docs/gateway/security/index.md+3 0 modified
    @@ -244,6 +244,7 @@ High-signal `checkId` values you will most likely see in real deployments (not e
     | `hooks.request_session_key_prefixes_missing`       | warn/critical | No bound on external session key shapes                                            | `hooks.allowedSessionKeyPrefixes`                                                                 | no       |
     | `logging.redact_off`                               | warn          | Sensitive values leak to logs/status                                               | `logging.redactSensitive`                                                                         | yes      |
     | `sandbox.docker_config_mode_off`                   | warn          | Sandbox Docker config present but inactive                                         | `agents.*.sandbox.mode`                                                                           | no       |
    +| `sandbox.dangerous_network_mode`                   | critical      | Sandbox Docker network uses `host` or `container:*` namespace-join mode            | `agents.*.sandbox.docker.network`                                                                 | no       |
     | `tools.exec.host_sandbox_no_sandbox_defaults`      | warn          | `exec host=sandbox` resolves to host exec when sandbox is off                      | `tools.exec.host`, `agents.defaults.sandbox.mode`                                                 | no       |
     | `tools.exec.host_sandbox_no_sandbox_agents`        | warn          | Per-agent `exec host=sandbox` resolves to host exec when sandbox is off            | `agents.list[].tools.exec.host`, `agents.list[].sandbox.mode`                                     | no       |
     | `tools.exec.safe_bins_interpreter_unprofiled`      | warn          | Interpreter/runtime bins in `safeBins` without explicit profiles broaden exec risk | `tools.exec.safeBins`, `tools.exec.safeBinProfiles`, `agents.list[].tools.exec.*`                 | no       |
    @@ -299,8 +300,10 @@ schema:
     - `channels.mattermost.accounts.<accountId>.dangerouslyAllowNameMatching` (extension channel)
     - `agents.defaults.sandbox.docker.dangerouslyAllowReservedContainerTargets`
     - `agents.defaults.sandbox.docker.dangerouslyAllowExternalBindSources`
    +- `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin`
     - `agents.list[<index>].sandbox.docker.dangerouslyAllowReservedContainerTargets`
     - `agents.list[<index>].sandbox.docker.dangerouslyAllowExternalBindSources`
    +- `agents.list[<index>].sandbox.docker.dangerouslyAllowContainerNamespaceJoin`
     
     ## Reverse Proxy Configuration
     
    
  • docs/install/docker.md+7 1 modified
    @@ -368,6 +368,8 @@ precedence, and troubleshooting.
       - `"rw"` mounts the agent workspace read/write at `/workspace`
     - Auto-prune: idle > 24h OR age > 7d
     - Network: `none` by default (explicitly opt-in if you need egress)
    +  - `host` is blocked.
    +  - `container:<id>` is blocked by default (namespace-join risk).
     - Default allow: `exec`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status`
     - Default deny: `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`
     
    @@ -376,6 +378,9 @@ precedence, and troubleshooting.
     If you plan to install packages in `setupCommand`, note:
     
     - Default `docker.network` is `"none"` (no egress).
    +- `docker.network: "host"` is blocked.
    +- `docker.network: "container:<id>"` is blocked by default.
    +- Break-glass override: `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true`.
     - `readOnlyRoot: true` blocks package installs.
     - `user` must be root for `apt-get` (omit `user` or set `user: "0:0"`).
       OpenClaw auto-recreates containers when `setupCommand` (or docker config) changes
    @@ -445,7 +450,8 @@ If you plan to install packages in `setupCommand`, note:
     
     Hardening knobs live under `agents.defaults.sandbox.docker`:
     `network`, `user`, `pidsLimit`, `memory`, `memorySwap`, `cpus`, `ulimits`,
    -`seccompProfile`, `apparmorProfile`, `dns`, `extraHosts`.
    +`seccompProfile`, `apparmorProfile`, `dns`, `extraHosts`,
    +`dangerouslyAllowContainerNamespaceJoin` (break-glass only).
     
     Multi-agent: override `agents.defaults.sandbox.{docker,browser,prune}.*` per agent via `agents.list[].sandbox.{docker,browser,prune}.*`
     (ignored when `agents.defaults.sandbox.scope` / `agents.list[].sandbox.scope` is `"shared"`).
    
  • src/agents/sandbox/browser.ts+12 8 modified
    @@ -36,6 +36,7 @@ import { readBrowserRegistry, updateBrowserRegistry } from "./registry.js";
     import { resolveSandboxAgentId, slugifySessionKey } from "./shared.js";
     import { isToolAllowed } from "./tool-policy.js";
     import type { SandboxBrowserContext, SandboxConfig } from "./types.js";
    +import { validateNetworkMode } from "./validate-sandbox-security.js";
     
     const HOT_BROWSER_WINDOW_MS = 5 * 60 * 1000;
     const CDP_SOURCE_RANGE_ENV_KEY = "OPENCLAW_BROWSER_CDP_SOURCE_RANGE";
    @@ -107,14 +108,15 @@ async function ensureSandboxBrowserImage(image: string) {
       );
     }
     
    -async function ensureDockerNetwork(network: string) {
    +async function ensureDockerNetwork(
    +  network: string,
    +  opts?: { allowContainerNamespaceJoin?: boolean },
    +) {
    +  validateNetworkMode(network, {
    +    allowContainerNamespaceJoin: opts?.allowContainerNamespaceJoin === true,
    +  });
       const normalized = network.trim().toLowerCase();
    -  if (
    -    !normalized ||
    -    normalized === "bridge" ||
    -    normalized === "none" ||
    -    normalized.startsWith("container:")
    -  ) {
    +  if (!normalized || normalized === "bridge" || normalized === "none") {
         return;
       }
       const inspect = await execDocker(["network", "inspect", network], { allowFailure: true });
    @@ -216,7 +218,9 @@ export async function ensureSandboxBrowser(params: {
         if (noVncEnabled) {
           noVncPassword = generateNoVncPassword();
         }
    -    await ensureDockerNetwork(browserDockerCfg.network);
    +    await ensureDockerNetwork(browserDockerCfg.network, {
    +      allowContainerNamespaceJoin: browserDockerCfg.dangerouslyAllowContainerNamespaceJoin === true,
    +    });
         await ensureSandboxBrowserImage(browserImage);
         const args = buildSandboxCreateArgs({
           name: containerName,
    
  • src/agents/sandbox/config.ts+9 0 modified
    @@ -95,6 +95,15 @@ export function resolveSandboxDockerConfig(params: {
         dns: agentDocker?.dns ?? globalDocker?.dns,
         extraHosts: agentDocker?.extraHosts ?? globalDocker?.extraHosts,
         binds: binds.length ? binds : undefined,
    +    dangerouslyAllowReservedContainerTargets:
    +      agentDocker?.dangerouslyAllowReservedContainerTargets ??
    +      globalDocker?.dangerouslyAllowReservedContainerTargets,
    +    dangerouslyAllowExternalBindSources:
    +      agentDocker?.dangerouslyAllowExternalBindSources ??
    +      globalDocker?.dangerouslyAllowExternalBindSources,
    +    dangerouslyAllowContainerNamespaceJoin:
    +      agentDocker?.dangerouslyAllowContainerNamespaceJoin ??
    +      globalDocker?.dangerouslyAllowContainerNamespaceJoin,
       };
     }
     
    
  • src/agents/sandbox-create-args.test.ts+20 0 modified
    @@ -181,6 +181,12 @@ describe("buildSandboxCreateArgs", () => {
           cfg: createSandboxConfig({ network: "host" }),
           expected: /network mode "host" is blocked/,
         },
    +    {
    +      name: "network container namespace join",
    +      containerName: "openclaw-sbx-container-network",
    +      cfg: createSandboxConfig({ network: "container:peer" }),
    +      expected: /network mode "container:peer" is blocked by default/,
    +    },
         {
           name: "seccomp unconfined",
           containerName: "openclaw-sbx-seccomp",
    @@ -271,4 +277,18 @@ describe("buildSandboxCreateArgs", () => {
         });
         expect(args).toEqual(expect.arrayContaining(["-v", "/tmp/override:/workspace:rw"]));
       });
    +
    +  it("allows container namespace join with explicit dangerous override", () => {
    +    const cfg = createSandboxConfig({
    +      network: "container:peer",
    +      dangerouslyAllowContainerNamespaceJoin: true,
    +    });
    +    const args = buildSandboxCreateArgs({
    +      name: "openclaw-sbx-container-network-override",
    +      cfg,
    +      scopeKey: "main",
    +      createdAtMs: 1700000000000,
    +    });
    +    expect(args).toEqual(expect.arrayContaining(["--network", "container:peer"]));
    +  });
     });
    
  • src/agents/sandbox/docker.ts+4 0 modified
    @@ -267,6 +267,7 @@ export function buildSandboxCreateArgs(params: {
       bindSourceRoots?: string[];
       allowSourcesOutsideAllowedRoots?: boolean;
       allowReservedContainerTargets?: boolean;
    +  allowContainerNamespaceJoin?: boolean;
     }) {
       // Runtime security validation: blocks dangerous bind mounts, network modes, and profiles.
       validateSandboxSecurity({
    @@ -278,6 +279,9 @@ export function buildSandboxCreateArgs(params: {
         allowReservedContainerTargets:
           params.allowReservedContainerTargets ??
           params.cfg.dangerouslyAllowReservedContainerTargets === true,
    +    dangerouslyAllowContainerNamespaceJoin:
    +      params.allowContainerNamespaceJoin ??
    +      params.cfg.dangerouslyAllowContainerNamespaceJoin === true,
       });
     
       const createdAtMs = params.createdAtMs ?? Date.now();
    
  • src/agents/sandbox/validate-sandbox-security.test.ts+24 0 modified
    @@ -222,6 +222,30 @@ describe("validateNetworkMode", () => {
           expect(() => validateNetworkMode(testCase.mode), testCase.mode).toThrow(testCase.expected);
         }
       });
    +
    +  it("blocks container namespace joins by default", () => {
    +    const cases = [
    +      {
    +        mode: "container:abc123",
    +        expected: /network mode "container:abc123" is blocked by default/,
    +      },
    +      {
    +        mode: "CONTAINER:ABC123",
    +        expected: /network mode "CONTAINER:ABC123" is blocked by default/,
    +      },
    +    ] as const;
    +    for (const testCase of cases) {
    +      expect(() => validateNetworkMode(testCase.mode), testCase.mode).toThrow(testCase.expected);
    +    }
    +  });
    +
    +  it("allows container namespace joins with explicit dangerous override", () => {
    +    expect(() =>
    +      validateNetworkMode("container:abc123", {
    +        allowContainerNamespaceJoin: true,
    +      }),
    +    ).not.toThrow();
    +  });
     });
     
     describe("validateSeccompProfile", () => {
    
  • src/agents/sandbox/validate-sandbox-security.ts+26 3 modified
    @@ -42,6 +42,10 @@ export type ValidateBindMountsOptions = {
       allowReservedContainerTargets?: boolean;
     };
     
    +export type ValidateNetworkModeOptions = {
    +  allowContainerNamespaceJoin?: boolean;
    +};
    +
     export type BlockedBindReason =
       | { kind: "targets"; blockedPath: string }
       | { kind: "covers"; blockedPath: string }
    @@ -276,14 +280,30 @@ export function validateBindMounts(
       }
     }
     
    -export function validateNetworkMode(network: string | undefined): void {
    -  if (network && BLOCKED_NETWORK_MODES.has(network.trim().toLowerCase())) {
    +export function validateNetworkMode(
    +  network: string | undefined,
    +  options?: ValidateNetworkModeOptions,
    +): void {
    +  const normalized = network?.trim().toLowerCase();
    +  if (!normalized) {
    +    return;
    +  }
    +
    +  if (BLOCKED_NETWORK_MODES.has(normalized)) {
         throw new Error(
           `Sandbox security: network mode "${network}" is blocked. ` +
             'Network "host" mode bypasses container network isolation. ' +
             'Use "bridge" or "none" instead.',
         );
       }
    +
    +  if (normalized.startsWith("container:") && options?.allowContainerNamespaceJoin !== true) {
    +    throw new Error(
    +      `Sandbox security: network mode "${network}" is blocked by default. ` +
    +        'Network "container:*" joins another container namespace and bypasses sandbox network isolation. ' +
    +        "Use a custom bridge network, or set dangerouslyAllowContainerNamespaceJoin=true only when you fully trust this runtime.",
    +    );
    +  }
     }
     
     export function validateSeccompProfile(profile: string | undefined): void {
    @@ -312,10 +332,13 @@ export function validateSandboxSecurity(
         network?: string;
         seccompProfile?: string;
         apparmorProfile?: string;
    +    dangerouslyAllowContainerNamespaceJoin?: boolean;
       } & ValidateBindMountsOptions,
     ): void {
       validateBindMounts(cfg.binds, cfg);
    -  validateNetworkMode(cfg.network);
    +  validateNetworkMode(cfg.network, {
    +    allowContainerNamespaceJoin: cfg.dangerouslyAllowContainerNamespaceJoin === true,
    +  });
       validateSeccompProfile(cfg.seccompProfile);
       validateApparmorProfile(cfg.apparmorProfile);
     }
    
  • src/config/config.sandbox-docker.test.ts+64 0 modified
    @@ -53,6 +53,37 @@ describe("sandbox docker config", () => {
         expect(res.ok).toBe(false);
       });
     
    +  it("rejects container namespace join by default", () => {
    +    const res = validateConfigObject({
    +      agents: {
    +        defaults: {
    +          sandbox: {
    +            docker: {
    +              network: "container:peer",
    +            },
    +          },
    +        },
    +      },
    +    });
    +    expect(res.ok).toBe(false);
    +  });
    +
    +  it("allows container namespace join with explicit dangerous override", () => {
    +    const res = validateConfigObject({
    +      agents: {
    +        defaults: {
    +          sandbox: {
    +            docker: {
    +              network: "container:peer",
    +              dangerouslyAllowContainerNamespaceJoin: true,
    +            },
    +          },
    +        },
    +      },
    +    });
    +    expect(res.ok).toBe(true);
    +  });
    +
       it("rejects seccomp unconfined via Zod schema validation", () => {
         const res = validateConfigObject({
           agents: {
    @@ -219,4 +250,37 @@ describe("sandbox browser binds config", () => {
         });
         expect(res.ok).toBe(false);
       });
    +
    +  it("rejects container namespace join in sandbox.browser config by default", () => {
    +    const res = validateConfigObject({
    +      agents: {
    +        defaults: {
    +          sandbox: {
    +            browser: {
    +              network: "container:peer",
    +            },
    +          },
    +        },
    +      },
    +    });
    +    expect(res.ok).toBe(false);
    +  });
    +
    +  it("allows container namespace join in sandbox.browser config with explicit dangerous override", () => {
    +    const res = validateConfigObject({
    +      agents: {
    +        defaults: {
    +          sandbox: {
    +            docker: {
    +              dangerouslyAllowContainerNamespaceJoin: true,
    +            },
    +            browser: {
    +              network: "container:peer",
    +            },
    +          },
    +        },
    +      },
    +    });
    +    expect(res.ok).toBe(true);
    +  });
     });
    
  • src/config/types.sandbox.ts+5 0 modified
    @@ -52,6 +52,11 @@ export type SandboxDockerSettings = {
        * (workspace + agent workspace roots).
        */
       dangerouslyAllowExternalBindSources?: boolean;
    +  /**
    +   * Dangerous override: allow Docker `network: "container:<id>"` namespace joins.
    +   * Default behavior blocks container namespace joins to preserve sandbox isolation.
    +   */
    +  dangerouslyAllowContainerNamespaceJoin?: boolean;
     };
     
     export type SandboxBrowserSettings = {
    
  • src/config/zod-schema.agent-runtime.ts+27 1 modified
    @@ -126,6 +126,7 @@ export const SandboxDockerSchema = z
         binds: z.array(z.string()).optional(),
         dangerouslyAllowReservedContainerTargets: z.boolean().optional(),
         dangerouslyAllowExternalBindSources: z.boolean().optional(),
    +    dangerouslyAllowContainerNamespaceJoin: z.boolean().optional(),
       })
       .strict()
       .superRefine((data, ctx) => {
    @@ -153,14 +154,24 @@ export const SandboxDockerSchema = z
             }
           }
         }
    -    if (data.network?.trim().toLowerCase() === "host") {
    +    const network = data.network?.trim().toLowerCase();
    +    if (network === "host") {
           ctx.addIssue({
             code: z.ZodIssueCode.custom,
             path: ["network"],
             message:
               'Sandbox security: network mode "host" is blocked. Use "bridge" or "none" instead.',
           });
         }
    +    if (network?.startsWith("container:") && data.dangerouslyAllowContainerNamespaceJoin !== true) {
    +      ctx.addIssue({
    +        code: z.ZodIssueCode.custom,
    +        path: ["network"],
    +        message:
    +          'Sandbox security: network mode "container:*" is blocked by default. ' +
    +          "Use a custom bridge network, or set dangerouslyAllowContainerNamespaceJoin=true only when you fully trust this runtime.",
    +      });
    +    }
         if (data.seccompProfile?.trim().toLowerCase() === "unconfined") {
           ctx.addIssue({
             code: z.ZodIssueCode.custom,
    @@ -464,6 +475,21 @@ export const AgentSandboxSchema = z
         prune: SandboxPruneSchema,
       })
       .strict()
    +  .superRefine((data, ctx) => {
    +    const browserNetwork = data.browser?.network?.trim().toLowerCase();
    +    if (
    +      browserNetwork?.startsWith("container:") &&
    +      data.docker?.dangerouslyAllowContainerNamespaceJoin !== true
    +    ) {
    +      ctx.addIssue({
    +        code: z.ZodIssueCode.custom,
    +        path: ["browser", "network"],
    +        message:
    +          'Sandbox security: browser network mode "container:*" is blocked by default. ' +
    +          "Set sandbox.docker.dangerouslyAllowContainerNamespaceJoin=true only when you fully trust this runtime.",
    +      });
    +    }
    +  })
       .optional();
     
     const CommonToolPolicyFields = {
    
  • src/security/audit-extra.sync.ts+12 4 modified
    @@ -830,13 +830,21 @@ export function collectSandboxDangerousConfigFindings(cfg: OpenClawConfig): Secu
         }
     
         const network = typeof docker.network === "string" ? docker.network : undefined;
    -    if (network && network.trim().toLowerCase() === "host") {
    +    const normalizedNetwork = network?.trim().toLowerCase();
    +    if (normalizedNetwork === "host" || normalizedNetwork?.startsWith("container:")) {
    +      const modeLabel = normalizedNetwork === "host" ? '"host"' : `"${network}"`;
    +      const detail =
    +        normalizedNetwork === "host"
    +          ? `${source}.network is "host" which bypasses container network isolation entirely.`
    +          : `${source}.network is ${modeLabel} which joins another container namespace and can bypass sandbox network isolation.`;
           findings.push({
             checkId: "sandbox.dangerous_network_mode",
             severity: "critical",
    -        title: "Network host mode in sandbox config",
    -        detail: `${source}.network is "host" which bypasses container network isolation entirely.`,
    -        remediation: `Set ${source}.network to "bridge" or "none".`,
    +        title: "Dangerous network mode in sandbox config",
    +        detail,
    +        remediation:
    +          `Set ${source}.network to "bridge", "none", or a custom bridge network name.` +
    +          ` Use ${source}.dangerouslyAllowContainerNamespaceJoin=true only as a break-glass override when you fully trust this runtime.`,
           });
         }
     
    
  • src/security/audit.test.ts+25 0 modified
    @@ -855,6 +855,31 @@ describe("security audit", () => {
         );
       });
     
    +  it("flags container namespace join network mode in sandbox config", async () => {
    +    const cfg: OpenClawConfig = {
    +      agents: {
    +        defaults: {
    +          sandbox: {
    +            mode: "all",
    +            docker: {
    +              network: "container:peer",
    +            },
    +          },
    +        },
    +      },
    +    };
    +    const res = await audit(cfg);
    +    expect(res.findings).toEqual(
    +      expect.arrayContaining([
    +        expect.objectContaining({
    +          checkId: "sandbox.dangerous_network_mode",
    +          severity: "critical",
    +          title: "Dangerous network mode in sandbox config",
    +        }),
    +      ]),
    +    );
    +  });
    +
       it("checks sandbox browser bridge-network restrictions", async () => {
         const cases: Array<{
           name: string;
    

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.