external-sandbox-quicksand
A Python execution server for coding agents that runs commands inside a Quicksand QEMU virtual machine. It implements the shared External Sandbox protocol, alongside external-sandbox-v86 and external-sandbox-container: the same buffered and streaming shell APIs, Codex endpoint, browser terminal, host mounts, and allow-listed HTTP(S) proxy, with a full virtual machine as the sandbox.
Start
Requirements: uv and a host supported by Quicksand. This release is tested on Apple silicon macOS with HVF acceleration; Quicksand publishes no QEMU wheel for Intel Macs. QEMU and the Alpine image arrive as Python wheels; no Homebrew, Docker or separate VM tooling is needed.
uvx external-sandbox-quicksand --preserve-paths \
--mount-rw project=/absolute/path/to/project \
--mount-ro reference=/absolute/path/to/reference
uvx downloads and runs the latest release from PyPI, including about 100 MB of QEMU and Alpine image wheels on first use. To install the command permanently, use uv tool install external-sandbox-quicksand. From a source checkout, use uv run external-sandbox-quicksand instead.
The server listens on 127.0.0.1:8688, so it can run beside the v86 (8686) and container (8687) servers. Mounts appear at /mnt/project and /mnt/reference; --preserve-paths also binds them at their original absolute host paths for clients that send host paths. The first mount is the default working directory; without mounts it is /workspace. Stop the server with Ctrl-C to shut down and discard the VM. Files in writable mounts persist on the host; all other guest changes are disposable.
The VM gets two CPUs and 1 GiB of memory by default (--cpus, --memory). --accel tcg forces software emulation when hardware acceleration is unavailable. Run uvx external-sandbox-quicksand --help for every option.
Guest images
Alpine (default). Quicksand's Alpine 3.23 image lacks Bash and Python, which the protocol and agent need. On first use, the server boots that image once, installs bash, python3, git and ripgrep from https://dl-cdn.alpinelinux.org through its own allow-listed proxy (only GET/HEAD to that origin), and saves the result as a Quicksand save under ~/.cache/external-sandbox-quicksand/images/. On an Apple silicon Mac this took about seven seconds and produced a 65 MB save; later starts boot it directly and serve requests in about a second.
uvx external-sandbox-quicksand --prepare-image # prepare now, then exit
uvx external-sandbox-quicksand --packages git,ripgrep,nodejs,npm,uv,jq
--packages replaces the optional package list (bash and python3 are always included) and prepares a separately cached image. The cache key covers the Quicksand Alpine build, architecture and package list, so a Quicksand upgrade prepares a fresh image. Each prepared image records its installed package versions in external-sandbox-image.json. Alpine package versions come from the 3.23 branch at preparation time. Set --cache-dir or XDG_CACHE_HOME to move the cache.
Ubuntu 24.04 (source checkout only). Quicksand's Ubuntu image already includes Bash and Python 3.12. Its image wheel (about 300 MB) comes from Microsoft's Quicksand package index rather than PyPI, so it is not part of the published package. From a source checkout, the ubuntu dependency group installs it:
uv run --group ubuntu external-sandbox-quicksand --image ubuntu
--image also accepts a Quicksand save directory, provided it has /bin/bash and python3.
How it works
host: external-sandbox-quicksand (aiohttp) guest: Quicksand VM
/exec, /exec/ws, /codex ── virtio-serial port ───────────▶ guest_agent.py (python3)
9p fsdev (readonly=on for ro mounts) ─────────────────────▶ /mnt/<name>
host proxy ◀── bridge ◀── QEMU guestfwd stream ◀── guest-mux ◀── 127.0.0.1:3128 ◀── HTTP(S)_PROXY
Quicksand boots the VM and its own guest agent installs this server's agent from the Python package. The private agent speaks length-prefixed JSON over a dedicated virtio-serial port, separate from Quicksand's agent channel and unreachable through guest networking. It runs each command as /bin/bash -c in its own process group, streams binary output, and performs Codex filesystem operations inside the guest. These modules, the HTTP/WebSocket/Codex adapters, and the proxy engine are shared with external-sandbox-container; see PROVENANCE.md.
Host directories use QEMU virtio-9p mounts. Read-only mounts set QEMU's readonly=on, so the host rejects writes regardless of guest privileges. The VM starts with Quicksand's NetworkMode.NONE, which removes its default NIC, host agent port forward and SMB relay.
Browser terminal
Open http://127.0.0.1:8688/ to run commands through the streaming WebSocket API, with stdin, EOF, cancel, history and Copy session. Each run starts a new Bash process; files persist for the VM's lifetime. The terminal uses pipes, not a PTY. The HTML, CSS and JavaScript are a pinned bundle of external-sandbox-ui 0.3.0; update it with that repository's scripts/sync.py, as described in its README.
Controlled network access
Without --proxy-config the VM has no network interface. With a policy file, it gets exactly one: a QEMU user-mode NIC with restrict=on and a single guestfwd rule, 10.0.2.101:3128, connected to a host stream. Save this starter policy as proxy.json; it allows only GET and HEAD requests to https://example.com:
{
"allow": [
{"origin": "https://example.com", "methods": ["GET", "HEAD"]}
]
}
uvx external-sandbox-quicksand --proxy-config proxy.json
QEMU's guestfwd TCP backend is one persistent byte stream, so a small static Go helper (guest-mux, from the Quicksand proxy prototype) listens on guest 127.0.0.1:3128 and multiplexes each connection over that stream. The host bridge opens one connection per channel to the embedded host proxy and nowhere else. Commands receive standard proxy variables, NODE_USE_ENV_PROXY=1, and a CA bundle containing the proxy's public certificate.
The proxy, policy format, management page and API are the same as the container server's and follow the protocol's proxy profile: exact-origin allow rules with optional methods, paths and CIDRs; public addresses only by default; credential placeholders replaced on the host; secret redaction; and live policy edits at http://127.0.0.1:8688/proxy that close existing connections. examples/proxy.json demonstrates credential substitution and needs OPENAI_API_KEY in the server's environment. Literal redaction cannot prevent an allowed upstream from transforming and echoing a credential, so use credential rules only with trusted services and narrow their methods and paths. Removing proxy variables cannot open another route: QEMU drops everything except the guestfwd stream. If the guestfwd stream fails, proxy access fails closed until restart; /health reports proxyTransport.
Agents
Agent integrations are maintained in external-sandbox:
- Claude Code: set
EXTERNAL_SANDBOX_API_URL=http://127.0.0.1:8688and start this server with--preserve-paths --mount-rw project=$PWD. - Codex: use executor URL
ws://127.0.0.1:8688/codex. - Pi: use
--sandbox-url http://127.0.0.1:8688. The default Alpine image includes the Python and ripgrep its tools need.
Scope
- Commands and Codex filesystem operations run in the VM as guest root. The VM is the isolation boundary; guest root cannot change QEMU's mount modes or network restrictions.
- Mounts deliberately expose host directories. Read-write mounts change host files immediately; host changes are visible in the guest. Guest root can follow symlinks only within the guest view. A pre-existing host hardlink in a writable mount remains editable even if another name for the same file is in a read-only mount; avoid that alias if the file must stay unchanged. This backend does not reproduce v86's hardlink checks.
- The guest agent and proxy helper are not trusted by the host. The host bounds frame sizes and validates messages; a compromised guest can disrupt its own commands or proxy access. The host does not provision real credentials to the VM, but an allowed upstream can transform and echo one in its response.
- Quicksand's own guest agent remains in the VM and is used only by the host for setup.
- Agent processes, model traffic and host-side tools remain on the host; routing shell commands into the VM does not constrain those tools.
- The execution listener is for trusted local clients, with the same browser token, Origin, Fetch Metadata and Host checks as the other servers. It is not authentication between mutually untrusted host users or a remote multi-user service.
Test
uv run pytest -m 'not integration'
node --test tests/test_terminal_js.mjs
uv run ruff check .
Tests marked integration boot real VMs and are opt-in:
RUN_QUICKSAND_TESTS=1 uv run pytest -m integration
RUN_QUICKSAND_TESTS=1 SANDBOX_TEST_IMAGE=ubuntu uv run --group ubuntu pytest -m integration
RUN_QUICKSAND_TESTS=1 RUN_SANDBOX_TOOL_IMAGE_TESTS=1 uv run pytest tests/test_proxy_integration.py
EXTERNAL_SANDBOX_CLIENT=/absolute/path/to/external-sandbox/claude-code/external_sandbox_exec.py \
RUN_QUICKSAND_TESTS=1 uv run pytest tests/test_legacy_client.py tests/test_real_clients.py
The tool-image tests prepare an Alpine image with Node, npm and uv and check they reach a synthetic HTTPS service only through the proxy. The mux bridge tests also compile guest-mux for the host when Go is installed. To rebuild the bundled helpers, run guest-mux/build.sh; Go 1.27.2 reproduces the committed binaries exactly.
See verification for recorded results, including the shared conformance suite.
Metadata
Release files for external-sandbox-quicksand 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| external_sandbox_quicksand-0.1.0.tar.gz | 2.1 MB | With provenance | Details for external_sandbox_quicksand-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| external_sandbox_quicksand-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for external_sandbox_quicksand-0.1.0-py3-none-any.whl |
Total release size: 4.1 MB
Release files / external_sandbox_quicksand-0.1.0.tar.gz
| Download URL | external_sandbox_quicksand-0.1.0.tar.gz |
|---|---|
| Size | 2.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d68fc211ed2811b63a65d7a91a36729b18f17e2322519f700f86c1ede9e596da
|
|
BLAKE2b-256 checksum How to use checksums |
df9d90186a4e7493d466d15dac7318cfd447134c0d2c37432299b986513df455
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logRelease files / external_sandbox_quicksand-0.1.0-py3-none-any.whl
| Download URL | external_sandbox_quicksand-0.1.0-py3-none-any.whl |
|---|---|
| Size | 2.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fa8dfcb8b6ddb19ad1ef5dff77a432f087777217add1303658ac91b0de7dd51f
|
|
BLAKE2b-256 checksum How to use checksums |
182d9167955b4304f48fe0155d4f46d0069f055d1c44504537f8bee0dcc6172c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logSecurity for external-sandbox-quicksand 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for external_sandbox_quicksand-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.