This almost always turns out to be st.cache_resource on the connection, not the reverse proxy reusing anything.
You said you checked your st.cache_data functions and they key on username, which is the right instinct, but the leak usually hides in whatever function opens the SQL connection or session object. st.cache_resource caches are globally by default, they're shared across every user and every session in the app process, not per-session like st.session_state. The cache key comes from the function's arguments, so if your connection-builder function looks like this:
every user hits the same cached connection object for up to the TTL window, and that connection was opened with whichever token happened to trigger the first cache miss. That's exactly the symptom you're describing: queries occasionally running under a previous user's identity, and it'll look "random" because it only shows up when the cache is warm from someone else's request.
The fix is to make the token part of the cache key so each identity gets its own cached object:
Or simplest and safest, don't cache the connection at all, open it fresh per request and close it after the query, which is what Databricks' own Apps examples do for this exact reason.
To your second question, yes, there's a cheap way to verify the token belongs to who you think before you use it. Use the SDK with that specific token and check the identity it resolves to against X-Forwarded-Email:
One more thing worth knowing separately from the caching issue: st.context.headers itself is only populated once, at the initial page load / WebSocket connection, and doesn't refresh on reruns. So on a long-lived tab, even without any caching bug, the token you're holding can go stale if the underlying session persists across a reconnect. The identity check above catches that case too, since it fails closed instead of quietly running a query under a token that no longer matches.