Add a real process-boundary backend (backend="process")#267
Merged
Conversation
The sub-interpreter backend is not a boundary against hostile Python: a textbook object-graph walk (recovering an unrestricted __import__ via ().__class__.__base__.__subclasses__()) bypasses the trimmed builtins and the import allow-list and reaches the host. This lands the first layer of a real boundary by running guest code in a separate OS process. - pyisolate/runtime/child.py: fresh-interpreter guest runtime, launched as python -m pyisolate.runtime.child <fd>. Reuses the existing import allow-list / safe-builtins guards and speaks a length-framed JSON protocol over an inherited AF_UNIX socket. - pyisolate/runtime/process_backend.py: ProcessSandbox handle that duck-types the SandboxThread surface the Sandbox wrapper delegates to. Guest->host values are JSON only -- the supervisor must never pickle.loads bytes produced by untrusted guest code. Not-yet-supported handle methods (checkpoint/reset/tracing) raise NotImplementedError rather than AttributeError. - supervisor.py: move "process" into IMPLEMENTED_BACKENDS and route it to a parallel registry so the thread-specific watchdog/warm-pool/cgroup machinery is untouched; tenant-quota accounting is shared. The escape that fully compromises the sub-interpreter backend still runs in the child, but it is confined to that process and cannot touch supervisor memory -- covered by a dedicated test. Kernel confinement of the guest process (no-new-privs, seccomp, rlimits, Landlock, cgroups) follows in later changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDSofWX5HwawsrwbN2nSXR
This was referenced Jul 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The sub-interpreter backend is not a security boundary against hostile Python. A textbook object-graph walk recovers an unrestricted
__import__and reaches the host, bypassing the trimmed builtins and the import allow-list:backend="process"was declared in the API but failed closed as unimplemented. This PR makes it real: guest code runs in a separate OS process, so that escape — and every other in-process trick — is confined to the guest process and can no longer touch the supervisor's address space. This is the first of several layers toward a genuine boundary (kernel confinement follows).What
pyisolate/runtime/child.py— a fresh-interpreter guest runtime (python -m pyisolate.runtime.child <fd>). It reuses the existing import allow-list / safe-builtins guards fromruntime.threadand speaks a length-framed JSON protocol over an inheritedAF_UNIXsocket. A fresh interpreter (notfork) means no supervisor memory or secrets are inherited.pyisolate/runtime/process_backend.py—ProcessSandbox, a handle that duck-types the subset of theSandboxThreadsurface theSandboxwrapper delegates to (exec/call/recv/stop/kill/cancel/reap/is_alive/name/stats). Guest→host values are JSON only — the supervisor must neverpickle.loadsbytes produced by untrusted guest code. Not-yet-supported handle methods (checkpoint/reset/tracing) raiseNotImplementedErrorrather thanAttributeError.supervisor.py—"process"moves intoIMPLEMENTED_BACKENDS; it routes to a parallel registry so the thread-specific watchdog / warm-pool / cgroup machinery is left untouched. Tenant-quota accounting is shared via the existing reservation helpers.Boundary property (tested)
test_object_graph_escape_is_confined_to_the_child_processruns the escape above and asserts it reachesosonly inside the child process (a different PID), never the supervisor. Other tests cover round-trip, import-allowlist denial, exception surfacing, the JSON-only constraint,call, child termination on close,list_active, and thatmicrovmstill fails closed.Scope / follow-ups
This PR establishes the process boundary and transport only. Kernel-level confinement of the guest process is deliberately out of scope here and comes next:
Testing
tests/test_process_backend.py: 13 tests, all passing.🤖 Generated with Claude Code
https://claude.ai/code/session_01DDSofWX5HwawsrwbN2nSXR
Generated by Claude Code