Skip to content

fix(pixel): tell sandboxed Pixel what its exec sandbox provides - #6763

Open
Lightheartdevs wants to merge 1 commit into
mainfrom
claude/pixel-sandbox-capabilities
Open

Lightheartdevs wants to merge 1 commit into
mainfrom
claude/pixel-sandbox-capabilities

Conversation

@Lightheartdevs

Copy link
Copy Markdown
Collaborator

Problem

Pixel is never told what its exec sandbox contains. The workspace TOOLS.md "Shell" section says only that shell runs in the sandbox with a read-only root filesystem. So on coding tasks Pixel spends tool calls finding out:

  • Photo renamer ("write a python script that renames all photos in a folder by date taken, and test it"): Pixel sent five exec probes (python3 -c "from PIL import Image", import piexif, import exifread) before it wrote any file. Those calls used up the run's failure budget and the turn stopped.
  • Another install, same build: Pixel lost three attempts to pip: not found, pip3: not found and No module named pip.

The sandbox image (ods/vendor/pixel/deploy/sandbox/Dockerfile) ships Python 3.11 with Pillow and python-docx. It has no pip, and the sandbox has no network, so nothing can be installed.

Fix

Add one static contract, ODS_SANDBOX_RUNTIME_CONTRACT, after the conversation contract. It is added only when trusted configuration proves sandboxed exec, meaning executionHostForAgent returns sandbox (sandbox mode all with exec host sandbox). It tells Pixel:

  • the sandbox is Debian 12 (bookworm) with Python 3.11
  • the only Python packages beyond the standard library are Pillow (import PIL) and python-docx (import docx)
  • the installed commands: bash, git, jq, rg, rsync, file, unzip, the poppler-utils, tesseract with English data, and catdoc/xls2csv
  • pip, pytest, Node.js and npm are not installed, and there is no network. Pixel must not probe for or install packages, and should use the standard library and python3 -m unittest
  • files go in the workspace, which is writable and persists. /tmp is small scratch space. The root filesystem, including home, is read-only.

In native (full-access) mode, and in any mode that is not proven, Pixel gets no sandbox claim.

Why the plugin and not the workspace template

The text lives in pixel-agent/plugin/prompt-contract.mjs (conversationContractForExecution):

  • It reaches existing installs. The pixel-ods plugin loads on every Linux, WSL and native macOS install, so each install gets the text on its next update. The template is copied with cp -n, so changes to it never reach existing workspaces. migrate-workspace-source-boundary.mjs runs only in Linux apply.sh. bootstrap-capabilities.mjs changes only TOOLS.md files whose digest still matches the shipped one, so owner-edited files would be skipped.
  • It follows the execution mode. A workspace file can't say "sandbox only", and these facts are false when exec runs natively.
  • It doesn't touch vendor/pixel. No pixel.bundle regeneration is needed, and nothing overlaps fix(pixel): remove the owner's private operating contract from the workspace template #6755's template scrub. This branch does not need to be built on fix(pixel): remove the owner's private operating contract from the workspace template #6755.
  • It keeps the prompt cache stable. The text depends only on the release and the configured execution mode, so it is byte-identical across turns. It sits right after the conversation contract, before any per-message section. With fix(pixel): keep Pixel's prompt append-only across owner messages #6702 it lands in systemContext, the session-stable part. The September 16 core and compact core hashes are unchanged.

Platforms

All platforms use the same image definition:

  • Linux: vendor/pixel/scripts/bootstrap.sh builds deploy/sandbox as openclaw-sandbox:bookworm-slim.
  • Windows: Pixel runs only through the Linux installer inside WSL2. service-plan.ps1 disables Pixel on native Windows. So Windows uses the Linux path and the same image.
  • Native macOS: pixel-native-bootstrap.py builds the same deploy/sandbox Dockerfile for linux/arm64, tagged ods-pixel-native-sandbox:<version>-uid-<uid>-<ref>. Bookworm arm64 ships the same packages and Python 3.11.

All three render the sandbox through render-config.mjs: network none, a read-only root, /workspace read-write, and /tmp as a 256 MB tmpfs.

The only differences between platforms are the image tag and the CPU architecture.

Drift test

tests/sandbox_runtime_contract.test.mjs fails CI when the image or the sandbox settings stop matching the text. test-linux.yml runs pixel-agent/tests/*.test.mjs on every PR, so a change to the Dockerfile alone is caught. The test checks that:

  • every package in the Dockerfile's apt-get install line maps to a phrase in the contract, or is explicitly marked unadvertised (ca-certificates, and curl, which is useless without network). Adding or removing a package fails.
  • the base image's Debian release (bookworm) maps to the stated Python (3.11). A new release fails until the text is updated.
  • the Dockerfile has no other way to add tools: no pip/npm/gem/cargo/go install, no curl | sh, and no COPY/ADD.
  • the tools the contract calls absent (pip/venv, pytest, Node.js, npm) are not installed.
  • the sandbox block in render-config.mjs keeps network none, readOnlyRoot: true, workspaceAccess: "rw", workdir /workspace, and a /tmp tmpfs.
  • the Linux/WSL and macOS installers build that Dockerfile and render that config.
  • the contract appears exactly once, at the same position, for every prompt in both the full and lean contracts under sandbox exec, and never under native exec or an unproven mode.

I checked by hand that each of these changes fails the test: adding python3-numpy, removing python3-docx, adding nodejs, switching the base to trixie, adding a RUN pip install line, adding a COPY line, setting network to bridge, and setting readOnlyRoot: false.

Validation

Full Linux pixel-agent suite (node --test tests/*.test.mjs):

Tree Tests Pass Fail
main 5afd46c 3203 3165 0
this branch 46b9c67 3207 3169 0
this branch + main (merge commit, not pushed) 3207 3169 0
+ #6743 3279 3241 0
+ #6702 3244 3206 0
+ #6753 3230 3191 1

The #6753 failure (tower2 replay through the registered hooks: the refused cleanup keeps the current pass... in deletion_refusal_delivery.test.mjs) is not caused by this branch. It also fails on main + #6753 without this change, and passes on #6753 alone, which is behind main.

Conflicts

git merge-tree against open PRs:

🤖 Generated with Claude Code

Pixel was never told what the exec sandbox contains, so on coding tasks it
spends tool calls finding out: in a photo-renamer run it issued five exec
probes for Pillow, piexif and exifread before writing any file, and those
calls used up the run's failure budget; another install lost three attempts
to "pip: not found" / "No module named pip". The image has Pillow and no
pip, and no network to install anything.

Add one static sentence group to the system conversation contract when
trusted configuration proves sandboxed exec (sandbox mode "all" with exec
host "sandbox"): Debian 12 with Python 3.11, Pillow and python-docx beyond
the standard library, the installed commands, no pip/pytest/Node.js/npm
and no network (nothing can be installed), and where files can be written.
Native (full-access) exec and unproven modes get no sandbox claim.

The text is in the plugin, not the workspace template: every Linux, WSL
and native macOS install loads the pixel-ods plugin, so it reaches existing
installs on update regardless of owner-edited TOOLS.md, and it needs no
vendor/pixel bundle regeneration. It is release-constant, so the system
prompt stays byte-identical across turns.

sandbox_runtime_contract.test.mjs ties every stated fact to its source:
each package in the sandbox Dockerfile's apt-get install line must map to
a phrase in the contract (or be marked deliberately unadvertised), the
base image's Debian release must map to the stated Python, no other
install path (pip/npm/COPY) may exist, the absent tools must stay absent,
and render-config.mjs must keep network none, a read-only root, a writable
/workspace and a /tmp tmpfs. It also checks that the Linux/WSL and native
macOS installers build that Dockerfile and render that config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant