Skip to main content

maf-sandbox-docker-sbx

PyPI Python License

Experimental. Releases before 1.0 may change or remove APIs. Importing this package emits MafSandboxDockerSbxExperimentalWarning.

Run sandbox commands in Docker Sandboxes, one microVM with its own Linux kernel per sandbox, on your own Windows, macOS or Linux machine. The backend drives Docker's sbx CLI and has no Python dependency beyond maf-sandbox.

This is an independent package, not a Docker or Microsoft product.

Quickstart

pip install maf-sandbox-docker-sbx
from maf_sandbox import SandboxRouter
from maf_sandbox_docker_sbx import SbxSandboxBackend, SbxSandboxConfig

backend = SbxSandboxBackend(SbxSandboxConfig())
router = SandboxRouter([backend])

The backend clears the router's default MICROVM minimum, so no min_isolation is needed.

The Python event loop must support subprocesses. Windows' default Proactor loop does; WindowsSelectorEventLoopPolicy does not.

Requirements

Install sbx and sign in once:

  • Windows 11 with the Windows Hypervisor Platform: winget install -h Docker.sbx. It lands in %LOCALAPPDATA%\DockerSandboxes\bin, which may not be on PATH; pass that path as sbx_path.
  • macOS 14 or later on Apple silicon: brew install docker/tap/sbx.
  • Ubuntu 24.04 or later with KVM, and your user in the kvm group: the docker-sbx apt package.

Then prepare the host once:

sbx login
sbx policy init deny-all
sbx settings set ssh.agentForwardingEnabled false
sbx daemon restart

The backend checks two host-wide settings at every acquire and refuses with SbxHostNotConfined when either fails. It reads them and never writes them. Because the SSH setting takes effect only after sbx daemon restart, each new sandbox is also checked for the forwarded agent socket before any workload runs. That check runs in the template's own sh, so it catches a daemon that was not restarted, not a template that lies: use templates you trust.

  • SSH agent forwarding must be off. It is on by default, and it gives every sandbox a socket to the host's SSH agent.
  • No MCP server may be registered (sbx mcp ls). The host's MCP gateway answers a sandbox even under a deny-all rule.

A lapsed login fails every sbx command until a person signs in again; the backend raises SbxLoginRequired. In CI, pipe an access token to sbx login --username <user> --password-stdin.

sbx collects telemetry by default. Set SBX_NO_TELEMETRY=1 in the host's environment to opt out.

Supported operations

Setting Behavior
Isolation MICROVM
Capabilities EXEC, FILES_IN, FILES_OUT, FILES_LIST, FILES_DELETE, RECLAIM
Guest OS POSIX
Network CLOSED
Isolation scope CONVERSATION
Egress observation No
Command output 8 MiB of stdout and stderr together; more kills the command and refuses the call

RUN_CODE is not declared, because any image is accepted and the runtime is the image's. SNAPSHOT is not declared. HOST_TOOLS is not declared yet: an idle sandbox stops 30 seconds after its last sbx session and kills every process, and the host-tool transport has not been measured against that.

spec.image_id, or else spec.image, is passed to sbx create --template, and a warm acquire refuses a spec that changes either. Without it the sandbox uses Docker's shell template, where commands run as agent (uid 1000). An image without that user runs commands as root. Commands run in a user namespace of their own, so sudo does not work in them, even in Docker's template. The namespace maps only the command's own uid and gid. Files owned by anyone else show as uid 65534 (nobody), and chown to another user fails, also in a template that runs as root. Permission checks still use the real owners.

Any Linux image works as a template if it has /bin/sh and /bin/bash and the tools listed under Commands. Without /bin/bash, sbx cannot start the sandbox. An image built locally reaches sbx through a tar:

docker build -t my-image:local .
docker save -o my-image.tar my-image:local
sbx template load my-image.tar

Then pass image="my-image:local".

Files: a workspace answered by the host

Each sandbox mounts one fresh, private host directory, <workspace_root>/<sandbox name>/ws-<random>, and nothing else. Every create gets a new one, so a new sandbox never mounts an earlier one's files, and sbx ls tells which create made a sandbox. Every command binds that mount again at the storage base's parent, inside a user and mount namespace of its own, and then runs as the image's user. With the default storage base /maf-sandbox/work, that parent is /maf-sandbox. sbx lets only its own templates mount as root, and the namespace needs no such capability, so any template works. At create, root only makes the empty directory the mount goes over.

Stats, reads, listings and writes act on the host side of the mount. No path check is answered inside the guest.

  • On macOS and Linux each path component is opened relative to its parent with O_NOFOLLOW, so a link the guest creates between a check and an operation makes the operation fail rather than follow it. On Linux the guest can create links in its workspace, and the plane refused to read or write through each one, measured with sbx v0.45.1.
  • On Windows there are no descriptor-relative calls. The backend refuses reparse points, and relies on the guest being unable to create a link in its workspace, measured with sbx v0.45.1.
  • Writes land in a temporary file and are renamed into place, so a write never goes through what stood at the destination.
  • remove and reclaim both remove in the guest, at the guest's own authority. remove first runs the host-side path check, then rm -f, which refuses a directory swapped in after the check, or rm -rf when recursive. reclaim checks placement, then runs the host-side check on the target, then rm -rf: a relative target must name a child of the working directory, and an absolute one may be anywhere strictly inside the workspace. A guest keeps seeing a name for seconds after a host-side delete, so a host-side removal would leave it acting on a file that is gone.
  • Names the host would change, hide or merge are refused: a name that stats but is not listed verbatim, such as a case variant on a case-insensitive host. On Windows, reserved characters, reserved device names and a trailing dot or space are refused too. The name check and the operation are separate calls, and neither NTFS nor APFS offers an exact-case open, so a guest racing them can make an alias appear in between. That redirects the operation only to another name inside the guest's own workspace, which the guest could reach anyway.

File methods reach only paths under the storage base's parent. Any other absolute path is refused with ValueError. work_dir must have a parent other than /, and that parent must not already exist in the image.

workspace_root defaults to a per-user state directory: %LOCALAPPDATA%\maf-sandbox-docker-sbx\workspaces, ~/Library/Application Support/maf-sandbox-docker-sbx or $XDG_STATE_HOME/maf-sandbox-docker-sbx/workspaces. It is never the temporary directory, which a host may clean.

Commands

exec runs argv verbatim, with separate, byte-exact streams and the command's own exit code. Every command runs through a small sh wrapper. The image needs sh, base64, setsid, mount, unshare from util-linux 2.38 or later, mkdir, cat, rm and sleep, and acquire checks for all of them:

  • argv is base64-encoded, because sbx refuses an empty argument;
  • a nonce on stderr marks where the command's own stderr starts, so a missing sandbox is never read as a command that exited 1;
  • the command runs in its own process group. When timeout expires, the backend kills that group with a second sbx exec, bounded by exec_cleanup_timeout_seconds, and then raises TimeoutError. Killing the sbx client alone would leave the command running. The same command first leaves a cancel file, which the wrapper checks after recording its group, so a command that has not started yet never will. Only the expired command is touched; a sibling call in the same sandbox keeps running. If the kill cannot run at all, the sandbox is retired: every handle to it refuses further use, and the next acquire replaces it. The retirement is written to the sandbox's record, so a backend in another process, or after a restart, replaces it too. The kill reaches only the command's own process group. A command that starts its own session, or rewrites the pid file its user can write, outlives it; only disposal ends every process in the sandbox.
  • output past 8 MiB, stdout and stderr together, ends the command the same way and raises SandboxExecOutputLimitExceeded, so a guest printing without end cannot fill host memory. The sandbox is kept unless the kill fails.

A missing working directory exits 125 with the shell's message on stderr.

Network access

Every sandbox is created with --deny-network "**". A per-sandbox deny beats every global allow rule and every allow added later, so the sandbox has no network whatever the host's global policy says. A denied raw TCP connection still connects to Docker's proxy and then carries no data.

ALLOWLIST is not declared. Global allow rules apply to every sandbox, including running ones, so an exact allowlist would depend on host state that can change after acquire.

Ownership, cleanup and retention

sbx has no labels, so ownership is in the name: name_prefix, then digests of the conversation, the whole key and the kind. Two creates racing one name get one sandbox and a conflict.

dispose and dispose_scope run sbx rm --force on every name with the matching prefix, taken from sbx ls and from the workspace directories. Afterwards they delete only what was on the host before the removal, never a workspace made after it, since the name is then free for another process to create again. sbx rm accepts only a name, so disposing of one instance checks its id in sbx ls first, then removes by name: another process that replaces the instance in between loses the replacement. The directories are a second record because the daemon can lose its engine and then report no sandboxes at all. That failure surfaces as SbxDaemonFault, which names sbx daemon restart, and a disposal reports it as unreachable rather than as a sandbox that is gone.

Nothing expires on its own. A sandbox left behind by a crashed host keeps its cpus and memory until a disposal or sbx rm removes it. An idle sandbox stops after 30 seconds and keeps its files; the next command starts it again, in 0.5 to 1.2 seconds on the ubuntu-24.04 runner.

Verification

The live suite in tests/test_sbx_e2e.py runs the shared storage-base, FILES_IN, FILES_OUT, FILES_DELETE, RECLAIM, reach and EXEC conformance suites against a real sandbox, and checks by response content that the network is closed. Set MAF_SANDBOX_SBX_E2E=1, and MAF_SANDBOX_SBX_PATH when sbx is not on PATH.

tests/test_sbx_e2e_host.py checks the refusals and faults that depend on host-wide state: SSH agent forwarding, a registered MCP server, a lapsed login, and a daemon that has lost its engine (its docker.sock hidden). Most of these tests change that state and put it back, so they also need MAF_SANDBOX_SBX_E2E_HOST=1. Set it only on a host no one else is using. The login test logs back in with DOCKER_USERNAME and DOCKER_PAT.

Tested with sbx v0.45.1 on Windows 11 and on GitHub's ubuntu-24.04 runner, where the live suite runs nightly. Not tested on macOS. sbx is closed source and in early access, and its behaviour has changed between releases, so re-run the live suite on every sbx version you install.

Metadata

Release files for maf-sandbox-docker-sbx 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for maf-sandbox-docker-sbx 0.2.1
File Size Uploaded
maf_sandbox_docker_sbx-0.2.1.tar.gz 27.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for maf-sandbox-docker-sbx 0.2.1
File Interpreter ABI Platform
maf_sandbox_docker_sbx-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 57.2 kB

Release files / maf_sandbox_docker_sbx-0.2.1.tar.gz

Download URL maf_sandbox_docker_sbx-0.2.1.tar.gz
Size 27.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ac7ac90c25b216e6437a7972371322fca465e4ee7b574c6b3c73029d27dd6e75
BLAKE2b-256 checksum
How to use checksums
04621cbcf49f0a5dd5e1acd906b84cae72d3090d4b270d34da4c571e4a1c8a68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 28, 2026.

Transparency log

Release files / maf_sandbox_docker_sbx-0.2.1-py3-none-any.whl

Download URL maf_sandbox_docker_sbx-0.2.1-py3-none-any.whl
Size 29.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
11b71e635eb28edc8dcd3b70de88d32c7e365196712bb985dae119fa8bcee3ad
BLAKE2b-256 checksum
How to use checksums
368b2c4ff93d3e80c3a2f686cd9935a6cdbab5d22015322eae493b975b36871e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

This release

0.2.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page