A browser describes itself to websites in two ways at once. The old user agent string travels in every request header and is readable from JavaScript. The newer User-Agent Client Hints split the same facts into separate headers (Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform and more) and a JavaScript API, navigator.userAgentData. On a normal browser all of these agree, because one piece of code writes them all. When something changes one copy and not the others, a site sees two different browsers in one visit.
This guide explains what each copy contains, why the user agent string was frozen, how high-entropy hints work, and how to check that the headers and JavaScript in your browser tell the same story.
The user agent string, and why it was frozen
The user agent string is a single line such as:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/<major>.0.0.0 Safari/537.36Most of it is history. "Mozilla/5.0", "AppleWebKit" and "like Gecko" were added over the years so that old sites would serve modern pages, and every Chrome-based browser still sends them.
For years the string also carried the exact browser build, the exact operating system version and, on Android, the phone model. That made it a cheap fingerprinting ingredient, sent to every server without asking. So Chrome-based browsers reduced it in stages during 2022 and 2023:
- the minor, build and patch numbers became
0.0.0, leaving only the major version; - the macOS version is frozen at
10_15_7, whatever version you run; - Windows always says
Windows NT 10.0; Win64; x64, for Windows 10 and Windows 11 alike; - on Android the device model is replaced with a fixed
K.
The detail did not disappear. It moved into client hints, where a site has to ask for it.
Client hints: the structured copy
User-Agent Client Hints divide the user agent into named values. They come in two tiers.
Low-entropy hints, sent by default
On every request over HTTPS, a Chrome-based browser sends three hints without being asked:
| Header | Example value | What it says |
|---|---|---|
Sec-CH-UA | "Google Chrome";v="<major>", "Not_A Brand";v="8", ... | The brands and major versions |
Sec-CH-UA-Mobile | ?0 | Whether this is a phone (?1) or not (?0) |
Sec-CH-UA-Platform | "macOS" | The operating system name |
The same three values are available to scripts as navigator.userAgentData.brands, .mobile and .platform, in the page and in Web Workers.
High-entropy hints, sent on request
The more identifying values are only given out when a site asks. A server asks by sending an Accept-CH response header listing the hints it wants, and the browser includes them in later requests to that site:
Accept-CH: Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Model, Sec-CH-UA-Full-Version-ListA script asks through [getHighEntropyValues](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorUAData/getHighEntropyValues), which returns a promise:
const hints = await navigator.userAgentData.getHighEntropyValues([
'platformVersion', 'architecture', 'bitness', 'model', 'fullVersionList',
]);
console.log(hints);The answer includes the full browser version down to the build, the real operating system version (which is how a site tells Windows 11 from Windows 10 now that the string says the same for both), the processor architecture and bitness, and the device model on Android phones.
GREASE: the brand that is not a brand
The brand list always contains an entry like "Not_A Brand";v="8" or "Not/A)Brand";v="99", with punctuation, version and position that change between releases. This is deliberate. The specification calls it GREASE: by inserting a meaningless brand, browsers stop servers from writing brittle code that expects an exact list. It also means the brand list has a recognisable shape for each browser version, which matters below.
Why changing only JavaScript contradicts the headers
Suppose a tool wants a profile on a Mac to look like Windows. The simplest approach is a script that runs before the page and replaces navigator.userAgent. Look at what a site can now compare:
- Header against script. The
User-Agentrequest header and theSec-CH-UA-Platformheader are written by the browser's network code, which the script never touches. The server receives "macOS" in the headers while the page's JavaScript says Windows. - Hints against string.
navigator.userAgentData.platformstill says "macOS" unless the script also replaced it. Even if it did,getHighEntropyValuesis a separate function, and itsplatformVersionandarchitecturehave to fit a Windows machine too. - Version against version. The major version in the string must match the brands in
Sec-CH-UAand the full version list. A user agent copied from a list of "common" strings is often a release or two away from the engine actually running, and the hints report the real one. - Brand list shape. Each release has its own brand order and GREASE entry. A hand-written brand list that looks like no shipped release stands out.
- Page against worker. A Web Worker has its own
navigator. A script injected into the page does not run there unless it is injected into every worker in time, so the worker gives the original answer. - Platform against everything else.
navigator.platform, the fonts installed, the graphics renderer and the TLS handshake all belong to the real operating system. A Windows user agent on top of macOS fonts and an Apple GPU is a contradiction no header can fix.
Changing the header with a command-line switch has the opposite problem: the header moves, and the client hints and JavaScript stay behind. The only way to keep all of them in step is for the code that writes every copy to agree, which is the case on an ordinary browser and in a browser that changes these values in its engine. Our page on engine-level fingerprint protection explains that difference in detail.
How to check your own browser
You can compare both sides in a few minutes.
In the request headers
- Open DevTools (F12, or Cmd+Option+I on a Mac) and choose the Network tab.
- Reload any HTTPS page and click the first request (the document).
- Under Request Headers, find
user-agent,sec-ch-ua,sec-ch-ua-mobileandsec-ch-ua-platform. Note the major version and the platform.
In JavaScript
In the Console tab, run:
navigator.userAgent;
navigator.userAgentData.brands;
navigator.userAgentData.platform;
await navigator.userAgentData.getHighEntropyValues(['platformVersion', 'fullVersionList']);The platform and major version should match what the headers said, and the full version list should begin with the same major version as the string.
With the free fingerprint test
Our browser fingerprint test reads every client hint from JavaScript, the low-entropy ones and the high-entropy ones, and lists them in its identity section: platform, brands, mobile, OS version, processor, device model and full versions. Then three consistency checks do the comparison for you:
- User agent vs platform compares the string with
navigator.platform. - User agent vs client hints compares the operating system, major version and mobile flag in the string with the hints and the full version list.
- Page vs Web Worker reads the same values from a worker and lists any that differ.
The test runs entirely in your browser and makes no network request, so it sees the JavaScript side only. Use DevTools for the header side, as above. If both agree, the two copies are in step.
How Browz keeps them in agreement
Browz changes what a site reads inside the browser engine, not with a script added to the page. That matters most for the user agent, because the engine is the one place that writes the header, the client hints, navigator.userAgentData and the value a worker reads.
- One source for every copy. The user agent, the client hints and their brand list match the browser version the profile really runs and the operating system it presents, as listed on our anti-detect page. Headers, page, iframes and workers are answered by the same code.
- The brand list of a real release. Because the version in the hints is the version running, the brand order and GREASE entry are the ones that release ships with.
- Part of one device. Each profile's platform, fonts, graphics names and screen are derived together, so the user agent is not the only thing that says which operating system the profile is on. You can set the platform and user agent to Auto, Custom or Real per profile on the engines page.
- The network side agrees too. TLS and HTTP/2 come from the engine's own network stack. We cover that layer in TLS fingerprinting explained.
We run the free test against each release and publish the result. On 13 November 2026, Browz 0.1.9 on macOS passed 15 of 15 consistency checks, including user agent against platform and against client hints, in both a normal window and headless mode; see our own result.
Frequently asked questions
What is the difference between the user agent and client hints?
The user agent is one string sent with every request. Client hints split the same information into separate headers and a JavaScript API, and hold back the most identifying details until a site asks for them. On a normal browser they describe the same browser, version and operating system.
Why does my user agent show an old macOS version?
Because Chrome-based browsers freeze the macOS version in the string at 10_15_7 to make it less identifying. Your real version is still available to sites that ask for the platform version hint, through Accept-CH or getHighEntropyValues.
Is changing my user agent enough to change my fingerprint?
No. The user agent is one value among many, and it is cross-checked against the client hints, the platform, the fonts, the graphics renderer and the network handshake. Changing it alone usually creates a contradiction that is easier to notice than the original value. See fingerprint consistency for the other pairs sites compare.
Do Firefox and Safari send client hints?
At the time of writing, User-Agent Client Hints are sent by Chrome-based browsers. Other browsers rely on the user agent string, so a site that receives no hints from a browser that claims to be Chrome has found a mismatch of its own.
What is Sec-CH-UA-Full-Version-List?
It is a high-entropy hint that lists each brand with its full version, down to the build. A site gets it only after asking with Accept-CH, or by calling getHighEntropyValues with fullVersionList. Its major version should always match the one in the user agent string.