2026-09-05 20:36:11 +02:00
|
|
|
# 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.
|
|
|
|
|
|
2026-09-06 23:51:50 +02:00
|
|
|
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
|
2026-09-05 20:36:11 +02:00
|
|
|
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
|
|
|
|
|
|
2026-09-05 22:08:18 +02:00
|
|
|
Bwrap refuses `network.default: allow` and setup secret references. Network
|
|
|
|
|
egress is opt-in through the owner allowlist described in bwrap-egress.md;
|
2026-09-06 00:25:07 +02:00
|
|
|
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
|
2026-09-05 20:36:11 +02:00
|
|
|
declared egress list as evidence would not make that list enforced or usable.
|
2026-09-06 00:25:07 +02:00
|
|
|
Exec credential route references contain no values. Nonempty references now
|
|
|
|
|
require an owner-configured, consumer-bound provider; references alone grant nothing.
|
2026-09-05 20:36:11 +02:00
|
|
|
|
|
|
|
|
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.
|
feat: return combined bwrap runtime candidate to Glas
Answer the GLAS-WP-0015 handoff for SAND-WP-0015-T04 without activating any
production path.
Add candidate profile profile.claude-agent-dev-proof v1.1.0 (ext.bwrap,
localhost-only, default: deny, declared api.anthropic.com:443) for GLAS-WP-0012
review. The committed profile grants no egress by itself — ext.bwrap refuses it
unless owner extension config independently allowlists the destination — and a
regression test asserts that fail-closed default.
Reconcile the differing project examples in favour of the acceptance runner's
actor agt / project glas-local-proof, keeping the documented credential route
bound to that single project rather than broadening it.
Record the return contract (profile revision, host scope, consumer tuple,
runtime digest and mount paths, declared egress, value-free denial/cleanup
receipts) in docs/bwrap-runtime.md.
T04 stays wait: Claude credential lane, owner machine authentication, pinned
Claude executable and real-model acceptance remain operator gated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjyScPKb8MV8y2VZHGFSSV
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 716401@bnt-lap001
Assistant-Session: 0d02392b-d4a8-4fed-98e3-32333f768169
2026-09-06 20:34:51 +02:00
|
|
|
|
|
|
|
|
## 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.
|
2026-09-06 23:51:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
## 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.
|
2026-09-08 21:08:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
## Install a reviewed artifact in the local owner's store
|
|
|
|
|
|
|
|
|
|
Use `scripts/install-bwrap-runtime.py --source <absolute-candidate> --sha256
|
|
|
|
|
<reviewed-complete-digest>` from the owner checkout with its Python environment.
|
|
|
|
|
The default store is `~/.local/share/sandboxer/runtimes`, with mode 0700. The
|
|
|
|
|
command accepts only a canonical store beneath that owner's home, checks owner
|
|
|
|
|
and ancestor write permissions, verifies the source, copies into a private
|
|
|
|
|
staging directory, verifies the copy and atomically publishes under its digest.
|
|
|
|
|
It refuses unsafe modes, store aliases and altered existing destinations.
|
|
|
|
|
Reinstalling the same unchanged artifact is idempotent; it never overwrites one.
|
|
|
|
|
|
|
|
|
|
This is owner-controlled local storage. The owner remains able to change its
|
|
|
|
|
files, as in the artifact trust contract above; it is not root-owned storage or
|
|
|
|
|
Railiance placement. Sandboxes still recheck the pin on creation and receive a
|
|
|
|
|
read-only mount. Installation does not select a profile, configure credentials,
|
|
|
|
|
activate network access or enable a schedule. Rollback selects a separately
|
|
|
|
|
reviewed retained digest through owner configuration; no in-place replacement
|
|
|
|
|
or automatic deletion is part of this command.
|
|
|
|
|
|
|
|
|
|
The 2026-09-08 bnt-lap001 installation used the existing combined Claude 2.1.263
|
|
|
|
|
candidate unchanged. Its installed-path startup/lifecycle proof is
|
|
|
|
|
[the local installation receipt](evidence/SAND-WP-0015-protected-local-install-2026-09-08.json).
|
|
|
|
|
SAND-WP-0015-T06 is complete. T04 still owns configured execution, native
|
|
|
|
|
credential adoption, real-model acceptance and production placement.
|
2026-09-09 22:54:39 +02:00
|
|
|
|
|
|
|
|
## Standalone owner candidate
|
|
|
|
|
|
|
|
|
|
`build-rein-runtime.py --owner-runtime` verifies rein's runtime-contract lock,
|
|
|
|
|
then installs non-editable rein/llm/Glas/sandboxer packages from rein's frozen
|
|
|
|
|
uv.lock. The artifact records source pins, package versions and the uv.lock digest.
|
|
|
|
|
Use the existing explicit `--claude-binary/--claude-sha256/--claude-version` pin;
|
|
|
|
|
a rolling workstation alias is not a candidate input. Console shebangs remain
|
|
|
|
|
fixed to the namespace mount; use the candidate interpreter with `-I -B -m` for
|
|
|
|
|
host owner commands. No credential, provider call or installation is part of build.
|
|
|
|
|
|
|
|
|
|
Sandboxer wheels now include profile and extension definitions with packaged-first
|
|
|
|
|
lookup and source-checkout fallback. This closes a concrete local-versus-installed
|
|
|
|
|
owner discrepancy for bwrap. It is not a claim that every remote backend's external
|
|
|
|
|
hosts/templates/tools are packaged or admitted. The ephemeral Messages binding may
|
|
|
|
|
select an exact runtime path/digest; usual overlap and default-deny checks apply.
|
|
|
|
|
|
2026-09-09 23:02:29 +02:00
|
|
|
Required `make check`: lint clean, 205 tests passed. Rein's
|
2026-09-09 22:54:39 +02:00
|
|
|
`scripts/prove-metered-runtime.py` consumes the candidate to test installed imports,
|
|
|
|
|
packaged definitions, explicit one-cycle bootstrap, actual pinned CLI/metered stream,
|
|
|
|
|
insufficient-capacity refusal and teardown against fixtures. The resulting receipt
|
|
|
|
|
belongs to `prj-helixforge-factory/evidence/2026-09-09-owner-bootstrap.json`.
|
|
|
|
|
Production installation and credential-holder/empty-egress profile/placement admission
|
|
|
|
|
remain SAND-WP-0015-T04 and HFACT-WP-0001-T03/T04; no prior profile is promoted.
|
2026-09-09 23:02:29 +02:00
|
|
|
|
|
|
|
|
The first standalone candidate exposed a stale cached local wheel despite current
|
|
|
|
|
Git metadata. Owner builds now refresh all local distributions and compare every
|
|
|
|
|
installed package source/definition file with its tracked source; stale, missing or
|
|
|
|
|
extra files refuse the build. Four content-conformance regressions cover that gap.
|