Skip to content

Sandbox path validation fails for container-internal paths (/workspace/...) #9560

Description

@Explorer1092

Summary

When an AI agent running inside a sandbox container attempts to write files using container-internal paths (e.g., /workspace/test.svg), the path validation in wrapSandboxPathGuard fails with "Path escapes sandbox root" error, even though the path is valid within the container.

Environment

  • OpenClaw version: latest (commit 1b0ffeeb3)
  • Sandbox mode: all
  • Workspace access: rw
  • Docker sandbox with default workdir: /workspace

Steps to Reproduce

  1. Configure an agent with sandbox enabled:

    {
      "agents": {
        "defaults": {
          "sandbox": {
            "mode": "all",
            "workspaceAccess": "rw",
            "scope": "agent"
          }
        }
      }
    }
  2. Ask the AI to create/write a file, e.g., "create an image file at /workspace/test.svg"

  3. The AI attempts to use the write tool with path /workspace/test.svg

Expected Behavior

The write operation should succeed because:

  • /workspace is the container's workdir (mapped to the actual workspace directory)
  • workspaceAccess is set to rw
  • The path is within the sandbox's allowed area

Actual Behavior

The operation fails with:

[tools] write failed: Path escapes sandbox root (~/.openclaw/workspace/team): /workspace/test_image.svg

Root Cause Analysis

The issue is in src/agents/pi-tools.read.ts in the wrapSandboxPathGuard function:

function wrapSandboxPathGuard(tool: AnyAgentTool, root: string): AnyAgentTool {
  return {
    ...tool,
    execute: async (toolCallId, args, signal, onUpdate) => {
      const filePath = record?.path;  // e.g., "/workspace/test.svg"
      if (typeof filePath === "string" && filePath.trim()) {
        await assertSandboxPath({ filePath, cwd: root, root });  // Direct validation without path mapping
      }
      return tool.execute(toolCallId, normalized ?? args, signal, onUpdate);
    },
  };
}

The assertSandboxPath function in src/agents/sandbox-paths.ts then does:

export function resolveSandboxPath(params: { filePath: string; cwd: string; root: string }) {
  const resolved = resolveToCwd(params.filePath, params.cwd);  // "/workspace/test.svg" (absolute, unchanged)
  const rootResolved = path.resolve(params.root);  // "/home/node/.openclaw/workspace/team"
  const relative = path.relative(rootResolved, resolved);  // "../../workspace/test.svg" (starts with "..")
  if (relative.startsWith("..") || path.isAbsolute(relative)) {
    throw new Error(`Path escapes sandbox root...`);  // ERROR!
  }
}

The problem: The AI uses container-internal paths (/workspace/...), but the path validation compares these directly against the host filesystem path (/home/node/.openclaw/workspace/team). There's no mapping from the container workdir (/workspace) to the actual workspace directory.

Suggested Fix

Before validating the path, map container-internal paths to the actual filesystem path:

function wrapSandboxPathGuard(tool: AnyAgentTool, root: string, containerWorkdir?: string): AnyAgentTool {
  return {
    ...tool,
    execute: async (toolCallId, args, signal, onUpdate) => {
      let filePath = record?.path;
      
      // Map container-internal paths to actual filesystem paths
      const workdir = containerWorkdir ?? '/workspace';
      if (typeof filePath === "string" && filePath.startsWith(workdir + '/')) {
        filePath = path.join(root, filePath.slice(workdir.length + 1));
      } else if (filePath === workdir) {
        filePath = root;
      }
      
      if (typeof filePath === "string" && filePath.trim()) {
        await assertSandboxPath({ filePath, cwd: root, root });
      }
      return tool.execute(toolCallId, normalized ?? args, signal, onUpdate);
    },
  };
}

The containerWorkdir can be obtained from SandboxContext.containerWorkdir (which comes from cfg.sandbox.docker.workdir, defaulting to /workspace).

Workaround

Currently, the workaround is to instruct the AI to use relative paths (e.g., media/test.jpg) instead of absolute container paths (/workspace/media/test.jpg). However, this is not intuitive for the AI since it sees /workspace as its working directory inside the container.

Related Code

  • src/agents/pi-tools.read.ts: wrapSandboxPathGuard, createSandboxedWriteTool, createSandboxedEditTool
  • src/agents/sandbox-paths.ts: resolveSandboxPath, assertSandboxPath
  • src/agents/sandbox/types.ts: SandboxContext.containerWorkdir

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions