WebSocket upgrade handshake broken on Community Cloud — HTTP 502 on external client, and session cookies not surviving reload in-app (post Debian bullseye-security incident)

App URL: https://archirapid.streamlit.app
Repo: private (Archirapid/archirapid-mvp), branch main
Python: 3.10 (runtime.txt)
Streamlit: 1.56.0 (pinned)

I believe this is a second, distinct issue caused by the same forced full-container
rebuild that resulted from the bullseye-security InRelease expiration incident
(2026-09-08/09) — not a regression in my own app code. Evidence below.

Symptom 1 — session cookie set correctly, but “orphaned” (its hash matches no row

our server ever wrote)

My app writes an auth cookie (_ar_session, a random urlsafe token) server-side after
login, and stores a hash of that same token in our own database for later verification
on page reload. This has worked reliably for months.

Since the rebuild:

  • Cookie IS present in the browser (confirmed via DevTools > Storage > Cookies:
    correct domain, path=/, SameSite=Lax, 24h expiry, well-formed 43-char urlsafe token).
  • Computing sha256(cookie_value) does NOT match the hash we stored server-side for that
    exact login (confirmed by comparing timestamps to the second, and checking ALL rows
    in the relevant time window, not just the closest one).
  • Result: every page reload (F5) or browser Back navigation logs the user out, in every
    browser tested (Firefox, Chrome), in Incognito, with any browser-level privacy/AV
    tooling disabled to rule that out.
  • Reproduced 100% of the time in production. Cannot reproduce locally
    (streamlit run app.py on the same code, same Python/Streamlit version) — it works
    perfectly locally, every time.

I initially suspected an unpinned tornado transitive dependency resolving to a newer
6.5.x release during the forced rebuild (streamlit==1.56.0’s own metadata excludes
tornado==6.5.0 explicitly, suggesting Streamlit is aware of a WS/cookie regression in
that release line). I pinned tornado==6.4.2 and redeployed — confirmed installed in the
build log — but the symptom persists identically. So this doesn’t look like a pip-level
dependency issue on my end.

Symptom 2 — external WebSocket client gets HTTP 502 on the exact same endpoint

I have a GitHub Actions workflow that keeps the app warm by opening a raw WebSocket
connection every ~hour to wss://archirapid.streamlit.app/~/+/_stcore/stream (no
cookies, no login — just a bare websockets.connect() from Python, anonymous). This
ran green for months.

Since ~2026-09-08 02:55 CEST it fails almost every run with:

Keepalive FAILED: server rejected WebSocket connection: HTTP 502

with occasional green runs interspersed (suggesting partial/intermittent recovery, not
a full fix yet). This is a completely independent client (no browser, no cookies, no
app code of mine involved) hitting the same WS upgrade endpoint and getting rejected at
the edge/proxy level — which lines up with real users’ sessions also breaking at
what looks like the same layer (cookie delivery during the WS upgrade handshake).

Timeline

  • Aug 22 – Sep 8 01:18 CEST: keepalive green consistently.
  • Sep 8 ~02:55 CEST onward: keepalive fails with HTTP 502 almost every run, a few
    scattered greens since (partial stabilization?).
  • Sep 8-9: apt-get bullseye-security incident (separate, now resolved per forum thread
    122420).
  • Sep 9 ~12:00 UTC onward: real users’ sessions confirmed not surviving reload in
    production, while the identical code works locally.

Question

Is the WebSocket upgrade path (/_stcore/stream, reached via the app’s /~/+/ proxy
route on *.streamlit.app subdomains) still degraded platform-side following the
bullseye-security rebuild? Specifically: is the Cookie header being forwarded
correctly by the edge/proxy during the WS upgrade handshake?

Happy to share more diagnostic data (exact timestamps, hash comparisons, keepalive
workflow file) if useful.

Hey there, thanks for the super detailed report and for sharing your troubleshooting steps! :man_superhero: This is a fantastic example of a minimum reproducible example and clear diagnostics—kudos for that! :tada:

Based on your description, this is almost certainly a platform-level issue with Streamlit Community Cloud’s WebSocket proxying and/or cookie handling after the recent forced container rebuild (bullseye-security incident). Your findings match known classes of issues where the edge proxy fails to forward the Cookie header during the WebSocket handshake, causing session validation to break—even though the cookie is present in the browser and your code works locally. The fact that your external WebSocket keepalive client (which doesn’t use cookies or app logic) is now getting HTTP 502s on the same endpoint further supports this being a platform regression, not an app or dependency bug. This pattern has been seen before in similar incidents and is not a regression in your code or dependencies (see related issues, docs).

There’s no indication in the public release notes or recent bugfixes that this specific proxy/cookie forwarding issue has been resolved since the rebuild (release notes). The symptoms you describe—session cookies present but not matching server state, and intermittent 502s on /_stcore/stream—are classic signs of a proxy misrouting or stripping headers during the WebSocket upgrade. This is not something you can fix in your app code or requirements; it requires a fix on the Streamlit Community Cloud infrastructure. If you haven’t already, please also post this in the Streamlit forum to help the team prioritize and track the incident. Community members, if you’re seeing similar issues, please chime in!

Sources:

Hey @restautelmarbella , I have looked a little bit into this and based on our logs your app seems to be healthy for some time now. Can you confirm that?

Also, it looks like your app is actually running Python 3.14, not 3.10.

For the cookie hashing, does this happen in the browser? Because custom cookies can be set server-side, but I believe cannot be sent back from the browser to the application because they are stripped.

Status + answers to your three questions

1. Is the app healthy now?

No — the session-restore issue is still reproducing 100% of the time. We just re-tested live (fresh Incognito window, login with real credentials) and got the same result: the server mints a valid session row on login (verified via direct read-only query against our production Postgres — opaque random token, correct role, +24h expiry), but on page reload the user is bounced back to the login screen every time. The server-side write path works; the read-back on reload does not.

2. Python 3.14 vs runtime.txt

Confirmed on our side: runtime.txt contains exactly python-3.10 (single line, no whitespace, unchanged in git for ~3 months). Our local environment and our whole dependency set are built and tested against CPython 3.10.

Our deploy log reports Using Python 3.14.7 environment at /home/adminuser/venv — so after the post-apt-get-incident rebuild, runtime.txt appears not to be honored; the container is on 3.14.7, not 3.10.

Two questions:

  • Is python-3.10 still a supported value for runtime.txt on Community Cloud, or has the supported range been narrowed?
  • When runtime.txt specifies a version you no longer support, does the platform now silently fall back to the current default (3.14.x) instead of failing the build?

This is material to the cookie problem: our entire wheel set (including tornado’s C extensions and anything touching http.cookies / asyncio) was resolved and tested on 3.10, and the container is now on 3.14 — a far larger environment drift than the one dependency we pinned.

3. The _ar_session cookie mechanism (“does the hashing happen in the browser?”)

No hashing happens in the browser. The browser only ever holds a raw, opaque random token (secrets.token_urlsafe(32), 43 chars). The SHA-256 hashing is done server-side, in Python, on read: we hash the incoming token and look up sha256(token) in a sessions table. The raw token is never stored server-side; the browser never sees a hash.

How the cookie is written (this is the key detail):

  • It is set client-side, in JavaScript — document.cookie = '_ar_session=' + encodeURIComponent(token) + '; path=/; SameSite=Lax; max-age=86400' — executed inside a st.components.v1.html(...) component, i.e. from within its iframe writing to window.parent.document.cookie.
  • It is not set via an HTTP Set-Cookie response header from the server.
  • Confirmed in browser DevTools: after login the _ar_session cookie is present on the app’s origin, with the expected Domain, Path=/, SameSite=Lax and expiry.

How it is read back:

  • Server-side, via st.context.cookies.get("_ar_session").
  • Per Streamlit 1.56.0’s own source (web/server/browser_websocket_handler.pyTornadoClientContext), st.context.cookies is populated from tornado_request.cookies of the WebSocket upgrade handshake (GET /_stcore/stream, Upgrade: websocket). The docstring: “Captures headers, cookies, and client info from the initial WebSocket handshake.”
  • Observed failure: on reload, st.context.cookies returns no _ar_session, even though the cookie is in the browser and — being SameSite=Lax on the same origin — should be included on the same-origin WebSocket upgrade request.

So on your point that “custom cookies … cannot be sent back from the browser … because they are stripped”: we are not setting it server-side, but we do need to read it server-side, and the read path is specifically the Cookie: header of the WS upgrade handshake. Our own diagnosis — an exact SHA-256 comparison of the browser’s cookie value against the production sessions table — points at that handshake phase, not at ordinary HTTP requests.

Explicit question: Does the cookie-stripping you suspect apply specifically to the Cookie: header of the WebSocket upgrade request (/_stcore/stream), or to any HTTP request as well? And does it apply to all non-Streamlit cookies regardless of how they were set (client-side JS vs server Set-Cookie)? If it is the WS upgrade specifically, that is exactly where our evidence lands — and it would mean st.context.cookies currently cannot see any app-set cookie on Community Cloud in the platform’s present state.