navigator.hardwareConcurrency, the WebGL renderer string, screen.availHeight, the fonts it can measure, the voices speechSynthesis lists, and the HTTP headers the browser sends. A detector reads many of them and checks that they agree with each other.
Apostate changes each of these values in Chromium’s C++, at the place Chromium produces it. Every API, worker, iframe and header that reads the value then reads the new one, and Apostate adds nothing to the page that a script could find. The values come from one document, the profile, which the browser composes from a seed when it starts.
The profile
The profile is a JSON document of the values a machine shows: platform, User-Agent, cores, memory, GPU strings and limits, WebGPU adapter, screen, fonts, voices, audio, media devices, network, battery and a few more. Profile schema lists every section and field. A section the profile leaves out stays the host’s. The browser process composes the profile before it starts any other process. Every child process, whether renderer, GPU process or network service, receives the same profile, base64-encoded, in its--apostate-profile switch. No child draws values of its own, so a value computed in two processes agrees. The Device-Memory header, which the browser process sends, and navigator.deviceMemory, which a renderer answers, read the same number.
Composition runs in ChromeMainDelegate::PreSandboxStartup, after the user data directory is known and before any child exists. On Linux that is before the zygote forks, so every renderer forked from it carries the profile. The browser writes nothing to disk apart from the identity file.
Composing a machine
The catalogue says what a machine can be. It lives in three places in the repository:scripts/generate-dispersion-tables.py compiles them into the browser during the build. The browser never reads them from disk.
A machine is a series of weighted choices, made in this order:
- platform (the persona)
- OS release
- GPU family
- GPU model
- machine class, such as a desktop tower, a 15-inch laptop or a MacBook Pro with a Studio Display
- CPU cores
- memory
- screen
- taskbar, menu bar or panel
- font packs
- media devices
- audio
- network
- battery
- voices
- extensions
--fingerprint-locale and --fingerprint-timezone. See Locale and timezone.
--fingerprint-explain prints every choice a launch made, the layer that made it and the evidence behind the option, then exits. Personas shows the output.
GPU families
A GPU family is a set of WebGL and WebGPU values measured on real hardware: the WebGL limits, the extension list, shader precision, context attributes, and the WebGPU adapter’s features and limits. A persona takes one family whole. Mixing values from two families would describe a device that does not exist.
Within a family the WebGL values are identical for every model, so a persona can name any model of the family by its renderer string. The WebGPU adapter’s architecture name follows the model’s chip generation, and so does the smallest subgroup size on Intel’s 12th-generation graphics. GPU models lists the models, and GPU covers choosing one.
The persona picks the family, not the host’s GPU. A Windows persona takes a Direct3D 11 family on any host, including a Linux server with no GPU. The host’s CPU family narrows the choice, because a Windows persona reports it. An x86 host gets the NVIDIA or Intel family, and an ARM host gets the Qualcomm family.
Where the values come from
The project captured real machines with the collector page incapture/, in headed Chrome, and keeps the results in resources/fingerprints/raw/:
- macOS on an Apple M4 Max
- Windows with an NVIDIA RTX 3070 Ti, an NVIDIA RTX A4500 and Intel UHD Graphics 630
- Windows 11 on ARM on a Snapdragon X2 Elite laptop with a Qualcomm Adreno X2-90
- Linux with an NVIDIA RTX 3060, RTX 3090, RTX 4070 Ti SUPER, RTX 4080 SUPER and RTX PRO 4000
- stock headless Chrome on Linux and macOS, for comparison
evidence field says which of these it is.
A value changed at its source
Every change is a patch to the Chromium code that produces a value. Take the core count. A page reads it fromnavigator.hardwareConcurrency, but Chromium also sizes its thread pools from the same number, in base::SysInfo::NumberOfProcessors(). Changing only the JavaScript getter would leave a 32-thread browser claiming 8 cores, and a page that times parallel work in workers would see the difference.
Patch 0004 changes the function the thread pools and the getter both call:
navigator.platformis set at the constantNavigatorBasereturns, so workers report the same value as the window.- The User-Agent Client Hints come from the one
UserAgentMetadatastruct that feeds everySec-CH-UAheader andnavigator.userAgentData. - The screen is set on the
ScreenInfothe browser process sends to renderers, soscreen.*, the CSS media queries and the pixel-ratio client hint agree, in top frames and in out-of-process iframes. - One predicate hides fonts from canvas and CSS text measurement,
@font-face local()andqueryLocalFonts().document.fonts.check()returns true for any family name, installed or not, so it does not read the font list.
patches/series lists the patches in the order they apply, and each patch file starts with a description of what it changes. Patches explains how to write one.
Which setting wins
Strongest first:--apostate-profiledescribing a device: your profile, nothing composed.--fingerprint=host: the host’s own values, nothing composed.- Per-field switches, such as
--fingerprint-hardware-concurrency: one value each, on top of the seed’s machine. --fingerprint=SEED.- The seed in
DIR/apostate/identity, when--user-data-dir=DIRis set. - A fresh seed from OS entropy.
--fingerprint=host refuses to launch together with a persona, a GPU family or a per-field switch. A written profile replaces the seed’s machine, so the packages refuse --fingerprint in args next to a profile. Switches lists every switch.
Host mode
--fingerprint=host turns composition off. The browser builds no profile, and every value a persona would change is the host’s. Use it to tell whether a problem comes from the persona or from the machine and network under it. Personas has the details.