Skip to content

fix(server): forward Authorization header on sandbox proxy routes#1

Merged
unofn merged 1 commit into
teablefrom
fix/proxy-forward-authorization
Jul 2, 2026
Merged

fix(server): forward Authorization header on sandbox proxy routes#1
unofn merged 1 commit into
teablefrom
fix/proxy-forward-authorization

Conversation

@unofn

@unofn unofn commented Jul 2, 2026

Copy link
Copy Markdown

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/files requests straight to the in-sandbox file service (port 18923) with Authorization: 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:

Runtime Transport Authorization header
Kubernetes (ingress.mode=gateway) opensandbox-ingress-gateway (Go) routes *.sandbox.<domain> directly to the pod forwarded — the gateway only strips its own routing headers (OpenSandbox-Ingress-To, secure-access signature; see components/ingress/pkg/proxy/proxy.go)
Docker edge rewrites the preview host to /v1/sandboxes/{id}/proxy/{port} on the server stripped by SENSITIVE_HEADERS in server/opensandbox_server/api/proxy.py

Problem

In the docker runtime, every browser-direct file request fails with 401 missing authorization: the server proxy drops the Authorization header 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/files with an invalid bearer returns missing authorization (header never arrived), while passing the same token via ?token= query reaches the daemon and returns access 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. Stripping authorization/cookie there 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:

  1. The route narrowed to /sandboxes/{sandbox_id}/proxy/{port} — it can only reach the sandbox's own endpoint, not arbitrary hosts.
  2. The proxy path was exempted from platform API-key auth (middleware/auth.py _is_proxy_path) — callers on this path are end users, not platform clients holding platform credentials.
  3. The k8s ingress-gateway established the de-facto semantics that inbound Authorization on a sandbox preview origin belongs to the in-sandbox service and is forwarded.

On the preview path today, Authorization is 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

  • Remove authorization from SENSITIVE_HEADERS in server/opensandbox_server/api/proxy.py, aligning the server proxy with the ingress-gateway. cookie and the OpenSandbox API key header (real platform/infra credentials) are still stripped.
  • Flip the two assertions in 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.
  • Built the image from this branch and ran it in a docker-runtime compose stack: PUT .../v1/files with an invalid bearer now returns access denied (header reached the in-sandbox verifier, which rejected it via the Teable callback); requests with no token still fail closed with missing authorization.

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 authorization header in server/opensandbox_server/api/proxy.py by removing it from SENSITIVE_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.

@unofn unofn requested a review from tea-artist July 2, 2026 07:09
@unofn unofn merged commit 8197993 into teable Jul 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants