Problem
Public preview URLs are documented as supporting three auth modes via box.getPublicURL(port, options):
- Default (no options): public URL, no auth.
{ bearerToken: true }: Upstash issues a Bearer token.
{ basicAuth: true }: Upstash issues username + password.
When mode 1 (default) is used, the URL is documented as fully public with no Authorization enforcement. In practice, Upstash's preview edge silently overwrites whatever Authorization header the client sends and replaces it with a Basic auth header before the request reaches the box's dev server.
Repro
Create a box, expose a port with no auth options:
const box = await Box.create({ ... });
await box.getPublicURL(3000); // no options
Inside the box, run a Next.js dev server that echoes request headers. Then from outside:
curl -H "Authorization: Bearer my-jwt-token" https://<box>-3000.preview.box.upstash.com/api/echo-headers
Expected: the dev server sees Authorization: Bearer my-jwt-token.
Actual: the dev server sees Authorization: Basic admin:<64-char-hex>. The original client header is gone. The injected Basic value is the same on every request for a given box, suggesting it is a per-box internal proxy credential rather than dynamic.
Same behavior with no Authorization header at all on the client side:
curl https://<box>-3000.preview.box.upstash.com/api/echo-headers
The dev server still sees Authorization: Basic admin:<hex>.
Confirmation that the URL has no auth configured
Listing the box's public URLs via the SDK shows no token, username, or password fields:
curl -H "Authorization: Bearer $UPSTASH_BOX_API_KEY" \
https://<region>.box.upstash.com/v2/box/<box-id>/preview
Returns:
{
"previews": [
{
"id": "<box-id>-3000",
"box_id": "<box-id>",
"port": 3000,
"created_at": <ts>
}
]
}
No bearer_token, no basic_auth. So this is not the expected behavior of any documented auth mode.
Impact
Any user app that uses the industry-standard Authorization: Bearer <jwt> pattern for its own auth (Auth.js, NextAuth, JWT-based sessions, custom JWT routes, etc.) works correctly when deployed to Vercel / Cloudflare Pages / anywhere else, but returns 401 on every authenticated request when viewed inside the preview iframe. The JWT never reaches the app's verifyToken because Upstash has already replaced the header with its own Basic credential.
This breaks AI coding agent platforms that build apps and let users test them in a sandbox preview. The "build a TODO app with login" flow works end to end on the deployed site but fails to load any data in the preview because every fetch returns 401. Users report this as a platform bug when it is actually Upstash silently mangling the header.
We hit this in production on Automatio and chased it through the wrong layers (our CF Worker proxy, env vars, AUTH_SECRET) for hours before identifying Upstash as the source. We are filing it as a separate issue from #101 because the cause is different (header rewrite vs same-origin / CORS / WebSocket gaps), and the workaround is different too (change app code, not iframe / proxy config).
Workarounds we are aware of
These all require changes inside the user app:
- Send the token via a custom header like
X-Auth-Token and read it from there in backend routes. Upstash leaves X-Auth-Token alone.
- Send the token via a cookie (
Set-Cookie on login response, req.cookies on backend). Cookies pass through Upstash untouched.
We do not want to force our coding agent to generate non-standard auth code (the Authorization: Bearer pattern is the right default for the deployed world), so we are blocked on this.
Asks
- Confirm whether the Basic header injection is intentional behavior for default public URLs. If yes, please document it so other platforms do not hit the same trap.
- If it is intentional, please consider adding an option to disable the rewrite for public URLs that opted out of auth.
- If it is unintentional, this is a bug worth fixing. The expected behavior for
getPublicURL(port) with no auth options is that the dev server sees exactly what the client sent.
Environment
@upstash/box SDK current version
- Verified on multiple boxes in multiple regions
Related: this is from the same "Upstash edge interferes with user app headers" family as #101 (origin and CORS) and #106 (x-forwarded-host being internal IP).
Problem
Public preview URLs are documented as supporting three auth modes via
box.getPublicURL(port, options):{ bearerToken: true }: Upstash issues a Bearer token.{ basicAuth: true }: Upstash issuesusername+password.When mode 1 (default) is used, the URL is documented as fully public with no Authorization enforcement. In practice, Upstash's preview edge silently overwrites whatever
Authorizationheader the client sends and replaces it with a Basic auth header before the request reaches the box's dev server.Repro
Create a box, expose a port with no auth options:
Inside the box, run a Next.js dev server that echoes request headers. Then from outside:
Expected: the dev server sees
Authorization: Bearer my-jwt-token.Actual: the dev server sees
Authorization: Basic admin:<64-char-hex>. The original client header is gone. The injected Basic value is the same on every request for a given box, suggesting it is a per-box internal proxy credential rather than dynamic.Same behavior with no
Authorizationheader at all on the client side:The dev server still sees
Authorization: Basic admin:<hex>.Confirmation that the URL has no auth configured
Listing the box's public URLs via the SDK shows no
token,username, orpasswordfields:Returns:
{ "previews": [ { "id": "<box-id>-3000", "box_id": "<box-id>", "port": 3000, "created_at": <ts> } ] }No
bearer_token, nobasic_auth. So this is not the expected behavior of any documented auth mode.Impact
Any user app that uses the industry-standard
Authorization: Bearer <jwt>pattern for its own auth (Auth.js, NextAuth, JWT-based sessions, custom JWT routes, etc.) works correctly when deployed to Vercel / Cloudflare Pages / anywhere else, but returns 401 on every authenticated request when viewed inside the preview iframe. The JWT never reaches the app'sverifyTokenbecause Upstash has already replaced the header with its own Basic credential.This breaks AI coding agent platforms that build apps and let users test them in a sandbox preview. The "build a TODO app with login" flow works end to end on the deployed site but fails to load any data in the preview because every fetch returns 401. Users report this as a platform bug when it is actually Upstash silently mangling the header.
We hit this in production on Automatio and chased it through the wrong layers (our CF Worker proxy, env vars, AUTH_SECRET) for hours before identifying Upstash as the source. We are filing it as a separate issue from #101 because the cause is different (header rewrite vs same-origin / CORS / WebSocket gaps), and the workaround is different too (change app code, not iframe / proxy config).
Workarounds we are aware of
These all require changes inside the user app:
X-Auth-Tokenand read it from there in backend routes. Upstash leavesX-Auth-Tokenalone.Set-Cookieon login response,req.cookieson backend). Cookies pass through Upstash untouched.We do not want to force our coding agent to generate non-standard auth code (the
Authorization: Bearerpattern is the right default for the deployed world), so we are blocked on this.Asks
getPublicURL(port)with no auth options is that the dev server sees exactly what the client sent.Environment
@upstash/boxSDK current versionRelated: this is from the same "Upstash edge interferes with user app headers" family as #101 (origin and CORS) and #106 (x-forwarded-host being internal IP).