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
25 changes: 0 additions & 25 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,5 @@
# Changelog

## 5.1.0 (unreleased)

### Added

- **Capabilities for isolated children** - `py_context:new(#{mode => isolated,
caps => ...})` names the directories, environment variables and network
addresses a child may reach; anything not named is refused. Leaving a key
out grants none of it, and omitting `caps` leaves existing behaviour
unchanged. Paths are resolved a component at a time with `openat` and
`O_NOFOLLOW` from the descriptor of the grant, so `..`, absolute paths,
symlinks out of a grant and symlinked directory prefixes are all refused,
and refusals are `PermissionError` rather than `FileNotFoundError` so they
disclose nothing about what exists outside. Network rules name addresses
and never host names, resolution is its own capability covering every
resolver, and binding is checked against `listen` rather than `connect`.
Process creation, `ctypes`, signals to another process and Unix-socket
addresses are refused outright. The model and its vocabulary come from
erlang_wasm's WASI implementation.

This is a cooperative policy over Python and not a boundary: it is built
on a CPython audit hook, so it binds Python and not a C extension, and it
covers only what CPython announces. `docs/capabilities.md` says what holds
and what does not. Shared memory and capability sets do not combine yet,
because a region reaches the child as a path.

## 5.0.0 (2026-08-29)

### Added
Expand Down
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,21 +622,14 @@ When creating Python contexts, you can choose the execution mode:
%% segfault only takes the child down, rlimits bound memory and CPU.
{ok, Ctx} = py_context:new(#{mode => isolated, kill_after => 1000,
rlimits => #{as => 512 * 1024 * 1024}}).

%% Name what the child may reach, and it reaches nothing else.
{ok, Ctx} = py_context:new(#{mode => isolated,
caps => #{dirs => [{"/srv/models", read}],
net => #{connect => [{tcp, <<"10.0.0.0/8">>, 5432}]}}}).
```

**Isolated mode** is the only mode with a hard bound: `py_context:interrupt/1`
stops a blocking C call, and `SIGKILL` is the backstop. It costs a process per
context (about 16 MB and 40 ms to start) and roughly twice the call latency.
Bulk data crosses through shared memory (`py_shm`, with the optional
[iommap](https://hex.pm/packages/iommap) dependency), and the `caps` option
names the files, addresses and environment the child may reach, as a
cooperative policy over Python rather than a kernel boundary. See
[Isolated Contexts](docs/isolated.md) and [Capabilities](docs/capabilities.md).
[iommap](https://hex.pm/packages/iommap) dependency). See
[Isolated Contexts](docs/isolated.md).

**Worker mode is recommended** because it works with any Python version and automatically benefits from free-threaded Python (3.13t+) when available. Each context owns a dedicated pthread, providing stable thread affinity for libraries with thread-local state (numpy, torch, tensorflow).

Expand Down
238 changes: 0 additions & 238 deletions docs/capabilities.md

This file was deleted.

2 changes: 0 additions & 2 deletions docs/code-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ exercised by suites). Guides are in `docs/`, suites in `test/`. Start with
| `py_channel`, `py_byte_channel` | Term and byte queues between Erlang and Python coroutines (NIF resources) | live | channel | `py_channel_SUITE`, `py_byte_channel_SUITE` |
| `py_buffer` | Native streaming input buffer; shared variant delegates to `py_shm` | live | buffer, isolated | `py_buffer_SUITE`, `py_isolated_buffer_SUITE` |
| `py_shm` | Shared memory regions over iommap and the ring behind shared buffers | live | isolated | `py_isolated_shm_SUITE` |
| `py_caps` | The `caps' option: what an isolated child may reach, and its wire form | live | capabilities | `py_isolated_caps_SUITE` |
| `py_import` | Registry of imports and `sys.path` entries applied to every interpreter | live | imports | `py_import_SUITE` |
| `py_preload` | Code run once per interpreter at start | live | preload | `py_preload_SUITE` |
| `py_state` | Shared key/value store visible from Python as `erlang.state_get/set/delete/keys` | live | README (shared state) | `py_state_SUITE` |
Expand Down Expand Up @@ -81,7 +80,6 @@ loop, channels and servers.
| `_erlang_impl/_etf.py` | Pure-Python ETF codec with the `py_convert.c` mapping | isolated child |
| `_erlang_impl/_isolated.py` | Child runtime: socket frames, reader thread, re-entrant main loop, interrupt signal, asyncio loop, the `erlang` shim | isolated child |
| `_erlang_impl/_shm.py` | `SharedMemory` and `SharedBuffer` wrappers over mmap | all |
| `_erlang_impl/_caps.py` | Capability enforcement in the child: path containment, address matching, the audit hook | isolated child |
| `py_isolated_child.py` | Child launcher: rlimits, parent-death signal, cgroup join, connect | isolated child |
| `test_erlang_loop.py`, `test_async_task.py`, `test_channel_ref.py`, `tests/` | Python-side tests of the loop, tasks and channels | test |

Expand Down
Loading
Loading