Canonical boundary definition: see
docs/threat-model.md(normative baseline).
The security boundary depends on the backend you choose. This is the single most important thing to understand before deploying PyIsolate:
backend="subinterpreter"(the default) is an execution cell, not a boundary against hostile Python. Run only trusted code in it.backend="process"is the boundary mode: the guest runs in a separate OS process confined in depth by the kernel.backend="microvm"is reserved and not yet implemented.
| Actor | Capabilities | Goal |
|---|---|---|
| Guest attacker | Executes arbitrary Python bytecode inside a sandbox. | Escalate into the supervisor or host OS; read/modify other sandboxes' data; exhaust shared CPU/RAM/I/O. |
| Network adversary | Can capture, replay, or inject broker frames over IPC/TCP. | Hijack privileged RPCs; tamper with or spoof audit trails. |
| Malicious extension author | Crafts a wheel with native code to break isolation. | Load arbitrary ELF, bypass policies. |
We assume the kernel and hardware are trusted and un-compromised. Kernel exploits and speculative-execution side channels are out of scope (see §4).
The guarantees below apply to backend="process". The sub-interpreter
backend provides none of them against adversarial Python — its restricted
builtins and import allow-list are bypassable guardrails, not a boundary.
Each kernel layer is applied best-effort and recorded in the sandbox's confinement report; a host that lacks a layer keeps the others. In hardened rollout mode, a required-but-unavailable layer fails closed rather than degrading silently.
- Address-space isolation — Guest code runs in a separate process and cannot
read or corrupt supervisor or sibling-sandbox memory. In-process Python
escapes (e.g. recovering the real
__import__viaobject.__subclasses__()) stay inside the guest process. - Syscall reduction —
PR_SET_NO_NEW_PRIVSplus a seccomp deny-list (SECCOMP_RET_KILL_PROCESS) kills the guest on dangerous syscalls:execve,ptrace, mount/namespace operations,bpf, kernel-module load,process_vm_readv/writev, and others. x86-64 Linux. This is a robust deny-list, not a proof that only a fixed syscall allow-list is reachable. - Filesystem policy — Landlock confines the guest to the policy's read/write paths, on kernels that support Landlock.
- Coarse capability gating — A per-cgroup eBPF/LSM
deny_maskdenies whole capability classes (process creation, ptrace/mount/bpf, and filesystem or network when the policy grants nothing in that class), on kernels with BPF-LSM. This is coarse: it cannot express per-path allow-lists — that is Landlock's job (§2.3) and the broker's. - Broker integrity & replay protection — Control-plane frames are AEAD-sealed (X25519 → HKDF-SHA-256 → ChaCha20-Poly1305, optional Kyber-768 hybrid) with a strictly increasing per-direction counter; forged, reordered, or replayed frames are rejected.
- Crash isolation — A crash in a guest process cannot bring down the supervisor.
Resource quotas (CPU/RAM/I/O) are enforced by rlimit and cgroup v2 controls
where available. Anything not listed above is not guaranteed.
The guest runs in a fresh interpreter in its own OS process, speaking a
length-framed JSON protocol to the supervisor. Values crossing the boundary are
JSON only — the supervisor never unpickles bytes produced by untrusted guest
code. Confinement is applied before any guest code runs, in order:
no_new_privs → rlimit → Landlock → seccomp.
- seccomp deny-list — kills the process on high-risk syscalls; inherited
across
fork/cloneand irremovable onceno_new_privsis set. - Landlock — filesystem access restricted to the policy's paths (kernels with Landlock support).
- eBPF/LSM — a per-cgroup
deny_maskonfile_open,socket_connect,task_alloc,bprm_check_security,ptrace,sb_mount, andbpfhooks, keyed by the sandbox's cgroup id (kernels with BPF-LSM). - cgroup v2 / rlimit — CPU, memory, and address-space caps; a supervisor
watchdog reads the
resource_guardring buffer and terminates sandboxes on quota breach (fail-closed if the ring stalls).
- X25519 key agreement (optional Kyber-768 hybrid) → HKDF-SHA-256, direction- separated keys.
- Per-frame ChaCha20-Poly1305 (96-bit counter nonce, 16-byte tag).
- Strictly increasing per-direction counter; a rollback or gap rejects the frame.
- Keys rotate by repeating the handshake; counters reset on success.
Import allow-listing and blocked builtins (open, eval, exec, …) are applied
in every backend. Treat them as defense-in-depth and ergonomics, not as the
boundary: in the sub-interpreter backend they are the only layer and are
bypassable.
The following are targets, not present guarantees, and must not be relied on:
no-GIL/free-threaded per-sandbox parallelism, per-interpreter allocator arenas,
CPython built with -fstack-protector-strong/-fsanitize=cfi, path-aware
(rather than coarse) eBPF filesystem/network matching, and the microVM backend.
| Item | Rationale / mitigation |
|---|---|
Hostile Python under backend="subinterpreter" |
Not a boundary; use backend="process" or an external VM/container. |
Hostile native extensions (ctypes, cffi, dlopen, native wheels) |
Deny by default; only allow vetted code. Native code can subvert interpreter-level assumptions. |
| Kernel exploits / verifier bypass | Run inside a VM or microVM if the attacker is assumed to have 0-day power. |
| Side-channel leakage (cache, branch predictor, Spectre) | Use one process per tenant on highly sensitive workloads; PyIsolate adds no microarchitectural mitigations. |
| Supervisor compromise | The model is broken for all sandboxes in that process; no recovery guarantee. |
| Missing kernel features | Each confinement layer degrades independently; use hardened rollout mode to fail closed instead. |
- Sub-interpreter — execution cell only; not a boundary against hostile code.
- Thread — scheduling/accounting unit.
- Process + kernel policy (
backend="process") — the production security boundary. - Container / microVM — stronger boundary and recommended for hostile multi-tenant environments.
- seccomp deny-list: x86-64 Linux.
- Landlock: Linux kernels that expose the Landlock ABI (skipped otherwise).
- eBPF/LSM
deny_mask: kernels with BPF-LSM and cgroup v2. - Where a feature is unavailable, that layer is skipped and recorded in the confinement report; the remaining layers still apply.
Use python -m pyisolate.conformance --grade (or pyisolate doctor --grade) to
see which guarantees are actually active on a given host.
pyisolate-doctor records installation provenance (Python build hash, no-GIL
status, kernel features, BPF toolchain availability, deterministic-wheel policy
flags). Reproducible-build, artifact-signing, and eBPF-bytecode-pinning
workflows are roadmap items; do not assume signed or reproducible artifacts
unless your own pipeline provides them.
Please report suspected vulnerabilities through the repository's private security advisory process on GitHub rather than a public issue. Include a minimal reproduction and the affected backend and kernel features. We aim to acknowledge promptly and to disclose after a fix is available.
Q: Why not seccomp only? A: seccomp filters syscall numbers/arguments but not paths. Landlock adds path-scoped filesystem enforcement, and eBPF/LSM adds per-cgroup capability gating. The layers are complementary.
Q: Is the sub-interpreter backend safe for untrusted code? A: No. Use
backend="process", ideally inside a VM or microVM for hostile multi-tenant workloads.
Q: Can I disable the broker and talk directly to the FS? A: No. That defeats the trust boundary. Route the operation through an approved broker capability.