feat: enforce owner allowlisted bwrap HTTPS egress
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
This commit is contained in:
tegwick 2026-09-05 22:08:18 +02:00
parent d69827aaa2
commit d477c3b5d9
11 changed files with 563 additions and 32 deletions

39
docs/bwrap-egress.md Normal file
View file

@ -0,0 +1,39 @@
# Bwrap HTTPS egress
Owner extension config can set `allowed_egress: [api.anthropic.com:443]`.
A profile must declare a subset in `network.egress`, with `default: deny`.
Defaults remain empty. Only exact lowercase DNS names ending in `:443`
are accepted; no wildcards, IP literals, URLs or other ports.
Each opted-in sandbox gets a separate host-side Unix CONNECT proxy. Only that
socket is mounted into the sandbox; an in-namespace loopback bridge provides
HTTPS_PROXY/https_proxy to child processes. The namespace retains only lo,
without a host interface or direct DNS/network access. The owner proxy resolves
the declared hostname, rejects any non-global result, and connects to a checked
numeric address without a second lookup. TLS stays between client and provider;
system CA certificates are mounted read-only. No TLS interception or body logging.
This enforces connection destinations, not HTTP paths, provider account identity,
or TLS SNI on shared hosting. A client can send arbitrary bytes to an allowed
server. It is not an application firewall or a defense against an allowed
provider's own forwarding features. A future stronger policy needs separate
application enforcement. DNS resolution uses the trusted host resolver.
Up to 16 concurrent tunnels per sandbox, 8 KiB CONNECT headers, 10-second
socket operations, 30-second idle and 900-second tunnel lifetime. Unsupported
methods, hosts, ports and request framing close without upstream dialing.
Teardown kills the dedicated proxy process and removes its socket directory.
The current host owner must remain trusted; no caller-selected proxy config.
Validation: `scripts/smoke-bwrap-egress.py` made a credential-free TLS GET to
api.anthropic.com from sandbox e290e788 and received HTTP 404. Undeclared
example.com and direct 1.1.1.1:443 were denied, only lo existed, and proxy plus
workspace teardown passed. This is transport evidence, not model authentication.
Unit tests cover invalid destinations, private and mapped-loopback DNS,
checked-address dialing, CONNECT framing and owner/profile allowlist separation.
Claude documents HTTPS_PROXY at https://code.claude.com/docs/en/network-config.
The pinned Claude executable must still pass its own proxy/startup compatibility
proof. The existing production profiles are unchanged. CCR-2026-0016 custody is
complete at version 2, but owner machine authentication and protected credential
delivery remain outstanding under SAND-WP-0015-T04 and GLAS-WP-0012-T02.

View file

@ -82,9 +82,10 @@ 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
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. There is still 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.