Skip to main content
Implemented Released in 0.5.0 Breaking change
Info

The fingerprint API is Rust-only: it has no Python SeleniumBase counterpart, and nothing in parity/api.toml changes. So this is a breaking change, listed in the changelog with a migration snippet. For how to use the API, see Fingerprints.

Why#

Version 1 kept every setting twice: a value (screen_width: Option<u32>) and a mode that said whether to use it (flags.screen_masking: MaskingMode). That allowed states that mean nothing, and it made callers say one thing in two places:

Version 1
Modes that overlap

MaskingMode::Mask and ::Custom did the same thing in every provider, Natural and Disabled did the same thing, and Auto was added to paper over the rest.

Value and mode disagree

A mode could be on with no value (a generic fill nobody asked for), or a value could be given while the mode was off.

Identity mixed with policy

Identity data (a time zone) and injection policy (is toString masked, are permissions granted) lived in the same struct.

The model#

One field per setting, four states:

Spoof<T>
StateMeansWhere
NaturalThe browser’s own value. The default.Every setting
RandomA random valid value, coherent with the rest, seeded by .seed(u64). Resolved by build().Every setting that has a random value
Value(T)Exactly this value. A plain value converts to it.Every setting
OffThe API is turned off: unavailable, or denied.Geolocation (denied), WebRTC (unavailable), media devices (none), battery, Bluetooth, speech voices, network information and Client Hints (unavailable)
  • The value and the mode are one field, so they cannot disagree.
  • Random is resolved by build(): the built Fingerprint holds Natural, Value or Off. A random value is drawn together with the rest of the identity (same operating system, same region, same locale), so it never contradicts a value the caller gave.
  • build() returns an error naming any setting outside the list above that was given Off.
  • Behaviour that is not a per-setting value (WebRTC IP policy, canvas and audio noise, native toString, granted permissions, blocked ports, …) is a StealthPolicy. A policy preset never changes a setting’s Spoof.
  • The built Fingerprint has private fields and read accessors. To change one, fp.to_builder(), change it, build().

Before and after (public signatures)#

Setting a value#

A plain value is Value. Spoof::Random, Spoof::Natural and Spoof::Off are accepted everywhere (Off is rejected by build() where it means nothing). Every other setter follows the same shape: platform, hardware_concurrency, device_memory, max_touch_points, vendor, locale, languages, accept_languages, pixel_ratio, color_depth, avail_screen, fonts, media_devices, proxy, battery, connection, speech_voices, bluetooth, client_hints.

Policy#

StealthFlags stays as a #[deprecated] alias of StealthPolicy.

Reading and editing#

Removed#

Warning

MaskingMode, NoiseMode, PopupMode, ProxyMaskingMode, StartupBehavior, Fingerprint::fills_defaults, Fingerprint::geolocation_popup, the public fields of Fingerprint, FingerprintBuilder::{flags, with_flags, canvas_seed, audio_seed}. CanvasNoiseMode becomes CanvasNoise (Natural, Persistent, Random, Low).

The example that must compile#

Version 2
Tip

The random position is a spot near Berlin, because the time zone is Value.

Random: how it stays coherent#

build() works out the operating system (the explicit one, else the one the user agent names, else Windows), then the place: the region of a Value time zone, else of a Value locale, else a drawn one. Every Random setting is taken from one seeded draw of that machine and that place (stealth::randomize), so a random locale agrees with a Value time zone, a random position is inside that time zone’s main city, and a random graphics adapter is one the operating system reports. build() returns FingerprintError when no coherent value exists (a random locale next to a time zone the place table does not know).

.seed(u64) seeds the draw and the canvas and audio noise, so the same calls give the same fingerprint. Without it the draw uses fresh entropy, and the seed it used is kept in the fingerprint.

JSON#

Spoof<T> serialises as "natural", "random", "off" or the value itself. Natural fields are left out. null reads as Natural. A fingerprint that holds "random" is resolved when it is read, the same way build() does it.

ProfileParams (external profile payloads) keeps its string flags, because the format is another tool’s, and reads the same words for every key:

Word in the payloadBecomes
natural, native, pass_through, whitelistNatural
autothe value the payload holds, else Natural
mask, spoof, custom, generic, random, noisethe value the payload holds, else Random
off, disabled, block, emptyOff where an API can be off (geolocation, WebRTC, media devices); Natural, logged, elsewhere
a literalValue
an unknown wordNatural, logged

The policy keys (canvas_noise, audio_masking, native_spoofing, grant_permissions, disable_csp, quic_mode, ports_masking) map to the policy. The full table is in Fingerprints: words every key reads.

As built#

Value types

The value types are ScreenSize, Geolocation, WebGl, WebRtcAddresses and MediaDevices (stealth/settings.rs); each converts from the obvious tuple. languages and accept_languages stay comma-separated strings, fonts is a Spoof<Vec<String>>, and Web Bluetooth is a Spoof<bool> (true: an adapter, false: none, Off: no API).

FingerprintError

FingerprintError has three cases: OffNotAvailable, NoRandomValue (proxy, oscpu, build_id) and Incoherent.

Seeds

Without .seed(..), a draw uses fresh entropy and a fingerprint with nothing to draw takes a seed from its user agent, so a preset is the same every time.

Denied geolocation

A denied geolocation (Off) is scripted even under native spoofing, because no protocol command denies the request for one tab.

See also#