Your Browser's Referer Header Tells Other Sites Where You Came From
Click a link and your browser quietly tells the destination which page you were on. That is the Referer header, and it can carry a full URL: path, query string and all. If that URL contains a password reset token or a search term, you have just handed it to a third party.
Yes, it is spelt wrong. The misspelling got into the HTTP specification early and stuck, so the header is Referer while the policy that controls it is spelt Referrer-Policy. Both are correct, which is mildly maddening.
What the browser actually sends today
Modern browsers no longer send the full URL everywhere. The default policy in current Chrome, Firefox and Safari is strict-origin-when-cross-origin. Chrome moved to it around version 85 and Firefox around 87, so older advice that says "the whole URL leaks" is often out of date.
In practice that default means:
- Same-origin requests get the full URL, including path and query.
- Cross-origin requests over HTTPS get only the origin, for example
https://example.org/. - A downgrade from HTTPS to HTTP sends nothing at all.
So the default is decent. The problem is everything that does not use the default: sites that explicitly set unsafe-url, older browsers, and pages that put secrets in URLs in the first place.
See it for yourself
A tiny Go server that prints what arrives is the quickest way to check. Run it, then link to it from any page on another origin.
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Referer: %q\n", r.Header.Get("Referer"))
})
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Go's net/http uses the correct HTTP spelling for the header, so r.Header.Get("Referer") is what you want. Note r.Referer() also exists as a shortcut.
Where this actually hurts
The classic leak is a secret in a URL. A password reset link like /reset?token=abc123 is fine until that page loads an image, script or analytics tag from another host, or contains an outbound link. Under a permissive policy, the token rides along in the Referer.
Other things that show up in URLs and then leak:
- Search queries on internal search pages.
- Session identifiers in query strings.
- Document or account IDs that reveal who you are or what you are reading.
- Internal hostnames on an intranet that links out to the public web.
Even an origin-only Referer says something. A site you visit can learn which pages of the web send you its way, and an ad network embedded in many sites can see which of them you read.
Quick detour: why Referer is not proof of anything
Hang on, people do use this header for security checks, so is it reliable? Not really. Browsers set it, but a script, curl or a proxy can send anything or nothing. Users can strip it with extensions, and policies can suppress it.
So a missing Referer is not evidence of an attack, and a present one is not proof of a legitimate source. For CSRF defence, prefer tokens and SameSite cookies, and treat Origin as a useful extra signal, not the Referer.
Turning it down on your own site
You have several levers, from broad to narrow. The broadest is the response header, which applies to the whole page:
func noReferrer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
next.ServeHTTP(w, r)
})
}
That value is the browser default, which is the point: set it explicitly so behaviour does not depend on the browser. For pages with sensitive URLs, such as reset or invoice pages, use no-referrer instead.
Per-element controls exist too:
<a href="..." rel="noreferrer">suppresses it for one link (and also impliesnoopener).<img referrerpolicy="no-referrer" ...>does it for a single resource.<meta name="referrer" content="no-referrer">sets the page-wide policy from HTML.
The better fix: keep secrets out of URLs
Policies reduce the damage, but the cleaner answer is to stop putting sensitive data in URLs. URLs also end up in server logs, browser history and proxy logs, so a token there leaks in more ways than one.
For reset links, a common approach is to exchange the token for a session on arrival and redirect to a clean URL, with no-referrer on that landing page. The token is single-use and short-lived either way, but removing it from the visible URL removes a whole class of leaks.
As a visitor
You mostly do not need to do anything, since the defaults are reasonable. If you want stricter behaviour, Firefox has the network.http.referer.XOriginPolicy preference and many privacy extensions can trim or drop the header. Expect the odd site that checks Referer to break, usually image hotlink protection or a login flow that was relying on it.
The details of each policy value are in the MDN Referrer-Policy reference, which is worth a skim if you are choosing between same-origin and strict-origin.