feat: pin bwrap rein runtimes and isolate private state
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
This commit is contained in:
parent
c2886b2f77
commit
d69827aaa2
12 changed files with 639 additions and 9 deletions
123
docs/bwrap-runtime.md
Normal file
123
docs/bwrap-runtime.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Bwrap runtime and private state
|
||||
|
||||
`ext.bwrap` can mount a standalone Python runtime selected by the owner's
|
||||
extension configuration. A create/exec caller cannot select host paths or
|
||||
replace the sanitized child environment.
|
||||
|
||||
```yaml
|
||||
config:
|
||||
runtime:
|
||||
path: /srv/sandboxer/runtimes/rein-aharness-example
|
||||
sha256: <reviewed-complete-artifact-digest>
|
||||
```
|
||||
|
||||
The path must be a canonical absolute directory with `pyvenv.cfg` and
|
||||
`bin/python3`. Its complete tree digest includes file contents, modes, directory
|
||||
entries, and internal symlinks. Absolute/escaping symlinks and special files
|
||||
are refused. Missing files, added files, changed permissions, or changed
|
||||
content invalidate the pin. The runtime must not overlap the source checkout
|
||||
or writable sandbox workspace.
|
||||
|
||||
The owner mounts the artifact read-only at `/opt/sandboxer/runtime` and prepends
|
||||
its `bin` directory to the child PATH. Console entrypoints must use that fixed
|
||||
mount prefix. Artifacts remain an owner trust boundary: publish them in a
|
||||
protected location and do not mutate them while sandboxes use them. Digest
|
||||
validation occurs at creation; it is not a mechanism for making a mutable host
|
||||
directory immutable to its owner.
|
||||
|
||||
## Building and testing the Python rein bundle
|
||||
|
||||
From this repository, with committed rein/llm-connect source checkouts:
|
||||
|
||||
```bash
|
||||
uv run python scripts/build-rein-runtime.py \
|
||||
--output /tmp/rein-runtime-candidate \
|
||||
--rein-source ../rein-aharness \
|
||||
--llm-source ../llm-connect
|
||||
```
|
||||
|
||||
The builder creates a standalone system-Python venv, installs both packages
|
||||
non-editably with their dependencies, rewrites Python console entrypoint
|
||||
shebangs to the fixed mount path, and records source revisions plus resolved
|
||||
package versions in `build-info.json`. It prints the resulting path/digest.
|
||||
Dependency versions are resolved during this candidate build; the digest pins
|
||||
the produced artifact, not a promise that another build will be bit-identical.
|
||||
No interactive home, credentials, or provider login is copied into the bundle.
|
||||
|
||||
Prove the artifact using the returned values:
|
||||
|
||||
```bash
|
||||
uv run python scripts/smoke-bwrap-runtime.py \
|
||||
--runtime-path /tmp/rein-runtime-candidate \
|
||||
--runtime-sha256 <returned-digest>
|
||||
```
|
||||
|
||||
The smoke uses an isolated owner extension configuration and the real broker.
|
||||
It imports the rein's Claude adapter and llm-connect, runs `rein-aharness
|
||||
--help`, proves the runtime rejects writes, writes private state outside the
|
||||
worktree, verifies source absence and a clean Git tree, checks loopback-only
|
||||
networking, and tears down. This is real CLI startup, not a model task.
|
||||
|
||||
The Python bundle does not package or pin the separate Claude executable.
|
||||
That executable's release, startup behavior, and authentication still require
|
||||
review in the eventual production runtime. No committed profile selects a
|
||||
temporary `/tmp` build as its production runtime.
|
||||
|
||||
## Private writable state
|
||||
|
||||
The broker creates mode-0700 directories in the namespace's temporary root:
|
||||
|
||||
| Environment variable | Location |
|
||||
|---|---|
|
||||
| `HOME` | `/run/sandboxer/state/home` |
|
||||
| `XDG_CONFIG_HOME` | `/run/sandboxer/state/config` |
|
||||
| `XDG_CACHE_HOME` | `/run/sandboxer/state/cache` |
|
||||
| `XDG_STATE_HOME` | `/run/sandboxer/state/data` |
|
||||
| `TMPDIR` | `/run/sandboxer/state/tmp` |
|
||||
|
||||
These directories persist between exec requests in the same sandbox, remain
|
||||
outside the Git worktree, and disappear with namespace teardown. Python user
|
||||
site packages and bytecode writes are disabled. The owner's child environment
|
||||
continues to carry exact consumer identity and value-free route references.
|
||||
|
||||
## Network and credentials remain separate gates
|
||||
|
||||
Bwrap now refuses a profile with `network.default: allow`, nonempty egress, or
|
||||
setup secret references. The implementation currently provides only a
|
||||
loopback-only network and no credential-delivery mechanism. Returning a
|
||||
declared egress list as evidence would not make that list enforced or usable.
|
||||
Exec credential route references continue to be labels, not credential values
|
||||
or delivery grants.
|
||||
|
||||
On 2026-09-05, `warden route find anthropic --json` and `warden route find
|
||||
claude-code --json` returned no matching workload routes. The generic OpenBao
|
||||
entry is explicitly a routing template, not an executable credential lane.
|
||||
The OpenRouter AppRole route belongs to the other rein and is not a substitute
|
||||
for Claude authentication.
|
||||
|
||||
`SAND-WP-0015-T04` / `SAND-WP-0014-T05` and `GLAS-WP-0012-T02` retain the
|
||||
production requirements: a concrete owner-approved Claude credential route,
|
||||
delivery and revocation semantics, a pinned Claude runtime, and explicitly
|
||||
enforced provider egress with negative tests. Glas local profiles remain
|
||||
blocked until those requirements and the real-model acceptance pass.
|
||||
|
||||
## 2026-09-05 candidate evidence
|
||||
|
||||
Runtime SHA-256:
|
||||
`4c316737ec2715936a12c4f49621a5e4be3d1f4fe4739130393f28cdda66fbd9`.
|
||||
Candidate path: `/tmp/sandboxer-rein-aharness-runtime-20260905-v2`.
|
||||
Source revisions: rein-aharness `1429db5ad4c83331b6375349ffde1eb13af9575b`,
|
||||
llm-connect `00560945f81ba6ff1f5cacd9fe99c7fe756cc4b1`; Python 3.12.3.
|
||||
|
||||
Sandbox `f333fb66` passed the real CLI/import/read-only/private-state smoke.
|
||||
`sys.prefix` was `/opt/sandboxer/runtime`, HOME was outside the worktree with
|
||||
mode 0700, Git stayed clean, source was absent, only `lo` existed, credential
|
||||
references were empty, and the workspace was removed. No model request or
|
||||
credential acquisition occurred.
|
||||
|
||||
Final validation: `make check` passed lint and 132 tests. Authenticated owner
|
||||
API smoke `223db65b` returned HTTP 200 with exact identity/stdin and complete
|
||||
teardown. Follow-up runtime smoke `d4de9531` repeated the real CLI/startup
|
||||
checks and proved private state survives a second exec in the same namespace.
|
||||
The default unconfigured `profile.bwrap-local` still has no selected rein
|
||||
bundle; candidate startup does not constitute production deployment.
|
||||
Loading…
Add table
Add a link
Reference in a new issue