A token in your URL is a token in your CDN logs, your history, and someone's Referer
A URL feels private. You click a link, the page loads, the address bar shows where you are, and that's the end of it as far as most people think. It isn't. A URL is one of the most-copied strings in the whole web stack: it's logged at every hop, saved by the browser, and quietly handed to the next site you visit. So when a credential rides along in the query string, it goes everywhere the URL goes, which is a lot further than you intended.
The shapes are familiar once you look for them. ?access_token=eyJ..., ?api_key=sk-..., ?session=..., ?sig=... on a download link. Each one is a live secret, sitting in the part of the request that gets recorded the most.
Where a URL actually travels
The problem isn't that someone might read the address bar over your shoulder. It's the four places a URL is copied automatically, every time, without anyone deciding to.
It goes into server and proxy access logs. Every reverse proxy, load balancer, and CDN in front of your app writes the full request line, query string included, to a log file that's retained for weeks and read by more people and systems than you'd guess. It goes into browser history, saved in plain text on the device and synced across the user's other devices. It goes into the browser cache and, if the page errors, often into error-reporting tooling. And then there's the Referer header. When the page that loaded with that token in its URL fetches an image, a font, or an analytics script, the browser attaches the URL it came from. For a same-origin fetch that's the full URL, token and all. For a cross-origin fetch the modern browser default (strict-origin-when-cross-origin) trims it to just the origin, which protects you, but only if you haven't set a looser Referrer-Policy like unsafe-url or no-referrer-when-downgrade, and plenty of sites still do. The header is the one sink you can partly control with policy, and the one people most often leave wide open.
Not every value in a query string is a credential
A check here has to be careful, because URLs are full of harmless parameters and flagging ?page=2 or ?token={{csrf}} would be useless noise. The thing that makes a URL parameter a finding is two facts together: the name and the value.
The name has to be one that carries auth material, access_token, id_token, session, api_key, client_secret, password, sig, and the like. And the value has to look like a real secret, not a template or a label. A live JWT, or 20-plus characters of base64url or hex, is a credential. A {placeholder} or a short human-readable word is not. Both signals have to hold, on a URL that actually appears in your served HTML, before it counts. That's what separates "a token is published on your page" from "a query string has a parameter in it."
This is a medium-severity finding. It's distinct from a token leaking inside an API response body, which is its own problem; this one is specifically about the credential being in the URL, the single most copied place you can put it.
Keep secrets out of the address bar
The fix is a rule, not a config change: a token never goes in a URL. Send it in an Authorization header or a POST body, where it isn't logged as part of the request line, isn't saved in history, and isn't sent in the Referer. For downloads that genuinely need a credential in the link, use short-lived, single-use signed URLs so a leaked one expires fast, and set a strict Referrer-Policy so the value isn't handed to third parties on the page. Anything already shipped this way should be rotated, because you have to assume it's in a log somewhere.
The credential in a URL is something a stranger reads straight out of your page source, no login and no guessing, which is exactly how SurfaceCheckr finds it: it scans the HTML you serve for URLs whose parameter name carries auth material and whose value is a real, high-entropy secret rather than a placeholder. It reports that the token is published, and it won't flag an ordinary ?page= parameter to pad the count, because a false credential is worse than a missed one. The whole point is to catch the secret that's sitting in the most-copied string on your page before it finishes propagating.
If the token leaked inside a response body instead of a URL, that's the API response leaking secrets article, and the broader question of what your page source gives away is in internal hosts and dev comments in page source.
Read next
- An OpenAI or Anthropic key in your frontend is someone else's free computeThe secrets hiding in your JavaScript
- GitLab, DigitalOcean, GCP: the infrastructure tokens that leak into bundlesThe secrets hiding in your JavaScript
- Is your Stripe secret key in your JavaScript bundle right now?The secrets hiding in your JavaScript
- Why is there an AWS key in your build, and who can use it?The secrets hiding in your JavaScript
Find it before someone else does.
Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.