# Firefox resistFingerprinting, Tor Browser and Mullvad Browser

> When Firefox reports fixed values on purpose, WhiskerJS leaves nine components out instead of reading normalised values, and it only does so when every trait is present.

Source: https://whiskerjs.com/browsers/firefox-rfp-and-tor/ (last updated 2026-10-26)

> **In plain words:**
>
> Firefox has a setting called resistFingerprinting, and Tor Browser and Mullvad Browser use it too. It makes the browser report the same, deliberately ordinary values as many other users: the time zone, the screen size and several graphics details. Reading those values would add nothing useful, so when WhiskerJS sees all the signs of this mode it simply does not collect nine of its measurements. It is careful to switch only when every sign is there, so an ordinary Firefox is not affected.

## At a glance

- Browser class: `firefox_rfp`, a special case of Firefox; `rfp_hardened` for Tor Browser and Mullvad Browser (since signalsVersion 6).
- Excluded components: 9, with ruleId `firefox_rfp` or `rfp_hardened` (7 of them in the visitorId). Both rows list the same components.
- Also unavailable by design, as in any Firefox: `device_memory`, `heap_limit`, `ua_client_hints`, `ua_versions`, `keyboard_layout`.
- Lab sample: none.

## The exact test

A Firefox (a `Firefox/<number>` user agent token or `mozInnerScreenX`) is classed `firefox_rfp` only when **all** of these hold, in this order, as written in `src/browser-class.ts`:

1. The Intl time zone is `UTC`, `Etc/UTC` or `Atlantic/Reykjavik` (newer releases report the last).
2. `navigator.webdriver` is not `true`.
3. `screen.width` is a positive number equal to `innerWidth`, and `screen.height` equals `innerHeight` (the screen is the content area).
4. `screen.availWidth` and `availHeight` equal the screen size (no space kept for a taskbar).
5. `devicePixelRatio` is exactly 1.
6. `screenX` and `screenY` are 0 (or absent).

If all six hold, the class is `rfp_hardened` when, in addition, the content area is on the letterboxing steps (`innerWidth` a positive multiple of 200 and `innerHeight` a positive multiple of 100 CSS pixels) and `navigator.languages` is `en-US` alone or `en-US,en`; otherwise `firefox_rfp`.

Otherwise it is plain `firefox` (or `firefox_fpp` when the canvas or WebGL known-answer probe came back altered; see [Firefox](https://whiskerjs.com/browsers/firefox/)). Each trait alone is common in stock Firefox, and stock Firefox under automation matches several of them (Playwright runs it in UTC with the screen equal to the viewport), so the test errs towards `firefox`. A missed resistFingerprinting browser only leaves normalised values in the result; a false positive would drop nine components from a normal browser.

### The signalsVersion 5 fix

Before signalsVersion 5, stock Firefox under automation was wrongly classed `firefox_rfp` and lost nine components. The class now needs every trait at once, accepts `Atlantic/Reykjavik` as an RFP time zone, and never applies when `navigator.webdriver` is true. The id effect is limited to Firefox browsers that were wrongly classed before. See [What’s new](https://whiskerjs.com/whats-new/).

## What works

Everything not in the exclusion row: offline audio, fonts and font preferences, WebGL parameters, Math, codecs, plugins, languages and Intl locale, color depth, touch support, storage presence, permission states and the display preference queries. Several of these will also be normalised by resistFingerprinting (for example the language), so expect them to be alike across users; WhiskerJS reports what the browser says.

## What is unavailable or blocked

The Chromium-only components are `unavailable` / `no_api` as in any Firefox. [`audio_latency`](https://whiskerjs.com/signals/audio-latency/) is skipped on desktop. [`canvas_readback`](https://whiskerjs.com/signals/canvas-readback/) and [`noise_profile`](https://whiskerjs.com/signals/noise-profile/) are not excluded, but resistFingerprinting changes canvas readback, so `altered` or a probe state other than `exact` is likely; that is useful information in itself.

## What WhiskerJS excludes and why

| Component | In visitorId | What resistFingerprinting does to it |
| --- | --- | --- |
| [`canvas`](https://whiskerjs.com/signals/canvas/) | yes | blocks or alters canvas readback |
| [`hardware_concurrency`](https://whiskerjs.com/signals/hardware-concurrency/) | yes | reports a fixed core count |
| [`pixel_ratio`](https://whiskerjs.com/signals/pixel-ratio/) | no | reports 1 |
| [`screen_frame`](https://whiskerjs.com/signals/screen-frame/) | no | available area equal to the screen |
| [`screen_resolution`](https://whiskerjs.com/signals/screen-resolution/) | yes | the content-area size as the screen |
| [`time_zone`](https://whiskerjs.com/signals/time-zone/) | yes | UTC (newer releases Atlantic/Reykjavik) |
| [`webgl_image`](https://whiskerjs.com/signals/webgl-image/) | yes | blocks or alters readback |
| [`webgl_renderer`](https://whiskerjs.com/signals/webgl-renderer/) | yes | placeholder strings |
| [`webgl_vendor`](https://whiskerjs.com/signals/webgl-vendor/) | yes | placeholder strings |

These report `excluded` with ruleId `firefox_rfp`, or `rfp_hardened` for Tor Browser and Mullvad Browser. With the WebGL renderer excluded, neither [`software_renderer`](https://whiskerjs.com/docs/integrity/#software_renderer) nor [`vm_renderer`](https://whiskerjs.com/docs/integrity/#vm_renderer) can fire. In the ids they count as “missing”, the same in every run, so they do not make the id unstable.

## Tor Browser and Mullvad Browser

Tor Browser and Mullvad Browser are built on Firefox and use resistFingerprinting, so they normally meet the test above. Since signalsVersion 6 they are classed `rfp_hardened`, recognised by two further measures they apply: letterboxing, which keeps the content area on steps of 200 by 100 CSS pixels, and a spoofed English locale (`navigator.languages` is `en-US` or `en-US,en`). A stock resistFingerprinting Firefox in English whose window happens to sit on those steps (its first window is rounded to them) is classed `rfp_hardened` too; the two rows exclude the same components, so only the label differs. The Tor Project describes further measures, such as shipping a limited set of fonts, all aimed at making users look the same. WhiskerJS does not try to get around any of it. In practice, many Tor Browser users of the same version will look alike, and sharing a visitorId is expected. For fraud and abuse work, the fact that a visitor uses Tor is usually better taken from your server’s own view (for example the network address) than from the fingerprint.

## Integrity flags to expect

- [`native_tampering`](https://whiskerjs.com/docs/integrity/#native_tampering) and [`prototype_lie`](https://whiskerjs.com/docs/integrity/#prototype_lie) only if add-ons override functions; resistFingerprinting itself changes values inside the browser.
- [`engine_mismatch`](https://whiskerjs.com/docs/integrity/#engine_mismatch) expects Gecko behaviour for these classes, as for Firefox.
- [`timezone_inconsistent`](https://whiskerjs.com/docs/integrity/#timezone_inconsistent) does not fire: resistFingerprinting reports UTC through both `Intl` and `Date`. [`media_query_mismatch`](https://whiskerjs.com/docs/integrity/#media_query_mismatch) does not run for these classes.
- Under automation `navigator.webdriver` is true, so the browser is classed `firefox` instead and [`automation_webdriver`](https://whiskerjs.com/docs/integrity/#automation_webdriver) fires.

## Early lab observations

There is no lab data for resistFingerprinting, Tor Browser or Mullvad Browser yet; the early, small sample at signalsVersion 1 covered Mac and iPhone Chrome and Safari only.

## Testing it yourself

In Firefox, set `privacy.resistFingerprinting` to `true` in `about:config`, maximise or resize as your setup needs, and open the [live demo](https://whiskerjs.com/demo/). If `browser_class` says `firefox_rfp` (or `rfp_hardened` in Tor Browser or Mullvad Browser), the nine components above show `excluded`. If it says `firefox`, one of the traits did not hold; that is the safe outcome. The [lab mode](https://whiskerjs.com/demo/#lab) copies one line per run.

## Support table for Firefox resistFingerprinting, Tor Browser and Mullvad Browser

Expected status of every component in this browser at signalsVersion 9, from [the support matrix](https://whiskerjs.com/browsers/support-matrix/) (updated 2026-10-26). Basis "code" means expected from the library code and documentation, not yet measured; "lab" means seen in the early lab sample.

| Component | Status | Basis | Note |
| --- | --- | --- | --- |
| [`apple_pay`](https://whiskerjs.com/signals/apple-pay/) | none | code | No ApplePaySession: reports the plain value none. |
| [`audio`](https://whiskerjs.com/signals/audio/) | ok | code | noisy / seeded_noise if the audio known-answer probe sees noise. |
| [`audio_latency`](https://whiskerjs.com/signals/audio-latency/) | varies | code | Skipped on desktop; read only with an Android user agent. |
| [`browser_class`](https://whiskerjs.com/signals/browser-class/) | ok | code | Class firefox_rfp only when every resistFingerprinting trait holds, rfp_hardened for Tor and Mullvad Browser (letterboxing and spoofed English); otherwise firefox. |
| [`canvas`](https://whiskerjs.com/signals/canvas/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`canvas_readback`](https://whiskerjs.com/signals/canvas-readback/) | varies | code | resistFingerprinting changes canvas readback, so altered is likely. |
| [`color_depth`](https://whiskerjs.com/signals/color-depth/) | ok | code |   |
| [`color_gamut`](https://whiskerjs.com/signals/color-gamut/) | ok | code |   |
| [`content_blockers`](https://whiskerjs.com/signals/content-blockers/) | ok | code | none, or the bait names a content blocker hid. |
| [`cpu_architecture`](https://whiskerjs.com/signals/cpu-architecture/) | ok | code |   |
| [`device_memory`](https://whiskerjs.com/signals/device-memory/) | unavailable | code | navigator.deviceMemory is a Chromium API. Reports unavailable / no_api. |
| [`dynamic_range`](https://whiskerjs.com/signals/dynamic-range/) | ok | code |   |
| [`font_preferences`](https://whiskerjs.com/signals/font-preferences/) | ok | code |   |
| [`fonts`](https://whiskerjs.com/signals/fonts/) | ok | code | Tor Browser limits fonts to a bundled set, so values are alike across its users. |
| [`fonts_canvas`](https://whiskerjs.com/signals/fonts-canvas/) | ok | code | Tor Browser limits fonts to a bundled set, so values are alike across its users. |
| [`forced_colors`](https://whiskerjs.com/signals/forced-colors/) | ok | code |   |
| [`hardware_concurrency`](https://whiskerjs.com/signals/hardware-concurrency/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`heap_limit`](https://whiskerjs.com/signals/heap-limit/) | unavailable | code | performance.memory is a Chromium API. Reports unavailable / no_api. |
| [`intl_locale`](https://whiskerjs.com/signals/intl-locale/) | ok | code |   |
| [`keyboard_layout`](https://whiskerjs.com/signals/keyboard-layout/) | unavailable | code | No navigator.keyboard here: unavailable / no_api. |
| [`languages`](https://whiskerjs.com/signals/languages/) | ok | code | resistFingerprinting can normalise the reported language. |
| [`math`](https://whiskerjs.com/signals/math/) | ok | code |   |
| [`media_codecs`](https://whiskerjs.com/signals/media-codecs/) | ok | code |   |
| [`noise_profile`](https://whiskerjs.com/signals/noise-profile/) | ok | code | Probe states other than exact are likely, since resistFingerprinting changes readback. |
| [`pdf_viewer`](https://whiskerjs.com/signals/pdf-viewer/) | ok | code |   |
| [`permissions`](https://whiskerjs.com/signals/permissions/) | ok | code |   |
| [`pixel_ratio`](https://whiskerjs.com/signals/pixel-ratio/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`platform`](https://whiskerjs.com/signals/platform/) | ok | code |   |
| [`plugins`](https://whiskerjs.com/signals/plugins/) | ok | code |   |
| [`prefers_contrast`](https://whiskerjs.com/signals/prefers-contrast/) | ok | code |   |
| [`private_click`](https://whiskerjs.com/signals/private-click/) | none | code | No attributionSourceId on anchors: reports the plain value none. |
| [`reduced_motion`](https://whiskerjs.com/signals/reduced-motion/) | ok | code |   |
| [`screen_frame`](https://whiskerjs.com/signals/screen-frame/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`screen_resolution`](https://whiskerjs.com/signals/screen-resolution/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`speech_voices`](https://whiskerjs.com/signals/speech-voices/) | varies | code | Digest of the voice list, or none when the list is empty. |
| [`storage`](https://whiskerjs.com/signals/storage/) | ok | code |   |
| [`time_zone`](https://whiskerjs.com/signals/time-zone/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`touch_support`](https://whiskerjs.com/signals/touch-support/) | ok | code |   |
| [`ua_client_hints`](https://whiskerjs.com/signals/ua-client-hints/) | unavailable | code | navigator.userAgentData (Client Hints) is a Chromium API. Reports unavailable / no_api. |
| [`ua_versions`](https://whiskerjs.com/signals/ua-versions/) | unavailable | code | navigator.userAgentData (Client Hints) is a Chromium API. Reports unavailable / no_api. |
| [`vendor`](https://whiskerjs.com/signals/vendor/) | ok | code |   |
| [`vendor_flavors`](https://whiskerjs.com/signals/vendor-flavors/) | ok | code |   |
| [`webgl_image`](https://whiskerjs.com/signals/webgl-image/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`webgl_params`](https://whiskerjs.com/signals/webgl-params/) | ok | code | Where WebGL 1 is available; unavailable / no_context without it. |
| [`webgl_renderer`](https://whiskerjs.com/signals/webgl-renderer/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). Neither renderer flag can fire. |
| [`webgl_vendor`](https://whiskerjs.com/signals/webgl-vendor/) | excluded | code | Exclusion table: firefox_rfp, or rfp_hardened in Tor and Mullvad Browser (fixed or normalised values). |
| [`webgpu`](https://whiskerjs.com/signals/webgpu/) | varies | code | Depends on browser version, operating system and GPU; needs a secure context. unavailable / no_api without navigator.gpu, no_context without an adapter. |

## Questions and answers

### How does WhiskerJS know resistFingerprinting is on?

It does not read the setting. It classes Firefox as firefox_rfp when every trait the setting produces holds at once: an RFP time zone (UTC, Etc/UTC or Atlantic/Reykjavik), a screen equal to the content area, no taskbar space, a pixel ratio of 1, a window at 0,0, and navigator.webdriver not true.

### How are Tor Browser and Mullvad Browser told apart from plain resistFingerprinting?

They run resistFingerprinting with letterboxing and a spoofed English locale. When every resistFingerprinting trait holds and, on top, the content area sits on the letterboxing steps (width a multiple of 200, height a multiple of 100 CSS pixels) and navigator.languages is en-US or en-US,en, the class is rfp_hardened (since signalsVersion 6). Both classes exclude the same nine components.

### Will Tor Browser users get the same visitorId?

Many may. Tor Browser is designed to make its users look alike, and WhiskerJS respects that: the normalised components are left out, and the rest tend to be similar across users of the same Tor Browser version. Treat a shared id here as expected.

### Can a stock Firefox be wrongly classed firefox_rfp?

The test is built to avoid it, and since signalsVersion 5 it never applies when navigator.webdriver is true. A wrong class would drop nine components; a missed one only leaves normalised values in, so the test errs towards plain firefox.

## Related pages

- [Firefox](https://whiskerjs.com/browsers/firefox/): Stock Firefox, with nothing excluded.
- [resistFingerprinting](https://whiskerjs.com/glossary/#resistfingerprinting): The setting in a few sentences.
- [The exclusion table](https://whiskerjs.com/docs/how-it-works/#the-exclusion-table): Every row and its ruleId.
- [What's new](https://whiskerjs.com/whats-new/): The signalsVersion 5 false-positive fix.
- [Brave](https://whiskerjs.com/browsers/brave/): Another browser with exclusions.
