pyplaypen-sandbox
One-shot subprocess sandbox for running Python inside a container.
What it does
Each call forks a fresh child, in its own process group, that:
- runs under a wall-clock timeout that kills the whole process group (
os.killpg), so background descendants cannot outlive the call - applies POSIX rlimits pre-exec: CPU seconds, address space (memory), process count, max file size, open file descriptors
- drops from root to a dedicated non-root UID (Linux exempts root from
RLIMIT_NPROC, so this matters if the parent runs as root) - reaps orphaned grandchildren after teardown, via
PR_SET_CHILD_SUBREAPERon Linux - optionally confines writes to the directories the call owns, with Landlock. No root, no added capability, no container change
- captures stdout/stderr bounded and SHA-256-hashed, and returns the final expression as JSON
- collects workspace files as artifacts, rejecting symlinks and any path that would escape the workspace
- logs a structured audit record per call, naming what this platform enforced, what it applied best-effort, and what it left unsupported
Install
pip install pyplaypen-sandbox
No dependencies, required or optional.
from pyplaypen_sandbox import run
result = await run("1 + 1", artifact_root="./artifacts")
# {"status": "ok", "return_value": 2, ...}
Command line
Installing also puts a pyplaypen executable on PATH. Prefix
any command with it to run that command under enforced limits, with no
code to write.
pyplaypen run --limit wall_seconds=30 -- python script.py
It refuses a limit this machine cannot enforce, and
pyplaypen enforcement prints what each limit enforces here.
Extending it
Nothing about numpy, pandas, httpx, or any other library is built in.
Code that needs to call out to something gets a
globals_provider, an import path to a plain function whose
returned dict becomes names in the exec namespace. A return value this
library cannot serialize gets a type_projector, an import
path of the same form. Both run inside the child, under the same rlimits
as the code. projectors.py ships a numpy and pandas
projector as the worked example.
Lower-level building blocks
Sandbox.run_process(argv, cwd=...) runs an existing program
under the same rlimits, UID drop, process-group teardown and subreaper
reaping as execute(). There is no JSON protocol. You get an
exit code and bounded stdout and stderr. argv accepts a
script, a shell command, or a compiled binary. It runs the target in a
cwd you own and changes nothing about that directory.
pyplaypen_sandbox.privilege holds the functions both are
built from: the rlimits, the UID drop, and Landlock write confinement.
It depends on no other part of this package, and not on asyncio, so a
plain subprocess.Popen(preexec_fn=...) can call it.
For coding agents
This repo is meant to be adapted to what a project needs.
Set up a process sandbox in this project using
https://github.com/lkraider/pyplaypen-sandbox. Read the README fully
first, then decide the integration this project needs.