Two Auth Bugs With the Same Root Cause: The Secondary Path Was Never Updated
TLDR
During a security sweep of our authentication flow, we discovered two separate defects: a sensitive polling secret leaking directly into server access logs as a URL parameter, and a sign-out mechanism that left HttpOnly session cookies alive on the server for up to an hour. Both primary implementations had already been hardened months prior; the bugs survived exclusively inside secondary fallback handlers and backup proxy scripts cloned from earlier drafts.
| Bug Issue | Vulnerability Vector | Root Cause in Secondary Path | Production Remediation |
|---|---|---|---|
| Credential Log Leak | URL Query Parameter (?secret=Y) | Backup proxy script cloned from unhardened draft | Migrate secret to Authorization: Bearer header |
| Zombie Session Cookie | Active server session post-logout | Sign-out function only wiped client localStorage | Force edge endpoint call with Set-Cookie: Max-Age=0 |
Hardening one code path creates false security
When we hardened our primary sign-in and polling routines, we felt confident. The main auth proxy was handling token headers cleanly, and the primary client UI cleared user credentials on sign-out.
However, during a security audit across our secondary paths (backup proxy fallback endpoints and legacy component helpers), we realized that our earlier fixes never propagated down to secondary code paths. A backup proxy file created months ago was still passing secrets via query parameters, while an auxiliary sign-out handler was clearing localStorage without notifying the server edge function.
Secondary paths bypass primary security controls
Credential leaks in URL query parameters
How we found it: During a routine audit of our Cloudflare WAF access logs, we noticed a high volume of recurring requests containing raw authentication hashes. A search for parameters revealed that our backup proxy was writing the user's raw handshake secret directly into server logs in plain text as a ?secret= query parameter.
Our VS Code handshake relies on a polling endpoint every 5 seconds. In the backup proxy script, the polling request was appending ?secret=Y directly to the URL string.
Web servers, CDNs, load balancers, and browser histories all log full URL request paths by default. Every one of them wrote our bearer secret into plain text, once every 5-second tick, for the whole auth attempt.
Vulnerability: Query Parameter credential leak
[Extension Client] -- (appends ?secret=token) --> [Access Logs (PLAIN TEXT)]
| (Security Leak)
v
[Cloudflare WAF / CDN Cache]
Resolution: Authorization Header bearer token [Extension Client] -- (Authorization: Bearer token) --> [Access Logs (REDACTED)] | (Secure) v [Cloudflare Edge Worker]
Client-side logout leaving zombie edge cookies
How we found it: After clicking "Sign Out" in a secondary modal, we observed that subsequent protected API calls still returned 200 OK. Inspecting the Chrome DevTools Application tab showed the gx_session cookie was still present. Because the cookie was HttpOnly, client-side script purges were completely ignored, allowing requests to remain authenticated.
When a user clicked "Sign Out" in one of our secondary modal views, the script purged all Supabase keys from localStorage and reset the UI state.
However, because the gx_session cookie is marked as HttpOnly, JavaScript cannot inspect or delete it directly. Because the secondary sign-out routine failed to issue an HTTP request to the server cookie manager, the browser retained a fully valid session cookie, leaving the API open to authenticated requests for up to an hour.
Vulnerability: Client-only logout
[User clicks Sign Out] ---> [Clears localStorage] ---> [HttpOnly Cookie remains active]
|
v
[API remains open to abuse]
Resolution: Mandatory server roundtrip [User clicks Sign Out] ---> [POST /api/auth/session] ---> [Set-Cookie: Max-Age=0] | (Destroys cookie) v [API is secured]
Repository-wide credential audits prevent access leaks
Fixing the credential leak
We updated the secondary proxy script to extract the bearer token strictly from HTTP headers instead of query parameters:
// WRONG: Secret exposed in URL string and access logs
const secret = url.searchParams.get("secret");
target.searchParams.set("secret", secret);
// CORRECT: Secret passed strictly via Authorization header const secret = request.headers .get("Authorization") ?.replace(/^Bearer\s+/i, "") .trim(); return fetch(target.toString(), { headers: { Authorization: Bearer ${secret} }, });
Fixing the zombie cookie expiration
We updated every sign-out routine to mandate a server roundtrip before clearing any client-side state:
function signOut() {
// 1. Mandatory server roundtrip to wipe HttpOnly cookie
fetch("/api/auth/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "clear" }),
}).catch(() => {});
// 2. Clear client-side state only after edge trigger identityProvider.auth.signOut().catch(() => {}); clearLocalStorageTokens(); resetAuthState(); }
The edge endpoint responds with Set-Cookie: gx_session=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0, immediately instructing the browser engine to destroy the session cookie.
Rule of thumb: Always search your entire repository forsearchParams.setor?secret=before deploying proxy endpoints. Remember that client-sidelocalStorage.clear()is purely cosmetic for HttpOnly cookies. A logout is not real until the server returnsMax-Age=0.