Back to Knowledge Hub
Fingerprint

Navigator Properties Fingerprinting: Browser Leaks

How navigator values such as platform, hardwareConcurrency, deviceMemory, languages, and userAgentData combine into a fingerprint signal, and how to check that they stay coherent.

BotBrowser Team

Documentation

Prefer the maintained product doc?

This article has a matching page in the docs center. Use the docs for the canonical setup flow, current flags, and long-term reference.

Which Navigator Values Form the Signal

The navigator object is the most basic place where a page learns about the browser it is running in. It needs no permission prompt, it is available as soon as a script starts, and a large share of ordinary web code reads it for legitimate reasons, such as choosing a layout, sizing a pool of workers, or picking a display language. That same availability makes it a standard input for fingerprinting. No single property identifies a person. The signal comes from the combination: a script reads many values, joins them into one record, and compares that record with the records it has seen before.

The W3C guidance on mitigating fingerprinting in web specifications describes this pattern directly. Each browser capability adds a little more variation that a script can read, and many small differences together can be enough to tell browsers apart even when no single value looks unusual. For that reason the sections below do not quote a fixed uniqueness figure for navigator values. How identifying a given combination is depends on the population of browsers that a site actually sees, and a number measured on one audience does not transfer to another.

A loaded profile feeds the main thread, a Web Worker, and Sec-CH-UA request headers, which all report the same platform, core count, memory tier, languages, and brand. Below, a changed User-Agent string next to an unchanged platform value produces a mismatch.

The diagram shows both halves of the topic. When one device description feeds every place that answers a navigator question, the answers agree. When only one value is changed, the related values still describe the original device, and the disagreement becomes a signal of its own.

Identity values: userAgent, platform, and vendor

navigator.userAgent is the oldest and most frequently read value. It carries a browser name, a version, and an operating system token. Chromium-based browsers now reduce the detail in this string, so the operating system version and the minor browser version are no longer spelled out, and the finer details moved to User-Agent Client Hints. navigator.platform is a short string such as "Win32", "MacIntel", or "Linux x86_64". MDN marks it as deprecated, but current browsers still return a value and scripts still read it. navigator.vendor returns a vendor string that differs between browser families.

These three values describe the same two facts, which browser this is and which operating system it runs on, so a script can check that they agree with one another. An honest browser passes that check without any effort, because every value comes from the same installation. A modified browser passes only if every related value was modified together.

Hardware, locale, and connection values

navigator.hardwareConcurrency returns the number of logical processors available to run threads. Browsers may report a lower or rounded number than the machine really has, so treat it as a hint that depends on hardware and browser, not as a precise inventory. navigator.deviceMemory is an approximation of device RAM in gigabytes. It exists only in Chromium-based browsers, it is available only in secure contexts, and the reported value is capped at 8, so a machine with more memory still reports 8. Both values can be read inside a Web Worker as well as on the main page, which matters for the checks later in this article.

navigator.language returns the preferred language, and navigator.languages returns the ordered list of preferred languages, for example en-US followed by en. The list and the Accept-Language request header normally derive from the same browser setting, so the value a page reads and the value a server receives describe the same preferences. A list that is unusually long, unusually short, or unlikely for the reported region makes the combination easier to distinguish.

navigator.connection exposes effectiveType, downlink, rtt, and saveData in Chromium-based browsers. These values describe network conditions rather than hardware, and matching Client Hints headers can carry the same information on requests. Support differs between browsers, and the readings can change with the network, so they behave differently from the hardware values above. They still belong to the same record that a script assembles.

Client Hints and userAgentData

navigator.userAgentData is the structured counterpart of the User-Agent string in Chromium-based browsers. It exposes the list of browser brands, a mobile flag, and a platform name directly. Its getHighEntropyValues() method returns more detail on request, such as the platform version, architecture, bitness, model, and full version list. The same information travels in Sec-CH-UA request headers. The brand list, the mobile flag, and the platform are sent by default, while the higher-entropy values are sent only after the server asks for them in an Accept-CH response header.

As a result, three places answer questions about the same browser: the User-Agent string, the userAgentData object, and the Client Hints headers. A coherent browser gives the same answer in all three, and a script or a server that compares them needs only a few lines of code to do so.

Common configurations are shared by a very large number of browsers, and unusual ones are shared by very few. A typical laptop core count with a mainstream platform string and a widespread language list says little on its own. A core count far from what consumer devices usually report, a rarely seen memory tier, or an unusual language list narrows the group of browsers that could have produced the record. The exact effect depends on the audience of the site that collects it, which is why no single figure is given here.

Why One Changed Value Creates a Mismatch

The practical lesson is that navigator values are not independent. They describe one machine, and a site can check that they describe the same one. When only one value changes, the others still describe the original machine, and the difference between them is a signal of its own.

A changed User-Agent string

Replacing the User-Agent string is the oldest approach. Suppose an extension changes navigator.userAgent to a macOS string while navigator.platform still returns "Win32". The two values now name different operating systems. The same problem appears when navigator.userAgentData still reports the original platform, or when Client Hints headers are generated from different settings than the JavaScript values.

A site does not need to know which value is true. It only needs to see that they disagree, and a disagreement is more distinctive than either value was alone.

Randomized values

Randomization has a similar weakness. Picking a random core count and a random memory value for each session produces combinations that few real devices have, for example a mobile platform string next to a desktop-class core count, or a memory value that does not fit the rest of the reported hardware.

A value that changes on every visit also fails the simplest stability test, because a real browser reports the same hardware numbers the next time. Values that are both unusual and unstable are more distinctive than the honest ones they replaced.

Changes made in the wrong place

Where a change is made matters as much as what is changed. A script injected into the page can only affect what the main thread reads. A Web Worker has its own global scope and its own navigator, so a page-level override that does not reach workers leaves the original values visible there. Request headers are produced by the browser's networking code and not by page script, so a page-level override never changes them. The result is three answers where there should be one.

Other common measures do not address this signal either. A VPN changes the network address and leaves navigator values as they were. Private browsing keeps the same hardware and language values. Clearing cookies removes stored state but does not alter what the browser reports about itself.

A short set of rules describes what coherence means in practice:

  • The operating system in the User-Agent string matches navigator.platform and the platform in navigator.userAgentData.
  • The browser brand and major version agree between the User-Agent string, the userAgentData brand list, and the Sec-CH-UA header.
  • The core count and memory tier are plausible for the device class that the platform implies.
  • navigator.languages is plausible for the reported locale and agrees with the Accept-Language header.
  • Every one of these values is the same in the main thread, in workers, and across repeated sessions.

Verifying Coherence Across Threads and Headers

The goal of verification is not to find one correct number. It is to confirm that every place that answers a navigator question gives the same answer. The checks below need only a browser and a test page that you control, and they do not require visiting any third-party site.

Main thread and Web Worker

Open a page you control and read the values on the main thread: the User-Agent string, the platform, the vendor, the logical processor count, the device memory, the language list, and the brand list and platform from navigator.userAgentData. Record them. Then start a dedicated Web Worker from the same page, have it read the same properties from its own navigator, and send the result back to the page.

Compare the two records field by field. They should be identical. A difference usually means that a value was set by page script, or that the worker reads from a different source than the page does. A dedicated worker is the simplest to test, and shared and service workers can be added to the same comparison.

Client Hints headers

Next, compare the output of getHighEntropyValues() with what a server receives. The method returns a promise for the values you list, for example the platform version, architecture, bitness, and full version list. On the server side, the higher-entropy headers arrive only after an earlier response included an Accept-CH header that asks for them. The test page therefore needs a server you control that sends Accept-CH and records the request headers it receives.

Compare the brand list and the platform in Sec-CH-UA and Sec-CH-UA-Platform with the userAgentData object, and compare the platform version and architecture headers with the high-entropy result. All of them should describe the same browser and platform as the User-Agent string.

Repeated sessions

Close the browser, start a new session with the same profile and the same configuration, and run the same checks again. Values that describe the platform and hardware should be the same in every session. If you want to confirm that the values change together, run one more session with a different profile and check that every view moves to the new device description at once.

Keep the browser version and the profile version fixed during a comparison. A version change legitimately changes brand and version values, and mixing that change into the comparison hides the result you are looking for.

The full checklist, in order:

  1. Record the navigator values on the main thread.
  2. Record the same values inside a Web Worker and compare them field by field.
  3. Request high-entropy values and compare them with the userAgentData brand list and platform.
  4. Capture the Client Hints headers on a server you control and compare them with the JavaScript values.
  5. Repeat everything in a fresh session with the same profile and compare the records.
  6. Change one setting at a time, and repeat the comparison after each change.

Treat the first disagreement as the finding. Note which two views disagree, and go back to the configuration instead of adding another override on top, because each additional override adds one more place that may disagree.

Launching With a Profile

With BotBrowser, navigator values come from the loaded profile, so the simplest launch is a profile and a temporary user data directory:

chrome --bot-profile="path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

The BotBrowser documentation describes the profile as the source of identity, hardware, locale, network, and media device values, and states that JavaScript values, HTTP headers, and Worker contexts return consistent values from it. Start with this baseline and run the checks above before adding any other flag. Without a profile, navigator values reflect the machine you are actually running on.

Language and locale can be set explicitly with --bot-languages and --bot-locale. For network information, --bot-network-info-override controls the navigator.connection values and the matching Client Hints headers, and the documentation scopes the resulting policy to the BrowserContext. Set it before creating the first page in that context, so that every page and worker in the context sees the same values.

chrome --bot-profile="path/to/profile.enc" \
       --bot-languages="en-US,en" \
       --bot-locale="en-US" \
       --bot-network-info-override \
       --user-data-dir="$(mktemp -d)"

Prefer choosing a different profile over overriding single values on top of one. A profile is built so that related values agree, and changing one of them, the core count for example, leaves the values related to it unchanged. Custom User-Agent control, language and locale flags, and some identity flags, such as browser brand and version, are tier-gated in the documentation, so confirm what your license includes before you plan around them.

What BotBrowser Controls and What It Does Not

BotBrowser derives navigator values (userAgent, platform, hardwareConcurrency, deviceMemory, languages, and userAgentData) from the loaded profile, so the main thread, workers, and Client Hints headers return one consistent set, and the --bot-network-info-override flag controls navigator.connection values and the matching Client Hints, which gives you a repeatable setup for running the coherence checks above. BotBrowser cannot stop websites from collecting or correlating navigator values with other signals, cannot guarantee that a profile matches real hardware or a site's expectations, and does not replace network, account, or behavioral consistency; custom User-Agent control depends on license tier.

The limits are worth spelling out, because a coherent set of navigator values answers only one question that a site can ask.

  • A site can still read navigator values and combine them with canvas, WebGL, font, and screen signals. Each of those surfaces needs its own review.
  • A profile describes a device, and it is not that device. A site may hold expectations about hardware or software that no profile can know in advance.
  • Network location, account history, reputation, and behavior are separate inputs. Consistent navigator values do not change any of them.
  • The documented behavior applies to a valid profile loaded in a supported setup, so verify it in your own configuration with the checklist above.

Treat navigator coherence as one part of a consistent configuration. The checklist tells you whether that part holds, and it does not say anything about how a particular site will treat a session.

Questions About Navigator Values

Q: Is hardwareConcurrency enough to identify someone? A: No. It is one input among many. Common values are shared by many browsers, and uncommon values narrow the group further, but the useful signal comes from the combination with platform, memory, language, and the other surfaces.

Q: Why does deviceMemory stop at 8? A: The value is an approximation, and Chromium-based browsers cap it to limit how precisely it describes the machine. Other browser families do not expose the property at all, so its absence is normal there.

Q: Does changing only the User-Agent string help? A: Not when navigator.platform, navigator.userAgentData, or the Client Hints headers still describe the original browser. Related values need to change together.

Q: Do VPNs or private browsing change navigator values? A: No. The values come from the browser and the operating system, not from the network route or the browsing mode.

Q: Why compare a Web Worker with the main thread? A: A worker has its own navigator. A value that was changed only in page script remains unchanged there, so the worker is the quickest place to find a gap.

Q: Does BotBrowser make a profile equal to real hardware? A: No. The profile defines the values that the browser reports and keeps them consistent across contexts. It does not turn the machine into the device the profile describes.

For related topics, see Client Hints, CPU core count, timezone, locale, and language, and network information. The full set of controls is on the features page, and you can verify navigator consistency yourself in the Proof Center.

Sources

Public references used for the navigator, Client Hints, and BotBrowser statements in this article:

#Navigator#Fingerprinting#Browser Identity#Privacy

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.