Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ PyIsolate `0.0.x` is a prototype for API, policy, broker, observability, and tes

## Features and roadmap

* **Sub-interpreter sandbox API** — available for prototype development and conformance testing.
* **Sub-interpreter sandbox API** — the API surface is available for prototype development and conformance testing. The backend currently executes guests in a dedicated thread, not a CPython sub-interpreter; see [Sub-interpreter status](#sub-interpreter-status).
* **Import allow-listing and user-space quotas** — available as prototype guardrails; not a complete adversarial security boundary.
* **No-GIL/free-threaded CPython support** — experimental roadmap target for CPython 3.13+ `--disable-gil` builds.
* **Kernel enforcement** — experimental roadmap target; eBPF-LSM, cgroup, and verifier-backed policy enforcement are not guaranteed by the current release.
Expand Down Expand Up @@ -210,6 +210,32 @@ Use `pyisolate.policy.refresh("policy/<name>.yml", token="secret")` to hot‑loa

---

## Sub-interpreter status

`backend="subinterpreter"` does **not** currently use a CPython sub-interpreter.
`pyisolate/runtime/thread.py` runs each guest in a `threading.Thread` and
`exec`s guest source against a restricted `__builtins__` mapping. The backend
carries the name of its intended implementation.

This does not change any security claim in this repository — that backend is
documented throughout as an execution cell and *not* a boundary against hostile
Python, which is equally true of a thread and of a real sub-interpreter. What it
changes is the mechanism you should assume when reasoning about it:

| | thread (today) | sub-interpreter (intended) |
| --- | --- | --- |
| Address space | shared with supervisor | shared with supervisor |
| `sys.modules` | shared with supervisor | per-interpreter |
| Boundary vs hostile Python | none | none |
| GIL | shared | per-interpreter on free-threaded builds |

Landing the real implementation (`concurrent.interpreters` on 3.14, `_interpreters`
on 3.12+) is roadmap work. Until then, treat "sub-interpreter" as the name of an
API mode, not a description of the runtime, and use `backend="process"` for any
guest you do not trust.

---

## Canonical execution model

A cell is intentionally limited to seven operations: `exec`, `call`, `post`, `recv`, `log`, `metric`, and `request`.
Expand All @@ -225,11 +251,14 @@ See [docs/execution-model.md](docs/execution-model.md). We keep this model small
**The boundary is the backend.** Pick the backend to match your trust level:

* **`backend="subinterpreter"`** (default) - an **execution cell**, not a
boundary against hostile Python. The guest runs in a sub-interpreter in the
supervisor's own process; restricted builtins and the import allow-list are
bypassable guardrails (adversarial Python can walk `object.__subclasses__()`
to reach the real `os`/`open`). Use it for **trusted** code, or for scheduling
and organization.
boundary against hostile Python. Today the guest runs in a dedicated
*thread* of the supervisor's own process, with guest code `exec`'d against a
restricted `__builtins__` mapping — **not** in a CPython sub-interpreter; the
backend is named for its intended implementation, which is roadmap work (see
[Sub-interpreter status](#sub-interpreter-status)). Restricted builtins and
the import allow-list are bypassable guardrails (adversarial Python can walk
`object.__subclasses__()` to reach the real `os`/`open`). Use it for
**trusted** code, or for scheduling and organization.
* **`backend="process"`** - the **boundary mode**. The guest runs in a separate
OS process, confined in depth by the kernel before any guest code runs:
* `PR_SET_NO_NEW_PRIVS` + a seccomp deny-list that kills the process on
Expand Down
10 changes: 9 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ normative statement.

## Delivered

- **Backends** — `backend="subinterpreter"` (execution cell) and
- **Backends** — `backend="subinterpreter"` (execution cell; currently a
dedicated thread rather than a CPython sub-interpreter — see below) and
`backend="process"` (the boundary mode): a real separate-process boundary with
`no_new_privs` + a seccomp deny-list, Landlock filesystem rules, Landlock
TCP-egress rules (ABI ≥ 4), a coarse per-cgroup eBPF/LSM deny-mask, and
Expand All @@ -30,6 +31,13 @@ normative statement.

## Now / next

- **Real sub-interpreters for `backend="subinterpreter"`** — the backend is
named for its intended implementation but runs guests in a `threading.Thread`
today, so guests share `sys.modules` and the GIL with the supervisor. Build it
on `concurrent.interpreters` (3.14) / `_interpreters` (3.12+), or rename the
backend to `thread` and let this item own the real thing. Either way the
boundary claim is unchanged: it is an execution cell, not a boundary against
hostile Python.
- **Broker request execution** — the `request` op currently surfaces a
`BrokerRequest` to the host but nothing executes it or returns a result. Add a
request/response round-trip and a pluggable, capability-scoped handler so the
Expand Down
5 changes: 4 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
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.
boundary against hostile Python. Run only trusted code in it. It currently
runs guests in a dedicated thread of the supervisor process, not a CPython
sub-interpreter — see "Sub-interpreter status" in the README. Neither is a
boundary, so nothing below changes.
- `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.
Expand Down
6 changes: 6 additions & 0 deletions docs/execution-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ The public API names the isolation backend explicitly: `backend="subinterpreter"
`subinterpreter` and `process` are implemented; `microvm` is reserved and fails
closed until a launcher is available.

The `subinterpreter` backend currently executes guests in a dedicated **thread**
of the supervisor process rather than a CPython sub-interpreter — the mode is
named for its intended implementation. The cell ABI below is identical either
way, and so is the boundary claim (neither is one). See "Sub-interpreter status"
in the README.

The `process` backend runs guest code in a separate OS process, so in-process
Python escapes (for example recovering an unrestricted `__import__` by walking
`object.__subclasses__()`) can no longer reach the supervisor's address space —
Expand Down
16 changes: 10 additions & 6 deletions docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ PyIsolate's security posture is **not uniform** across backends. Read every
answer below as conditional on the backend you select.

- **`backend="subinterpreter"`** (the default) is an *execution cell*, **not** a
security boundary against hostile Python. Guest code runs in a sub-interpreter
in the supervisor's own process; the restricted builtins and import allow-list
are ergonomic guardrails that adversarial Python can bypass (for example by
walking `object.__subclasses__()` to recover an unrestricted `__import__` and
reaching the real `os`/`open`). Use it for trusted code, or for scheduling and
organization — not to contain code you do not trust.
security boundary against hostile Python. Guest code runs in a dedicated
**thread** of the supervisor's own process — not, despite the backend's name,
in a CPython sub-interpreter; see "Sub-interpreter status" in the README. The
restricted builtins and import allow-list are ergonomic guardrails that
adversarial Python can bypass (for example by walking
`object.__subclasses__()` to recover an unrestricted `__import__` and reaching
the real `os`/`open`). This document's answers are unchanged by that
distinction: neither a thread nor a sub-interpreter is a boundary against
hostile Python. Use it for trusted code, or for scheduling and organization —
not to contain code you do not trust.

- **`backend="process"`** is the boundary mode. The guest runs in a **separate
OS process** (so those in-process escapes can no longer touch supervisor
Expand Down
19 changes: 14 additions & 5 deletions pyisolate/runtime/thread.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
"""Sandbox thread implementation.

This is a greatly simplified placeholder that executes code in a dedicated
thread using the standard interpreter. Real isolation would leverage
sub‑interpreters and eBPF enforcement as outlined in AGENTS.md.
"""Sandbox thread implementation -- the ``backend="subinterpreter"`` runtime.

Despite that backend name, this executes guest code in a dedicated
:class:`threading.Thread` of the supervisor's own process, using the standard
interpreter: guest source is ``exec``'d against a restricted ``__builtins__``
mapping with thread-local policy state. It is **not** a CPython sub-interpreter,
and it is not a security boundary against hostile Python -- adversarial code can
walk ``object.__subclasses__()`` to recover the real ``__import__`` and reach
``os``/``open``. See "Sub-interpreter status" in the README, and
``docs/threat-model.md`` for the normative boundary statement.

Use :mod:`pyisolate.runtime.process_backend` (``backend="process"``) for code
you do not trust. Running guests in real sub-interpreters, and kernel eBPF
enforcement for this backend, are roadmap items tracked in ROADMAP.md.
"""

from __future__ import annotations
Expand Down
Loading