fix(server): forward Authorization header on sandbox proxy routes#1
Merged
Conversation
In-sandbox services authenticate callers with scoped bearer tokens, but the server proxy stripped the Authorization header as sensitive, so those tokens never reached the sandbox and requests failed with 401. The k8s wildcard ingress-gateway (which bypasses this proxy) already forwards the header; this aligns the proxy with that behavior. Cookie and the OpenSandbox API key header are still stripped. Claude-Session: https://claude.ai/code/session_011dwUhZSZyAfpLEKBVLEi2T
There was a problem hiding this comment.
Pull request overview
This PR fixes sandbox service authentication when accessing in-sandbox HTTP/WebSocket services through the lifecycle server’s /v1/sandboxes/{id}/proxy/{port} routes (notably in the docker-runtime path) by ensuring the client Authorization header is forwarded to the sandbox backend.
Changes:
- Stop stripping the
authorizationheader inserver/opensandbox_server/api/proxy.pyby removing it fromSENSITIVE_HEADERS. - Update proxy route tests to assert that
Authorization: Bearer ...is forwarded for both HTTP and WebSocket proxy paths.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| server/opensandbox_server/api/proxy.py | Allows Authorization to pass through the sandbox proxy while still stripping cookies and the platform API key header. |
| server/tests/test_routes_proxy.py | Updates assertions to validate Authorization forwarding for HTTP and WebSocket proxy behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tea-artist
approved these changes
Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
Teable's sandbox file operations are browser-direct by design: the frontend obtains a short-lived, scoped JWT from Teable and issues
PUT/GET/DELETE /v1/filesrequests straight to the in-sandbox file service (port 18923) withAuthorization: Bearer <token>. The in-sandbox daemon verifies the token via a callback to Teable (signature, purpose, per-op scope, sandbox-currency, quotas). File bytes never transit the Teable backend.This application-layer protocol is identical across deployments. The frontend resolves the sandbox origin from
/v1/sandboxes/{id}/endpoints/{port}and is agnostic to the runtime underneath. What differs is only the transport that carries the request into the sandbox:ingress.mode=gateway)opensandbox-ingress-gateway(Go) routes*.sandbox.<domain>directly to the podOpenSandbox-Ingress-To, secure-access signature; seecomponents/ingress/pkg/proxy/proxy.go)/v1/sandboxes/{id}/proxy/{port}on the serverSENSITIVE_HEADERSinserver/opensandbox_server/api/proxy.pyProblem
In the docker runtime, every browser-direct file request fails with 401
missing authorization: the server proxy drops theAuthorizationheader before forwarding, so the scoped token never reaches the in-sandbox verifier. The same flow works in Kubernetes because the ingress-gateway path never executes this proxy code.Probe evidence (docker runtime):
PUT .../v1/fileswith an invalid bearer returnsmissing authorization(header never arrived), while passing the same token via?token=query reaches the daemon and returnsaccess denied— isolating the header strip as the only break in the chain.Why the strip exists (and why its premises no longer hold)
The strip dates to 7082d64, when the proxy route was
/proxy/{endpoint}/{full_path}— a relay to an arbitrary caller-supplied host. Strippingauthorization/cookiethere was correct: a browser hitting the platform domain attaches platform-domain credentials, and forwarding them to an arbitrary, untrusted backend would leak them.Since then the architecture has changed in three ways, each removing a premise of that defense:
/sandboxes/{sandbox_id}/proxy/{port}— it can only reach the sandbox's own endpoint, not arbitrary hosts.middleware/auth.py_is_proxy_path) — callers on this path are end users, not platform clients holding platform credentials.Authorizationon a sandbox preview origin belongs to the in-sandbox service and is forwarded.On the preview path today,
Authorizationis an end-to-end credential minted for the in-sandbox service — and since the proxy path carries no platform auth of its own, that bearer token is the only auth signal the real security boundary (the in-sandbox service) can check. Stripping it removes authentication rather than adding safety.Change
authorizationfromSENSITIVE_HEADERSinserver/opensandbox_server/api/proxy.py, aligning the server proxy with the ingress-gateway.cookieand the OpenSandbox API key header (real platform/infra credentials) are still stripped.tests/test_routes_proxy.py(HTTP and WebSocket proxy paths) to assert the header is forwarded.If maintainers prefer a conservative default, this could instead be a config flag (e.g.
proxy_forward_authorization); happy to rework — though note the gateway path already forwards unconditionally, so a flag would preserve the very inconsistency this fixes.Verification
uv run pytest tests/test_routes_proxy.py— 14 passed.PUT .../v1/fileswith an invalid bearer now returnsaccess denied(header reached the in-sandbox verifier, which rejected it via the Teable callback); requests with no token still fail closed withmissing authorization.