THE 'ALMOST SECURE' AUTH GAPS

Four bugs that don't show up in a checklist because the code looks correct. Cookie scope set wider than necessary. TLS configured but with downgrade paths still open. OAuth tokens leaking through Referer headers. PKCE technically supported but optional.

The scenario referenced below runs on gapbench.vibe-eval.com — a public security benchmark we operate.

Bugs that pass review

Every one of these four bugs ships in code that looks fine. The review checklist says “cookies have HttpOnly and Secure” — yes. “TLS is enabled” — yes. “OAuth uses Authorization Code flow” — yes. “PKCE is supported” — yes. All four boxes checked. All four wrong in the same subtle way.

The review didn’t ask the second-order question. How wide is the cookie scope? Is TLS the only option, or just one option? Where does the OAuth token end up rendered? Is PKCE required, or just available?

res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  domain: '.example.com',  // <-- here
})

Domain=.example.com ships the cookie on every subdomain. If you have marketing.example.com running a third-party site builder, partner.example.com running a SaaS your team integrated with, old.example.com running a Vercel project nobody owns anymore, team.example.com from before your domain consolidation — every one of those sees the session cookie.

If any of those subdomains is compromised or under attacker control (subdomain takeover, marketing-vendor breach, anything), the session token leaks.

The fix: don’t set domain unless you actively need to share the cookie across subdomains. The default scope is the host that issued the cookie — exactly the principal that should see it. If you must set domain, set it to the most specific value that works.

Live: https://gapbench.vibe-eval.com/site/cookie-scope-leak/.

TLS downgrade (no HSTS)

You configured TLS. Your site responds correctly to https://example.com. Your reverse proxy redirects HTTP to HTTPS. Audit passes.

Then a user on a rogue WiFi types example.com without https://. The first request goes plain HTTP. The attacker sitting between user and server intercepts it, never sends the redirect, serves their own version of your site. The user is now on attacker.example, but their address bar (which they probably don’t look at) shows what looks like example.com.

The mitigation is HSTS — Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. Once a user has visited your site once over HTTPS with HSTS set, the browser refuses to ever connect over HTTP. The first-visit case is solved by submitting your domain to the HSTS preload list, which ships with browsers.

The fix is one header. AI-generated apps frequently ship without it because the AI’s “set up TLS” advice doesn’t always include it.

Live: https://gapbench.vibe-eval.com/site/tls-downgrade/.

OAuth token leak via Referer

OAuth callback pages occasionally end up with the access token in the URL — implicit flow, or hybrid flows, or just sloppy server-side rendering that puts the token in a hidden field on the page. If that page contains third-party scripts (analytics, tag managers, anything outbound) and any of them make a request before the page strips the token, the token can leak via the Referer header.

The same happens if the user clicks a link on the callback page before the token gets handled — the next page’s outbound requests include a Referer with the token.

Fix: don’t put tokens in URLs. Use the Authorization Code with PKCE flow, return tokens via POST. If you can’t avoid them in URLs (legacy implicit flow), set a strict Referrer-Policy: no-referrer on the callback page, and load no third-party scripts there.

Live: https://gapbench.vibe-eval.com/site/oauth-token-leak-referer/.

PKCE downgrade

The PKCE extension to OAuth requires the client to send a code_challenge when initiating the flow and a matching code_verifier when exchanging the code. The pair makes the authorization code useless to anyone who didn’t generate the verifier.

If the OAuth server supports PKCE but doesn’t require it, a flow can complete without the verifier. An attacker who intercepts the authorization code (via stolen redirect_uri, leaked Referer, etc.) can exchange it without supplying a verifier — because the server doesn’t insist.

Fix: configure your OAuth server to require PKCE for all public client flows. Reject any token exchange where the original authorization request didn’t include code_challenge.

Live: https://gapbench.vibe-eval.com/site/pkce-downgrade/. Companion: https://gapbench.vibe-eval.com/site/ref-oauth/ for the clean control.

Anonymized. A B2B product had three subdomains — app.example.com for the main UI, api.example.com for the backend, and marketing.example.com for the WordPress marketing site. The auth cookie was set with Domain=.example.com so it propagated across all three. Reasonable when the team set it up; brittle later.

The marketing subdomain was hosted on a third-party WordPress provider. The team migrated off WordPress to a different provider but didn’t update the DNS for ~3 weeks. During that window, the WordPress provider’s account had lapsed. Anyone who registered the same site name on that provider could serve content under marketing.example.com.

An attacker did exactly that. They served a small JavaScript on the takeover-eligible subdomain that did fetch('https://api.example.com/api/me', { credentials: 'include' }), got the response (because the cookie was scoped to .example.com), and exfiltrated session data.

The cleanup: shorter cookie scope (host-only, no Domain attribute), DNS hygiene (audit quarterly), the WordPress site moved to a separate apex domain.

The lesson: cookie domain scope is a load-bearing security control. The wider you set it, the more subdomain failures become full-cookie failures. Set the narrowest scope that works. If you must share across subdomains, audit those subdomains continuously — every one of them is now in your auth surface.

TLS — the things people miss

TLS being “configured” is a low bar. TLS being correctly configured is a longer list:

  1. HSTS with includeSubDomains and preload. The header tells browsers to refuse HTTP for the duration. Preload list inclusion makes it apply on first visit too.
  2. No HTTP at all on production hosts. Don’t redirect HTTP→HTTPS; refuse HTTP entirely. The redirect is a downgrade window.
  3. Modern cipher suites. TLS 1.2 minimum, 1.3 preferred. Ban RC4, 3DES, CBC-mode AES.
  4. Certificate transparency monitoring. Watch CT logs for certs issued for your domain by CAs you don’t use. Alerting on this catches a class of takeover attacks early.
  5. OCSP stapling enabled. Reduces revocation-checking latency and information leakage.
  6. No mixed content. All sub-resources on HTTPS. Mixed content has been silently blocked by browsers for years, but warnings still leak.
  7. Forward secrecy enabled. All modern cipher suites support it; verify your config doesn’t disable it.

Most of these are enabled by default on managed platforms (Vercel, Netlify, Cloudflare). Self-hosted setups often miss several. Tools like SSL Labs (ssllabs.com/ssltest) grade the configuration in detail.

OAuth — the parameters that matter

A correct OAuth Authorization Code flow includes:

  1. state parameter, generated server-side, validated on callback. Prevents CSRF on the OAuth dance.
  2. code_challenge (PKCE) for public clients, S256 method. Prevents code-interception attacks.
  3. redirect_uri exact-match in the OAuth provider’s allowlist. No wildcards, no subdomain patterns.
  4. nonce parameter for OIDC, validated against the ID token. Prevents replay.
  5. Token exchange via POST, not GET. Tokens never appear in URLs or referrer headers.
  6. Strict Referrer-Policy on OAuth callback pages. Referrer-Policy: no-referrer is the safest.

Each of these mitigates a specific known attack. AI-generated OAuth code typically gets 1, 3, 5 right and skips 2, 4, 6. The skipped ones are the ones we keep finding.

Wrong fix vs right fix — cookies

// WRONG: convenience, broad scope
res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  domain: '.example.com',  // every subdomain sees this
})
// WRONG: SameSite=None for "OAuth compatibility" without thinking
res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'none',  // CSRF surface restored
  domain: '.example.com',
})
// RIGHT: tightest scope that works
res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',  // or 'lax' if you need top-level cross-site nav
  // No Domain attribute — host-only cookie
  // Only the issuing host (e.g., app.example.com) sees this
  path: '/',
})

Cross-stack notes

  • Express: res.cookie controls everything. Default sameSite is undefined (browser interprets as Lax), default secure is false. AI codegen often skips secure in dev and forgets to flip it.
  • Next.js: Per-route cookie setting via cookies(). Same shape as Express.
  • Django: SESSION_COOKIE_SAMESITE, SESSION_COOKIE_SECURE, SESSION_COOKIE_DOMAIN. The defaults are reasonable; the issue is when AI codegen overrides them.
  • Rails: config.session_store. Defaults to host-only and Lax. AI-generated apps that copy old tutorials sometimes set domain: :all which breaks this.
  • Spring: ServerHttpSecurity and CookieSerializer. Configuration has many knobs; AI codegen frequently sets some and not others.

How we detect

Cookie scope: we read Set-Cookie headers from your auth flow, parse the Domain attribute, flag anything broader than the issuing host.

TLS downgrade: we check whether your domain has HSTS, the max-age, whether includeSubDomains is set, and whether you’re on the HSTS preload list. We also probe whether HTTP returns content vs. an immediate redirect.

OAuth Referer leak: we walk the OAuth flow, observe whether tokens land in URLs, check for third-party script tags on callback pages, and verify Referrer-Policy.

PKCE: we initiate an OAuth flow without code_challenge, observe whether the server accepts it.

All runtime. Static scanners can flag the cookie config in source but can’t confirm the live HSTS posture or the OAuth-server-side PKCE setting.

CWE / OWASP

  • CWE-565 — Reliance on Cookies without Validation and Integrity Checking
  • CWE-319 — Cleartext Transmission of Sensitive Information
  • CWE-200 — Information Exposure (Referer)
  • CWE-345 — Insufficient Verification of Data Authenticity (PKCE)
  • OWASP Top 10 — A02:2021 Cryptographic Failures, A07:2021 Identification and Authentication Failures

Reproduce it yourself

COMMON QUESTIONS

01
What is overbroad cookie domain?
When a cookie is set with Domain=.example.com, the cookie ships on requests to every subdomain of example.com. If you have a subdomain you don't fully trust — a marketing site, a takeover-eligible old subdomain, anything that other teams or third parties run — that cookie reaches them. The fix is to scope cookies to the most specific domain possible. Don't set Domain at all unless you actively need to share.
Q&A
02
What is TLS downgrade?
Your site supports TLS. It also supports HTTP. If a user types example.com without https://, their first request goes over plain HTTP, and only the redirect tells them to upgrade. An attacker with network access (rogue WiFi, rogue ISP, MITM box) can serve their own response to that first request, never redirect to HTTPS, and intercept the entire session. The fix is HSTS — Strict-Transport-Security — which tells the browser 'always use HTTPS for this domain' on subsequent visits.
Q&A
03
What is OAuth token leak via Referer?
OAuth implicit flow returned tokens in the URL fragment. Modern Authorization Code flow returns tokens via POST. But intermediate flows still sometimes put tokens in URL parameters. If a page with a token in the URL contains third-party JavaScript or makes outbound requests, the token can leak in the Referer header. We see this in OAuth callback pages that load analytics scripts before exchanging the token.
Q&A
04
What is PKCE downgrade?
PKCE is the OAuth extension that makes the authorization code useless to anyone who didn't initiate the flow. It's mandatory for public clients per the latest OAuth specs. If your OAuth provider supports PKCE optionally — accepting the flow without it — an attacker who intercepts the code (via a stolen redirect_uri, a leaked Referer, or any other channel) can exchange it. The fix is to require PKCE for all flows, not just allow it.
Q&A
05
Why are these 'almost secure'?
Each one looks correct at a glance. Cookies are set with HttpOnly, Secure, SameSite — but the Domain is too wide. TLS is on — but HSTS isn't. OAuth uses Authorization Code flow — but the callback page is leaky. PKCE is mentioned in the docs — but optional. The bug is in the second-order detail, not the first-pass review.
Q&A
06
Where can I see this on a real URL?
https://gapbench.vibe-eval.com/site/cookie-scope-leak/, https://gapbench.vibe-eval.com/site/tls-downgrade/, https://gapbench.vibe-eval.com/site/oauth-token-leak-referer/, https://gapbench.vibe-eval.com/site/pkce-downgrade/.
Q&A
07
What CWE does this map to?
CWE-565 (Reliance on Cookies without Validation), CWE-319 (Cleartext Transmission for TLS), CWE-200 (Information Exposure for Referer leak), CWE-345 (Origin Validation Error for PKCE). OWASP A02:2021 (Cryptographic Failures), A07:2021 (Identification and Authentication Failures).
Q&A

TEST YOUR AUTH POSTURE

We probe cookie scope, TLS configuration, OAuth token handling, and PKCE enforcement.

RUN THE SCAN