agentscope-ai/agentscope · error · PermissionError

path escapes writable Bubblewrap mounts: {path}

Error message

path escapes writable Bubblewrap mounts: {path}

What it means

read_file inside the sandbox returns exit code 65 when the target path resolves outside the sandbox's writable bind mounts. The backend translates this into PermissionError to signal that the sandboxed shell refused to touch the path for containment reasons.

Source

Thrown at src/agentscope/workspace/_bubblewrap/_bubblewrap_backend.py:272

            [
                "sh",
                "-c",
                (
                    'resolved=$(realpath -e -- "$1") || exit 66; '
                    'case "$resolved" in '
                    "/workspace|/workspace/*|/tmp|/tmp/*) "
                    'cat -- "$resolved" ;; '
                    "*) exit 65 ;; "
                    "esac"
                ),
                "sh",
                sandbox_path,
            ],
        )
        if result.exit_code == 66:
            raise FileNotFoundError(f"not found in Bubblewrap sandbox: {path}")
        if result.exit_code == 65:
            raise PermissionError(
                f"path escapes writable Bubblewrap mounts: {path}",
            )
        if not result.ok():
            raise RuntimeError(
                "Bubblewrap read_file failed "
                f"(exit {result.exit_code}): "
                f"{result.stderr.decode(errors='replace')}",
            )
        return result.stdout

    async def write_file(self, path: str, data: bytes) -> None:
        """Write raw bytes, refusing every symbolic-link final component."""
        await self._write_via_cat(path, data)

    async def write_stream(
        self,
        path: str,
        stream: AsyncIterator[bytes],

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Use an absolute path under the sandbox workdir or tmpdir only
  2. Normalize and prefix-join relative paths with the sandbox workdir before calling read_file
  3. Treat PermissionError from read_file as a path-design bug, not a transient failure

Example fix

# before
await ws.read_file('../../etc/passwd')
# after
await ws.read_file('/workspace/notes.txt')
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import PurePosixPath
ALLOWED = ('/workspace/', '/tmp/')
norm = posixpath.normpath(path)
assert norm.startswith(ALLOWED), f'outside sandbox: {norm}'

Type guard

null

Try / catch

except PermissionError as e:
    if 'escapes writable' in str(e): reject_path(path)

Prevention

When it happens

Trigger: Calling read_file on a path like /etc/passwd or ../../etc/shadow — anything outside the writable SANDBOX_WORKDIR/SANDBOX_TMPDIR trees once normalized inside the sandbox.

Common situations: Passing host-absolute paths that don't exist in the sandbox view, or traversal sequences that escape the workspace root.

Related errors


AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28). Data as JSON: /api/errors/c08b4b3345bf4ac5. Report an issue: GitHub.