Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7463d78
feat(sandbox,gateway): route sandbox egress through corporate HTTP proxy
feloy Jul 9, 2026
8a532cd
fix(sandbox,podman): make corporate proxy routing operator-owned
feloy Jul 16, 2026
248e68f
feat(sandbox,podman): deliver corporate proxy credentials via secret …
feloy Jul 16, 2026
4cf6ffa
fix(sandbox,podman): fail closed on invalid upstream proxy configuration
feloy Jul 16, 2026
eb1aa65
fix(sandbox,podman): finish the fail-closed upstream proxy credential…
feloy Jul 16, 2026
2519abe
fix(sandbox,podman): close remaining fail-open upstream proxy config …
feloy Jul 16, 2026
fb85454
fix(sandbox,podman): reject upstream proxy URLs with path, query, or …
feloy Jul 16, 2026
43493f3
fix(sandbox,podman): remove plain-HTTP upstream proxy support
feloy Jul 16, 2026
0b91023
fix(sandbox,podman): escape generated TOML and require explicit proxy…
feloy Jul 17, 2026
3d087d7
fix(sandbox,podman): preserve tunneled bytes read with the CONNECT re…
feloy Jul 17, 2026
4a12936
fix(sandbox,podman): gate cleartext proxy Basic auth behind an explic…
feloy Jul 17, 2026
0845e87
fix(sandbox,podman): honor port qualifiers and resolved addresses in …
feloy Jul 17, 2026
d5ba73f
fix(sandbox,podman): bind proxied CONNECT tunnels to validated addresses
feloy Jul 17, 2026
c67f22c
fix(sandbox,podman): reject empty port after bracketed IPv6 proxy host
feloy Jul 17, 2026
c5596ed
fix(sandbox,podman): fall back across validated addresses in proxied …
feloy Jul 17, 2026
8b23811
fix(sandbox,podman): strip new reserved proxy vars and complete docs/…
feloy Jul 17, 2026
7cea0dd
fix(sandbox,podman): cap each proxied CONNECT attempt within the shar…
feloy Jul 17, 2026
cc539f7
fix(sandbox,podman): deliver corporate proxy config on the supervisor…
feloy Jul 17, 2026
648b9ce
docs(sandbox): align proxy comments with the argv transport
feloy Jul 18, 2026
c613f0e
fix(sandbox): parse bracketed IPv6 authorities in client CONNECT targets
feloy Jul 18, 2026
0afa86b
test(podman): cover proxy-auth secret cleanup across lifecycle failures
feloy Jul 18, 2026
cef24d7
docs: list corporate proxy keys in the Podman compute-driver overview
feloy Jul 18, 2026
461211d
test(sandbox): cover the SSRF-to-TLS composition across the proxy tunnel
feloy Jul 18, 2026
e6db7e6
fix(sandbox,podman): bound proxy-auth reads, reject port 0, fix stale…
feloy Jul 18, 2026
bf9190c
fix(sandbox): open proxy-auth file non-blocking to reject FIFOs promptly
feloy Jul 18, 2026
76ce3e7
test(podman): cover corporate proxy egress across driver and supervisor
feloy Jul 19, 2026
a798d4b
test(podman): restart the gateway on the proxy-config panic path
feloy Jul 19, 2026
0804d13
fix(supervisor-network): derive the loopback proxy bypass from resolv…
feloy Jul 19, 2026
f0504e9
fix(podman): clean up proxy-auth secret when container already deleted
feloy Jul 21, 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