Skip to content
Back to field notes
BrowserJune 6, 20266 min read

Referrer-Policy for AI-built SaaS: stop leaking auth tokens in the Referer header

A magic-link or OAuth token carried in a URL can ride the Referer header into your analytics and third-party scripts; this note shows how to confirm the leak and set a Referrer-Policy before you share the URL.

BrowserHeadersAuth

A token in the URL can leak through the Referer header

Here is the specific thing that can go wrong on launch day. A user clicks a magic link and lands on a page like https://yourapp.com/auth/callback?token=abc123. That page loads your analytics snippet and maybe a couple of third-party scripts. When the browser makes those outbound requests, it attaches a Referer header naming the page that triggered them. By default, that header can carry the full URL, query string included. So the one-time login token that was meant only for your server can ride along in a request to a domain you do not control.

This is the leak the Referer header creates. It is worth separating what is observable from what is only possible. Observable: you can open DevTools, click your own magic link, and read exactly what Referer value the browser sends to each outbound request. Possible, but not something you can prove from outside: whether a given third party logs that header, retains it, or exposes it later. You do not need the possible half to act. If the token is in the URL and the Referer can carry it cross-origin, the leak path exists and you close it before launch.

A few conditions make this real rather than theoretical:

When those line up, the token reaches a third party as a side effect of normal page loads. No attacker action is required for the exposure itself.

  • The sensitive value travels as a URL query parameter, not in a POST body or a header. Magic-link tokens, OAuth authorization codes on the redirect, and password-reset tokens are the usual ones.
  • The auth landing page loads at least one cross-origin resource: analytics, a tag manager, a font CDN, an embedded widget.
  • The page does not set a Referrer-Policy that strips the path and query for cross-origin requests.

Why AI-built SaaS apps hit this

The default that bites here lives in two places at once: how the auth flow is scaffolded, and what the server sends as a header.

AI coding tools reach for the simplest working auth pattern, which is often a token in the URL. A magic-link handler that reads request.query.token works on the first try and demos cleanly, so it ships. The generated code is not wrong about authenticating the request; the token does identify the user. What it skips is the question of where that URL travels after the click. That is an awareness gap, not a logic bug, which is why a passing test suite never surfaces it.

The header side is a separate omission. Browsers have moved to strict-origin-when-cross-origin as the default Referrer-Policy when a site sends no header at all, and that default already strips the path and query on cross-origin requests. But two things undercut it. First, plenty of AI-generated configs or older starter templates set Referrer-Policy: no-referrer-when-downgrade, which sends the full URL to any same-or-higher-security origin, reopening the leak. Second, same-origin requests still receive the full URL by default, so the token is visible to anything that shares your origin, including injected or compromised first-party scripts. The fast-deploy path means the public URL exists before anyone reads the response headers the platform actually sent.

The result is an app where the auth flow works, the analytics work, and the token-in-Referer path is invisible from the inside.

Check what your own app sends

You can confirm this on your own app, with your own account, in a few minutes. No third party is attacked and no real credentials leave your machine.

First, read the header. Open DevTools, go to the Network tab, reload your auth landing page, and click the document request. In the Response Headers, look for Referrer-Policy. Note the exact value. Absent, or no-referrer-when-downgrade, is the suspicious result. strict-origin-when-cross-origin, same-origin, or no-referrer is the safer result for an auth route.

Then watch the leak directly. Trigger a real magic link or reset link to your own inbox, click it, and on the callback page open the Network tab. Pick an outbound request to a cross-origin host (your analytics endpoint is the clearest one) and read its Request Headers.

Two things to keep straight while you check. This is about confidentiality of a value in transit, not about whether the token still works; a leaked one-time token that has already been consumed is lower risk than a long-lived one. And same-origin is not automatically safe: by default the full URL, token included, is sent on same-origin requests, so a strict policy still matters if you load first-party scripts on the auth page.

  • Suspicious result: the Referer header on that cross-origin request shows the full URL including ?token=... or ?code=.... The token has left your origin.
  • Safer result: the Referer shows only the origin (https://yourapp.com) with no path or query, or is absent entirely. The token stayed home.

Set a strict Referrer-Policy, and move tokens out of URLs

There are two layers to the fix, and you want both.

The header layer is fast. Set a Referrer-Policy that does not send the path and query off-origin. strict-origin-when-cross-origin is a reasonable site-wide default and matches what modern browsers already do without a header; setting it explicitly protects you from a stale template that downgraded it. For auth routes specifically, no-referrer is the strongest choice, because it sends no Referer at all and so cannot leak anything in either direction, including same-origin. You can set the header globally and override it to no-referrer on the auth callback paths, either through your framework's per-route header config or a meta referrer tag on those pages.

The flow layer is the real fix. A header reduces the leak; getting the token out of the URL removes it. Prefer auth patterns that carry the token in a POST body or exchange it server-side immediately, then redirect to a clean URL with no query string. For OAuth, complete the code exchange on the server and never let the authorization code sit in a client-visible URL longer than necessary. Make tokens single-use and short-lived so that even a leaked value is worth little by the time anyone reads a log.

Be honest about what the header does not cover:

  • Referrer-Policy only governs the Referer header. It does nothing about a token written into your analytics page-view payload, your error tracker's URL field, or browser history.
  • It does not shorten a token's lifetime or make it single-use. A leaked but still-valid token is an authorization problem the header cannot touch.
  • It is a browser-enforced control. It tells compliant browsers what to send; it is not a server-side guarantee and not a substitute for keeping secrets out of URLs.

Where VibeCodeGuard fits, and what it does not prove

VibeCodeGuard's Launch Check reads the Referrer-Policy response header on your public URL and flags an absent or lax value. On an app that uses magic-link or other URL-token auth, a missing header or a downgrading one like no-referrer-when-downgrade is exactly the signal worth surfacing before you share the link, and the scan reports it with severity and fix direction alongside the rest of your security headers. For the full header set this sits in, see the security headers field note; for the auth-flow side, see the magic-link security note.

What the scan reads is the header, not the behavior. It cannot watch your magic-link click flow and confirm that a token actually leaked into a Referer, and it cannot see what your analytics or third-party scripts do with whatever they receive. Those need the manual DevTools check above. It also will not tell you whether your tokens are single-use or short-lived; that is server-side logic and belongs in code review. A related control here is your Content Security Policy, which governs which third-party scripts load on the auth page in the first place; that is a separate header covered in the CSP note.

Next step: read the Referrer-Policy on your auth callback page in DevTools, set it to no-referrer on those routes, and then plan to move the token out of the URL entirely. Run a focused Launch Check against the public URL before sharing it, so a missing or lax header shows up in your scan and not in someone else's logs.

A clean Referrer-Policy header is one signal, not proof your tokens stay private. The scan confirms the header is set; it does not confirm the token never reaches a third party through analytics payloads, logs, or browser history. Treat a lax header on an auth-bearing app as a hardening task, and treat the URL-token pattern itself as the thing to design out.

> launch check

Scan the public surface before launch.

Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.