diff --git a/SCOPE.md b/SCOPE.md index 337301a..90382e7 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -121,13 +121,14 @@ own tunnels or CAs. - **Package:** `src/sandboxer/` — CLI, manager, extensions, routing, payments, snapshots, telemetry, HTTP API - **Profiles:** compose e2e/checkpoint, canary, vm-haskell-build, saas-stub, - burst-sandbox, e2b-burst, modal-gpu, agent-dev, build, vm-packer-build + burst-sandbox, e2b-burst, modal-gpu, agent-dev, build, vm-packer-build, + bwrap-local, claude-agent-dev-proof (candidate, not production) - **Extensions:** `ext.compose-ssh`, `ext.vm-packer`, `ext.saas-stub`, `ext.e2b`, `ext.modal` - **Docs:** `meta-framework`, `extension-sdk`, `host-telemetry`, `routing`, `payments`, `snapshots`, `migration-gaps`, `migration-build-machines` - **Registry:** `capability.execution.sandbox-provision` indexed (draft) -- **Tests:** 90 pytest cases; `make check` green +- **Tests:** 172 pytest cases; `make check` green - **Siblings:** wise-validator `validate run` (SAND-WP-0003); the-custodian `make e2e REPO=` shim (SAND-WP-0004) diff --git a/docs/bwrap-credentials.md b/docs/bwrap-credentials.md index 1a84bbe..030300d 100644 --- a/docs/bwrap-credentials.md +++ b/docs/bwrap-credentials.md @@ -20,7 +20,7 @@ Owner extension configuration has this shape (proposal only): credential_routes: glas-claude-agent-dev-anthropic: profiles: [profile.claude-agent-dev-proof] - projects: [glas-harness] + projects: [glas-local-proof] actors: [agt] exec_argv: - /absolute/owner/venv/bin/python @@ -38,6 +38,13 @@ credential_routes: - -- ``` +The consumer tuple is exactly `actor: agt`, `project: glas-local-proof`, +`profile.claude-agent-dev-proof` — the identity the Glas real acceptance runner +presents. The earlier `glas-harness` example in this document was a generic +consumer illustration, not a reviewed binding, and is superseded here. The route +stays bound to that single project; it is not broadened to every Glas project, +and `glas-harness` remains only a generic consumer name in unrelated fixtures. + Do not install this example until the exact provider runtime, service identity, approval contract and profile exist and are reviewed. No production route is configured by this change. Owner config is trusted executable configuration; diff --git a/docs/bwrap-runtime.md b/docs/bwrap-runtime.md index 43a6ddc..63c8093 100644 --- a/docs/bwrap-runtime.md +++ b/docs/bwrap-runtime.md @@ -122,3 +122,38 @@ 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. diff --git a/profiles/profile.claude-agent-dev-proof.yaml b/profiles/profile.claude-agent-dev-proof.yaml new file mode 100644 index 0000000..bb024d0 --- /dev/null +++ b/profiles/profile.claude-agent-dev-proof.yaml @@ -0,0 +1,37 @@ +# Candidate profile for the Glas real-rein acceptance (GLAS-WP-0012 review). +# Declaring egress here does not enable it: ext.bwrap refuses this profile +# unless the owner extension config independently allowlists the destination, +# and the pinned runtime/credential route stay in owner config, never here. +# Not a production profile — SAND-WP-0015-T04 gates activation. +id: profile.claude-agent-dev-proof +version: "1.1.0" +extension: ext.bwrap +isolation: + level: process +network: + default: deny + egress: + - api.anthropic.com:443 +workspace: + mode: mirror + access: rw +scope_default: session +ttl: + default: 1h + max: 4h + idle_reap: null +resources: + cpu: null + memory_mb: null +setup: + instructions: "" + secret_refs: [] +placement: + prefer: [localhost] + fallback: [] +reachability: + tunnel: ops-bridge + identity: ops-warden +metadata: + cost_class: self-hosted + latency_class: standard diff --git a/tests/test_egress.py b/tests/test_egress.py index 8656173..966c2ab 100644 --- a/tests/test_egress.py +++ b/tests/test_egress.py @@ -117,3 +117,21 @@ def test_failed_broker_readiness_removes_egress(): with pytest.raises(RuntimeError, match="startup failed"): ext.wait_ready(handle) cleanup.assert_called_once_with(handle) + + +def test_candidate_claude_profile_declares_exact_destination_and_fails_closed(tmp_path): + """The committed candidate profile grants nothing without owner allowlisting.""" + from sandboxer.extensions.bwrap import BwrapExtension + from sandboxer.profiles.loader import load_profile + + profile = load_profile("profile.claude-agent-dev-proof") + assert profile.version == "1.1.0" + assert profile.extension == "ext.bwrap" + assert profile.network.default == "deny" + assert profile.network.egress == ["api.anthropic.com:443"] + assert profile.setup.secret_refs == [] + + ext = BwrapExtension({"base_dir": str(tmp_path / "unused")}) + with pytest.raises(ValueError, match="owner allowlist"): + ext.provision(profile, {}, "localhost") + assert not (tmp_path / "unused").exists() diff --git a/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md b/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md index 0b1997d..f8139b7 100644 --- a/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md +++ b/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md @@ -8,7 +8,7 @@ status: blocked owner: codex topic_slug: bwrap-runtime-and-private-state created: "2026-09-05" -updated: "2026-09-05" +updated: "2026-09-06" state_hub_workstream_id: "d3f12387-fd23-58f0-b979-9c811507614d" --- @@ -129,3 +129,25 @@ a data-only consumer read policy. Its production exec refuses before OpenBao until SECRETS-WP-0007-T04 and SECRETS-WP-0008-T02/T06 deliver canonical authorization/consume and service authority. T04 remains waiting on that live owner path, the pinned Claude executable and real model acceptance. + +## 2026-09-06 combined-runtime candidate returned to Glas + +Answered the GLAS-WP-0015 handoff. Added candidate profile +`profile.claude-agent-dev-proof` version 1.1.0 (`ext.bwrap`, localhost-only, +`default: deny` with declared `api.anthropic.com:443`) for GLAS-WP-0012 review. +The committed profile grants no egress by itself: `ext.bwrap` refuses it unless +the owner extension config allowlists the destination, and a regression test now +asserts that fail-closed default. `make check`: lint clean, 172 tests passed. + +Resolved the differing project examples in favour of the acceptance runner's +`actor: agt` / `project: glas-local-proof`, and bound the documented credential +route to that single project rather than broadening it. The full return contract +— profile id/revision, host scope, consumer tuple, runtime digest and mount +paths, declared egress, and the value-free denial/cleanup receipts — is in +`docs/bwrap-runtime.md`. + +T04 stays `wait`: the concrete Claude credential lane and delivery/revocation +contract, owner machine authentication, the pinned Claude executable with its +own proxy startup proof, and the real-model acceptance are still operator and +upstream-owner gated. Provider workspace scope and spend limit remain operator +inputs. `SAND-WP-0014-T05` remains open on the same gates.