Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
196 lines
10 KiB
Markdown
196 lines
10 KiB
Markdown
# 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 default Python-only build does not package Claude. The optional explicit
|
|
Claude pin and isolated startup proof are documented in the later candidate
|
|
section below; production authentication remains a separate gate. 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 refuses `network.default: allow` and setup secret references. Network
|
|
egress is opt-in through the owner allowlist described in bwrap-egress.md;
|
|
empty-egress profiles retain loopback-only networking. Credential exec delivery is now opt-in through the owner route contract
|
|
in bwrap-credentials.md; no production route is active. Returning a
|
|
declared egress list as evidence would not make that list enforced or usable.
|
|
Exec credential route references contain no values. Nonempty references now
|
|
require an owner-configured, consumer-bound provider; references alone grant nothing.
|
|
|
|
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.
|
|
|
|
## 2026-09-06 combined-runtime return to Glas (GLAS-WP-0015 handoff)
|
|
|
|
Returned for `GLAS-WP-0012` review of revision 1.1.0. Nothing here is activated.
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Profile | `profile.claude-agent-dev-proof`, version `1.1.0`, `ext.bwrap` |
|
|
| Host scope | `localhost` only (`placement.prefer: [localhost]`, no fallback) |
|
|
| Consumer tuple | `actor: agt`, `project: glas-local-proof`, nonempty `run_id` |
|
|
| Credential route | `glas-claude-agent-dev-anthropic`, exec-env, `ANTHROPIC_API_KEY` |
|
|
| Declared egress | `api.anthropic.com:443`, under `network.default: deny` |
|
|
| Python runtime digest | `4c316737ec2715936a12c4f49621a5e4be3d1f4fe4739130393f28cdda66fbd9` |
|
|
| Runtime mount | read-only `/opt/sandboxer/runtime`, `bin` prepended to child PATH |
|
|
| Runtime sources | rein-aharness `1429db5`, llm-connect `0056094`, Python 3.12.3 |
|
|
| Claude executable | **not pinned** — no path, no digest, no startup proof |
|
|
|
|
The project example is reconciled to the acceptance runner's `glas-local-proof`;
|
|
the route is not broadened to other Glas projects. See bwrap-credentials.md.
|
|
|
|
Denial and cleanup evidence already recorded, all value-free: wrong-project
|
|
credential denial and next-exec key absence in sandbox `0e5fb35a`; undeclared
|
|
`example.com` and direct `1.1.1.1:443` denial with proxy/workspace teardown in
|
|
sandbox `e290e788`; read-only runtime, private 0700 HOME, clean worktree, absent
|
|
source and loopback-only networking in sandboxes `f333fb66` / `d4de9531`. The
|
|
committed profile declares egress but grants none: `ext.bwrap` refuses it unless
|
|
the owner extension config independently allowlists the destination, and a
|
|
regression test asserts that fail-closed default.
|
|
|
|
Still operator-blocked, so this profile stays unready: the concrete Claude
|
|
credential lane and its delivery/revocation contract (SECRETS-WP-0009 upstream
|
|
of CCR-2026-0016), owner machine authentication, the pinned Claude executable
|
|
with its own HTTPS_PROXY startup proof, and the real-model acceptance run.
|
|
Provider workspace scope and spend limit remain operator inputs; a model
|
|
`budget_tokens` setting is not a provider spend limit.
|
|
|
|
|
|
## Pinned Claude candidate and isolated startup — 2026-09-06
|
|
|
|
The builder now accepts `--claude-binary`, `--claude-sha256` and
|
|
`--claude-version` together. It requires a regular native ELF file, verifies the
|
|
exact copied bytes, refuses symlinks/wrappers/mismatches and existing destination
|
|
files, and copies only the executable as bin/claude mode 0755. The expected
|
|
version is metadata until the sandbox smoke checks the actual output. No
|
|
interactive HOME, provider key or login state is copied.
|
|
|
|
Candidate provenance: the explicitly selected installed native release
|
|
`/home/worsch/.local/share/claude/versions/2.1.263`, SHA-256
|
|
`26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba`.
|
|
This is a digest pin of the selected local artifact, not an upstream signature
|
|
verification claim. Complete runtime candidate:
|
|
`/tmp/sandboxer-claude-runtime-20260906`, SHA-256
|
|
`5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`.
|
|
Mount remains `/opt/sandboxer/runtime`; Claude is `/opt/sandboxer/runtime/bin/claude`.
|
|
Rein sources remain 1429db5 / llm-connect 0056094; resolved dependency versions
|
|
are recorded in the evidence. Rebuilding may resolve different dependencies;
|
|
the complete digest pins this artifact, not future builds.
|
|
|
|
Sandbox f8821ec2 ran real `claude --version` -> `2.1.263 (Claude Code)` plus rein
|
|
CLI/import checks, with owner allowlisted provider egress and HTTPS_PROXY set
|
|
by the broker. Runtime read-only, private 0700 HOME/state persistence, clean Git,
|
|
absent source and loopback-only interfaces passed; workspace and proxy removed.
|
|
`make check`: lint passed, 175 tests passed.
|
|
|
|
Evidence: docs/evidence/SAND-WP-0015-claude-startup-2026-09-06.json.
|
|
Reproduce with the builder options above and `scripts/smoke-bwrap-runtime.py
|
|
--runtime-path <candidate> --runtime-sha256 <digest> --claude-version 2.1.263`.
|
|
|
|
Limits: `--version` proves startup with the proxy environment, not that Claude
|
|
has sent a provider request through it. No credential acquisition or model run.
|
|
The /tmp candidate is not production placement; protected artifact installation,
|
|
owner configuration, credential adoption and real-model proof remain T04 gates.
|