Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cc0405c
feat(sandbox,gateway): route sandbox egress through corporate HTTP proxy
feloy Jul 9, 2026
3c43b52
fix(sandbox,podman): make corporate proxy routing operator-owned
feloy Jul 16, 2026
fa0082c
feat(sandbox,podman): deliver corporate proxy credentials via secret …
feloy Jul 16, 2026
a82cf26
fix(sandbox,podman): fail closed on invalid upstream proxy configuration
feloy Jul 16, 2026
1a9bcb0
fix(sandbox,podman): finish the fail-closed upstream proxy credential…
feloy Jul 16, 2026
2990c6c
fix(sandbox,podman): close remaining fail-open upstream proxy config …
feloy Jul 16, 2026
1394b24
fix(sandbox,podman): reject upstream proxy URLs with path, query, or …
feloy Jul 16, 2026
c6a92fa
fix(sandbox,podman): remove plain-HTTP upstream proxy support
feloy Jul 16, 2026
1f19d26
fix(sandbox,podman): escape generated TOML and require explicit proxy…
feloy Jul 17, 2026
4389400
fix(sandbox,podman): preserve tunneled bytes read with the CONNECT re…
feloy Jul 17, 2026
a1c8b99
fix(sandbox,podman): gate cleartext proxy Basic auth behind an explic…
feloy Jul 17, 2026
f86d73c
fix(sandbox,podman): honor port qualifiers and resolved addresses in …
feloy Jul 17, 2026
5617bcf
fix(sandbox,podman): bind proxied CONNECT tunnels to validated addresses
feloy Jul 17, 2026
32930c2
fix(sandbox,podman): reject empty port after bracketed IPv6 proxy host
feloy Jul 17, 2026
cdf5d89
fix(sandbox,podman): fall back across validated addresses in proxied …
feloy Jul 17, 2026
707a50f
fix(sandbox,podman): strip new reserved proxy vars and complete docs/…
feloy Jul 17, 2026
2768c08
fix(sandbox,podman): cap each proxied CONNECT attempt within the shar…
feloy Jul 17, 2026
fcc2651
fix(sandbox,podman): deliver corporate proxy config on the supervisor…
feloy Jul 17, 2026
5be9905
docs(sandbox): align proxy comments with the argv transport
feloy Jul 18, 2026
3192ee8
fix(sandbox): parse bracketed IPv6 authorities in client CONNECT targets
feloy Jul 18, 2026
19f2369
test(podman): cover proxy-auth secret cleanup across lifecycle failures
feloy Jul 18, 2026
80e4ac3
docs: list corporate proxy keys in the Podman compute-driver overview
feloy Jul 18, 2026
c8434ad
test(sandbox): cover the SSRF-to-TLS composition across the proxy tunnel
feloy Jul 18, 2026
4524ac9
fix(sandbox,podman): bound proxy-auth reads, reject port 0, fix stale…
feloy Jul 18, 2026
1f9fc5b
fix(sandbox): open proxy-auth file non-blocking to reject FIFOs promptly
feloy Jul 18, 2026
2b18a75
test(podman): cover corporate proxy egress across driver and supervisor
feloy Jul 19, 2026
c2c797e
test(podman): restart the gateway on the proxy-config panic path
feloy Jul 19, 2026
39b8d2a
fix(supervisor-network): derive the loopback proxy bypass from resolv…
feloy Jul 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions architecture/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,70 @@ handled by the inference interception path:
External inference endpoints that do not use `inference.local` are treated like
ordinary network traffic and must be allowed by policy.

In proxy-required networks, the supervisor chains upstream TLS tunnels through
a corporate forward proxy with HTTP CONNECT instead of connecting directly,
once policy and SSRF checks pass. Only TLS (CONNECT) egress is chained:
plain-HTTP requests always dial the destination directly, because forwarding
plain HTTP through a proxy requires absolute-form request forwarding rather
than CONNECT tunneling and is out of scope. The proxy configuration is an
operator-owned boundary delivered on the supervisor's command line
(`--upstream-proxy` and friends) by the compute driver; sandbox and template
environment — and `ENV` values baked into the sandbox image — cannot
influence it, since none of these can alter the argv the driver sets. The
conventional `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` variables a sandbox
controls are ignored on this path. Operator `NO_PROXY` destinations and
loopback always dial directly; add driver-injected host aliases (e.g.
`host.containers.internal`) to the operator `NO_PROXY` list when the corporate
proxy cannot reach the container host. `NO_PROXY` matching is port-aware and
resolution-aware: an entry with a `:port` qualifier only bypasses that port,
and IP/CIDR entries also match hostnames through their validated resolved
addresses, with the direct dial limited to the addresses the entry contains. Only `http://` proxy URLs in explicit
`http://host:port` form are supported — the scheme and port are both
required, and a path, query, or fragment is rejected. Local DNS resolution
and SSRF validation still run before the proxied dial, and the CONNECT
target sent to the corporate proxy is a validated resolved address, so the
proxy performs no DNS resolution of its own and the tunnel stays bound to
the answer that passed SSRF and `allowed_ips` validation. The hostname still
travels inside the tunnel (TLS SNI, application `Host`). In split-horizon
networks, point the gateway host at the corporate resolver so internal names
validate to their internal addresses; the `proxy_connect_by_hostname`
opt-in exists as a
last resort for proxies whose ACLs filter on hostnames and reject IP CONNECT
targets — with it, the proxy resolves the name itself and its ACLs become
the effective egress control for proxied TLS. (Resolving through the proxy's
own DNS view, e.g. DoH tunneled via CONNECT, is a possible future
enhancement and out of scope.) The workload child's proxy variables are
unaffected — they are always rewritten to point at the local policy proxy.

The configuration is fail-closed: a setting that is present but invalid — an
empty value, an unsupported or malformed proxy URL, an unreadable auth file,
a malformed credential, or an auth file or `NO_PROXY` list set while no proxy
URL is configured — is fatal to supervisor startup instead of being treated
as unset, so a misconfiguration can never silently degrade to direct dialing
or unauthenticated proxy access. Only an omitted argument means "no proxy".
The driver validates the same rules at sandbox-create time through
validators shared with the supervisor
(`openshell_core::driver_utils::parse_upstream_proxy_url` and
`parse_upstream_proxy_credential`).

Proxy credentials are never embedded in the URL: an inline `user:pass@` is
rejected because it would be stored in `gateway.toml` and exposed in container
metadata. Operators supply credentials via `proxy_auth_file`; the driver
stages them as a root-only secret mounted at a fixed path and passes only
that path on the supervisor's command line. The supervisor reads the
file and builds the `Proxy-Authorization: Basic` header; a credential that is
empty, contains control characters, or is not in `user:pass` form is fatal on
both sides.

The Basic header travels over the plain-TCP connection to the `http://` proxy,
so it is readable on the network path between sandbox host and proxy.
Configuring `proxy_auth_file` therefore requires the explicit opt-in
`proxy_auth_allow_insecure = true`. Both the
driver (at sandbox-create time) and the supervisor (at startup) reject an
auth file without the acknowledgement, and the acknowledgement without an
auth file, so credentials are never sent in cleartext without an explicit
operator decision.

## Credentials

Provider credentials are stored at the gateway and fetched by the supervisor at
Expand Down
3 changes: 3 additions & 0 deletions crates/openshell-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ base64 = { workspace = true }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"], optional = true }
reqwest = { workspace = true, features = ["blocking", "rustls-tls-webpki-roots"], optional = true }

[target.'cfg(unix)'.dependencies]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Critical: This target table also captures the existing chrono and reqwest declarations below it. Telemetry is enabled by default and uses both crates on non-Unix targets, so those builds will lose the dependencies and fail. Keep chrono and reqwest in [dependencies], and place only nix under [target.'cfg(unix)'.dependencies].

nix = { workspace = true }

[features]
default = ["telemetry"]
## Compile in anonymous telemetry emission support. On by default; disable with
Expand Down
Loading
Loading