uaFullVersion is one string: the full version of the browser's main brand. fullVersionList is the same information for every brand the browser lists, Google Chrome, Chromium and the GREASE entry, each with its own full version. The low-entropy brands list carries only majors. In a real Chrome all of them agree, and every full version has four numbers. Ours did not: for a while our engine reported uaFullVersion as "153", a version no Chrome has ever shipped, and fixing it moved FingerprintJS's confidence from 1 to stock's exact 0.99.

uaFullVersion vs fullVersionList, and the third reader

A page can learn Chrome's version from three places, and they are deliberately different in how much they say.

navigator.userAgent                      Chrome/153.0.0.0
navigator.userAgentData.brands           Chromium 153, Google Chrome 153, GREASE
getHighEntropyValues([...])              uaFullVersion, fullVersionList

The User-Agent string was reduced some years ago so it carries only the major, padded with zeros. brands is sent to every site and also stops at the major. The full build number lives in the high-entropy hints, which a page has to request, by script through getHighEntropyValues() or by asking the server for the Sec-CH-UA-Full-Version-List header. uaFullVersion is the older, single-string form and is deprecated in favour of the list, but it still answers, and detection scripts still read it because a mismatch between the two is cheap to spot.

A real Chrome version is major.minor.build.patch, such as 152.0.7977.83. Three things follow from that, and each is something a script can check in a line: every full version has four parts, the major in the full version equals the major in brands and the User-Agent, and the Chrome and Chromium entries carry the same build.

Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.

How it works: Machines that exist

How ours came out as "153"

Our engine, Tesseract Chrome, takes the brand version from one switch, --fingerprint-brand-version, and that one switch feeds all three readers. Inside the engine, GetChromiumVersion() returns the switch value verbatim, and GetMajorVersion() cuts the major off it.

We were sending the major alone. So brands, which wants the major, was right, and both of the other readers repeated the bare number:

stock   uaFullVersion "152.0.7977.83"   fullVersionList 152.0.7977.83
ours    uaFullVersion "153"             fullVersionList 153

The code that builds the switch, mosaicArgs(), already took the engine's real version as a parameter. It threw it away with void engineVersion, which was the one thing the parameter was there for. It sends the full version now, and uaFullVersion reads 153.0.8010.37.

The effect was measured on the FingerprintJS demo: its confidence moved from 1 to 0.99, which is the figure stock Chrome scored on the same machine. A different problem on that demo, its failure to name our browser at all, turned out to be about Google's branding rather than the version; that investigation is in when the FingerprintJS demo can't name your browser.

When a profile claims an older major

Every profile's claimed major follows the engine's real one, so the running binary's own version is normally what goes out. When a profile does claim another major, which the profile audit already flags, the full version comes from that major's real build number in our version table, not from an invented .0.0.0. A zeroed build is a tell of its own.

That table had drifted. It topped out at 149 while the engine was on 153, so it was refreshed from Google's own release data rather than typed in by hand.

Two things that looked wrong and were not

The GREASE brand reads Not_A Brand with version 8 at major 153, and we checked whether that was correct before touching it. It is. Chrome builds the GREASE name and version from the major itself: two characters picked from a fixed set of eleven by seed % 11 and (seed + 1) % 11, placed around A, and a version picked from three by seed % 3. At 153 that gives an underscore, a space and 8. At 152 the same algorithm gives stock's Not?A_Brand and 24. Changing it to look more random would have made it wrong.

The second is why we refuse to claim Edge. The same brand-version switch feeds the client-hint lists, which need the full build, and the User-Agent suffix, which needs major.0.0.0. For Chrome those two shapes come from one version. For Edge's brand they would not, so a profile could have a correct User-Agent or a correct set of client hints and never both. Profiles claim Chrome.

How to check the version your browser reports

Open DevTools on any page and run:

navigator.userAgentData
  .getHighEntropyValues(['uaFullVersion', 'fullVersionList'])
  .then(console.log)

Then read the answer against the rules above. Every version should have four parts. The Google Chrome and Chromium entries should carry the same full version, and its major should match brands and the Chrome/ token in navigator.userAgent. Compare with a branded Google Chrome of the same major rather than with another Chromium build, for the reasons in the control that settles "is it our patches?".

None of this is a fingerprint in the sense of something unique to you. It is a consistency check, and the browser either passes it or contradicts itself. How we keep the rest of a profile's values agreeing with each other is on real fingerprints, and what a page can read in general is in the browser fingerprint glossary entry.