Skip to main content

Cloche

Cloche runs a program in an unprivileged Linux sandbox. It is a Python library first, with a small command on top. It needs no root, no daemon and no container image: the program sees only the host paths you bind into the sandbox, read-only, and a few empty writable directories. When the program succeeds, the outputs you declared are copied out. The same sandbox can also run under a rootless OCI runtime, or in a small virtual machine with its own kernel for a stronger boundary.

Cloche began as the build sandbox of PyrixOS and its package manager Lattice, and is published on its own so any project can use it: build tools, test runners, graders for submitted code, or anything else that must run a program it does not trust with nothing but the inputs it was given.

Inside the sandbox the program:

  • runs as root of a new user namespace, mapped to the calling user, with no capabilities and no_new_privs set;
  • has its own mount, PID, network, UTS and IPC namespaces, with only a loopback interface and the host name localhost;
  • sees a new root that holds only the binds, the writable directories, /build (its working directory), /proc, a minimal /dev and /dev/shm;
  • cannot remount, unmount or write to a bind;
  • runs under a seccomp filter that denies setting setuid and setgid bits, extended attributes, keyrings and io_uring, and kills the program on any other system call ABI, such as x32;
  • gets a fixed environment: SOURCE_DATE_EPOCH=315532800, TZ=UTC, LANG=C.UTF-8, HOME=/homeless, TMPDIR=/build and PATH=/path-not-set, plus the variables you set;
  • has its core dumps disabled, a limit on open files, and optional wall-clock and silence time limits;
  • ends with its sandbox: every process left behind is killed.

Outputs are copied out without following symbolic links, and setuid and setgid bits are dropped. A special file in an output fails the run.

Status

Stable since 1.0.0. Three backends run the same sandbox:

Backend Runs the program in Needs
namespaces (default) Linux namespaces Cloche creates itself Unprivileged user namespaces
oci A rootless OCI runtime: crun, runc or youki The runtime, and unprivileged user namespaces
microvm A virtual machine with its own kernel, under Firecracker, Cloud Hypervisor or OpenVMM KVM, the monitor, mkfs.ext4, a guest kernel and the guest init

The namespace backend works on x86_64 and aarch64 Linux. The OCI backend is tested with runc and crun, and the microVM backend on x86_64 with Firecracker and Cloud Hypervisor. OpenVMM support follows its documented command line and is untested. The coverage floor is 80%.

Install

To use Cloche in another project:

pip install cloche

or add cloche>=1.0,<2 to the project's dependencies. Version 1 keeps the names in cloche.__all__, the fields of its models and the guarantees above stable for every 1.x release.

To work on Cloche itself:

python -m pip install --upgrade "pip>=25.1"
pip install --group dev
pip install --config-settings editable_mode=strict -e .
pybootstrap check

Python 3.12 or later is required. The only runtime dependency is Pydantic 2. The dev dependency group installs pybootstrap and the tools its gates drive: ruff, pyright, and pytest with coverage. The tests in tests/test_run.py run real sandboxes on every backend; they need unprivileged user namespaces and a C compiler that links statically, and are skipped otherwise. The OCI cases also need a runtime, and the microVM cases run only when CLOCHE_TEST_KERNEL names a guest kernel and the monitor is in PATH.

Run cloche status to see whether the sandbox can run on a machine and, if not, why. On Ubuntu 23.10 and later, AppArmor may restrict unprivileged user namespaces; the reason then names the sysctl involved.

Releases are made by pushing a tag vX.Y.Z that matches __version__ and has a section in CHANGELOG.md. The release workflow runs the gates, builds the package, publishes it to PyPI through trusted publishing with attestations, and creates the GitHub release.

Use

import tempfile

from cloche import Bind, Command, Limits, run

command = Command(
    name="hello",
    program="/usr/bin/sh",
    args=("-c", "echo hello > /build/greeting"),
    binds=tuple(Bind(source=p, destination=p) for p in ("/usr", "/lib", "/lib64")),
    outputs={"greeting": "/build/greeting"},
)
outcome = run(command, tempfile.mkdtemp(), Limits(timeout=60))
print(open(outcome.outputs["greeting"]).read())  # hello

run takes a Command, an empty work directory and optional Limits. It returns an Outcome with the host path of each output, the path of the log, and the run time. The log holds everything the program wrote to its standard output and error; pass on_log to receive it as it arrives.

Every field is checked by Pydantic when the model is created, and make_spec checks how the fields fit together before anything starts:

Field Meaning
name Label used in the log and in errors
program Absolute path of the program inside the sandbox; must be inside a bind
args Arguments after the program
env Variables to set; the fixed ones above cannot be changed
binds Host paths mounted read-only, each at its own destination
directories Empty writable directories, in addition to /build
outputs Name to path; each path must be below /build or a directory

A bind whose source is a symbolic link appears as that link, not as the path it names. Two mounts cannot share a path, a bind or directory cannot sit inside a bind, and /proc, /dev and /build are reserved.

Errors derive from ClocheError:

Error Raised when
InvalidCommand The command breaks a rule; nothing was started
RunFailed The program failed, timed out or left an output missing; the message ends with the last lines of the log
SandboxUnavailable The chosen backend cannot run on this machine

Lower down, make_spec turns a Command into a SandboxSpec, the exact list of mounts, ID maps, limits and seccomp rules a backend applies, and oci_config turns a spec into an OCI runtime configuration. backend_status reports which backends can run here, and select_backend returns one or raises; Cloche never falls back to a weaker backend.

Other backends

run uses the namespace backend unless backend names another:

from cloche import MicroVMConfig, run, select_backend

oci = select_backend("oci", runtime="crun")
vm = select_backend(
    "microvm",
    microvm=MicroVMConfig(vmm="firecracker", kernel="/srv/vm/vmlinux", init="/srv/vm/init"),
)
outcome = run(command, tempfile.mkdtemp(), Limits(timeout=60), backend=vm)

The OCI backend writes the configuration oci_config returns into a bundle and runs the runtime's create, start and delete as the calling user. No daemon and no image are involved.

The microVM backend boots a virtual machine with no network device. The guest kernel is an uncompressed vmlinux with virtio block, virtio vsock, ext4, tmpfs and devtmpfs built in, such as the guest kernels Firecracker publishes. The guest init is a small static C program shipped with Cloche; cloche guest-init OUTPUT or build_guest_init(OUTPUT) compiles it. The binds are copied, hard-linked where possible, into a read-only ext4 image, so every file in them must be readable by the caller. The program runs as root of the guest with the same mounts, seccomp filter, limits and environment as on the other backends, without capabilities and with /proc/sys, /proc/sysrq-trigger, /proc/bus, /proc/fs and /proc/irq mounted read-only. Its outputs come back as a newc archive on a sparse output device of output_mib MiB and are extracted with the same rules as the other backends; a single file cannot exceed 4 GiB. Outcome.boot_seconds is the time from starting the monitor to the guest init connecting back over vsock.

The command

cloche run --bind /usr --bind /lib --bind /lib64 --output greeting=/build/greeting \
    /usr/bin/sh -c 'echo hello > /build/greeting'
cloche oci-config --bind /usr /usr/bin/true
cloche status
cloche guest-init init
cloche run --backend microvm --kernel vmlinux --init init --bind /usr ... /usr/bin/true

cloche run streams the log to standard error and prints one line per output: its name and the host path it was copied to. --bind SOURCE binds a path at the same place; --bind SOURCE:DESTINATION moves it. --dir, --env NAME=VALUE, --name, --timeout, --max-silent-time, --max-open-files and --work DIR map to the fields above. Everything after the program is passed to it. Since PATH is fixed, name programs by their full path, or run /usr/bin/env PATH=/usr/bin .... --backend chooses namespaces, oci or microvm; --runtime names the OCI runtime, and --vmm, --vmm-path, --kernel, --init, --memory and --cpus configure the virtual machine. cloche status accepts the same backend options.

Layout

Module Role
models.py Command, Bind and Limits, the Pydantic models a caller fills in
spec.py make_spec, the SandboxSpec model and the OCI runtime configuration
environment.py The fixed environment
seccomp.py The seccomp filter, as rules and as a packed BPF program
namespaces.py The namespace backend: the helper that builds the sandbox and copies outputs out
oci.py The OCI backend: the bundle and the helper that drives the runtime
microvm.py The microVM backend: the input image, the initramfs, the monitor and the output archive
guest_init.c The guest init of the microVM backend
newc.py Writing and strictly reading newc cpio archives
runner.py run: supervises the helper, captures the log and enforces time limits
backends.py The Backend protocol and select_backend
probe.py backend_status and the user namespace probe
libc.py The few system calls Python does not expose
errors.py ClocheError and its subclasses
cli.py The cloche command

License

GPL-2.0-only. See LICENSE.

Metadata

Release files for cloche 1.0.0

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

Source distribution (sdist)

Source distribution for cloche 1.0.0
File Size Uploaded Provenance Actions
cloche-1.0.0.tar.gz 70.6 kB With provenance Details for cloche-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for cloche 1.0.0
File Interpreter ABI Platform Actions
cloche-1.0.0-py3-none-any.whl Python 3 none any Details for cloche-1.0.0-py3-none-any.whl

Total release size: 132.7 kB

Release files / cloche-1.0.0.tar.gz

Download URL cloche-1.0.0.tar.gz
Size 70.6 kB
Tags Source
SHA-256 checksum
How to use checksums
5c428278a6975bcfbce97eddd144bfcc1d47621d9784a9f12bd1359657518cec
BLAKE2b-256 checksum
How to use checksums
2f5fd8d247e7eb0db9048d9ab879937e342817fe8859386064416e3cc060f71d
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 Oct 29, 2026.

Transparency log

Release files / cloche-1.0.0-py3-none-any.whl

Download URL cloche-1.0.0-py3-none-any.whl
Size 62.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f3bc0f602af1ff6cc5f687c61fb82ec03c17b28290f848d8ba6aafcff26670e9
BLAKE2b-256 checksum
How to use checksums
01b1be395a3ad77b2aa2a161a449102e4c72ccd119a5fba005f57f16035d91da
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 Oct 29, 2026.

Transparency log

Security for cloche 1.0.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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for cloche-1.0.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 Oct 29, 2026.

Transparency log

A 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.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

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