python3 script.py need an interpreter. Mirage calls that
interpreter a runtime, and you pick it per workspace. Four runtimes
ship today:
The first two bridge file I/O through the workspace, so code sees your
mounts and nothing else. The last two run real CPython on the host and see
the host filesystem instead —
sandlock bounded to the paths you name,
local unbounded.
Monty (default)
Monty is a minimal, secure Python interpreter built for running AI-generated code: microsecond startup, no host filesystem, environment, or network access. Mirage wires Monty’s OS interface to the workspace dispatch, so code running underpython3 sees the workspace mounts, and
nothing else:
pathlib traversal, and os.environ all route through the
mounts. A python3 write is immediately visible to cat, and works the
same whether the mount is RAM, Redis, S3, or a virtual backend like
MongoDB’s database.json.
Monty requires the monty extra:
Concurrency
Runs execute on Monty’s own worker pool and are fully async: the event loop stays free, and cancelling a run halts the interpreter. One ceiling to know about when a server runs many I/O-heavy scripts at once: each run waiting on workspace I/O holds one pool worker (the Python binding only takes synchronous OS callbacks, pydantic/monty#560), and the pool defaults to the machine’s core count. SetTOKIO_WORKER_THREADS to lift it; waiting workers cost kilobytes of
stack and no CPU, so 100 parallel I/O-bound scripts on one core is a
TOKIO_WORKER_THREADS=100 away.
Without it, python3 exits with code 127 and an install hint; every other
command keeps working.
Differences from CPython
Monty implements a Python subset. Notably:- Command-line arguments are the
argvglobal (argv[0]is the script name);sys.argvdoes not exist. - No
sys.stdin: piping intopython3needs thewasiorlocalruntime. - No third-party imports (
numpy,requests, …). - File handles are not iterable (
for line in fraisesTypeError);f.read(),f.readline(), andf.readlines()all work. os.environreflects the session env only, never the host’s.
WASI
Thewasi runtime runs a real CPython, compiled to WebAssembly, inside
your process on wasmtime. It fills monty’s gaps (classes, the complete
stdlib, sys.argv, sys.stdin) while staying sandboxed: host files
and network sockets are invisible, and the run sees the interpreter’s
own build directory plus the workspace mounts.
Mirage intercepts the sandbox’s filesystem calls and bridges them
through the workspace dispatch, so open('/data/f.txt') inside the
code reads and writes the mount live — RAM, Redis, S3, or a virtual
backend — with the same cache, write modes, and per-session mount
narrowing as cat. Guest writes are immediately visible to every
command, a read-only mount (or a session narrowed to read) answers
PermissionError at open(), and none of this needs FUSE, a config
key, or any setup. The interpreter’s own build directory is served
read-only from the host; a mount at / coexists with it (mount
prefixes win for their subtrees, the build serves the rest).
It needs the wasi extra plus a CPython WASI build (download and
unzip a release from
cpython-wasi-build):
home on the
runtime entry (or the MIRAGE_WASI_HOME environment variable):
python.wasm (a few hundred milliseconds) and
caches the compilation as python.cwasm next to it; runs after that
boot in milliseconds. The build directory is read-only inside the
sandbox: guest writes land in the workspace or answer PermissionError,
never in the bundle.
Local
Thelocal runtime runs a host interpreter as a subprocess: full
CPython, host filesystem, host environment. Use it when the code needs
stdin, third-party packages, or host files:
home on the runtime entry (or the MIRAGE_LOCAL_HOME environment
variable) at another binary, e.g. a project venv whose packages the
code needs:
local code does not see workspace mounts; it sees the real
host filesystem.
For setup and its host-path security model, see the dedicated
Sandlock page under Sandbox.
Selecting in YAML
Workspace config files take a top-levelruntimes list: the ordered
world of runtimes, each entry a name or a mapping with the uniform
runtime options (captures, config, script). The first entry that
captures a command binds it; vfs is the in-process catch-all:
home
option locates the runtime’s interpreter or distribution, in the
spirit of JAVA_HOME: a build directory for wasi, an interpreter path
for local, a distribution URL for pyodide (TypeScript); it also
falls back to a MIRAGE_<RUNTIME>_HOME environment variable. Only
the selected runtime consumes its block and it rejects option keys it
does not know, so one config stays portable across runtimes and
languages. monty embeds its interpreter and has no options yet.
Resource limits
python3 is a command like any other: the same command_limits
blocks that guard cat or grep guard it, enforced at the same
central point. A run that exceeds timeout_seconds answers with exit
124 and python3: timed out after Ns on stderr, exactly like any
other command; max_bytes and max_lines cap its output the same
way. There is no python3-specific limit surface.
Firing the guard also cancels the run, and every runtime honors
cancellation: monty kills its worker process, wasi traps the run
through wasmtime’s epoch interruption, and local kills the
subprocess. A timed-out python3 run is reclaimed at the deadline,
not left burning in the background.