Skip to main content
A switch is a command-line argument to the browser. The Python and Node packages pass the seed, persona, locale and timezone switches from their options. Pass any other switch through the args option, or on the command line after apostate run --. A page cannot read the command line. When the browser refuses a value, it prints apostate: <reason> on stderr and exits with status 1. A value that is logged and ignored instead is marked as such below.

All switches

The Python and Node packages refuse a switch that starts with --fingerprint and is not in this table, and a switch within two edits of one in it, such as --fingeprint-platform. The browser itself ignores an unknown switch, as Chromium does.

Seed and persona

--fingerprint

The seed for the whole machine. The same seed and persona give the same machine on every launch and every host. Seeds and identity explains how long a machine lasts.
  • Value. 1 to 512 bytes of printable ASCII without spaces (bytes 0x21 to 0x7E). The Python and Node packages also take a non-negative integer, and accept a string only when it matches [A-Za-z0-9][A-Za-z0-9._:-]*.
  • Host mode. host presents the real machine and composes nothing. off, false, 0, disable and disabled mean the same, in any letter case.
  • Default. With --user-data-dir=DIR, the seed stored in DIR/apostate/identity, which the first launch writes. Without it, a fresh seed from OS entropy for this launch only.
  • Refused. An empty value, more than 512 bytes, or a space or other byte outside printable ASCII:
  • Refused with host mode. --fingerprint-platform, --fingerprint-anchor and every per-field switch, because nothing is composed for them to change:
    --fingerprint-noise, --fingerprint-webrtc-ip, --fingerprint-webrtc-udp and --fingerprint-explain are accepted with host mode. Noise does nothing there.

--fingerprint-platform

The operating system the machine presents: windows, macos or linux. The persona decides the User-Agent, Client Hints, GPU family, fonts, voices, screen layout and system colours. Personas lists everything it covers.
  • Default. macos on a macOS host, windows on a Windows host and windows on a Linux host.
  • Other values. Logged and ignored, and the host’s default persona applies. The Python and Node packages refuse any value except the three names and a few aliases, in any letter case. Both take win, win32, darwin, mac and osx. Python also takes linux2, and Node also takes mac os, mac os x and os x.

--fingerprint-anchor

Pins the GPU family instead of letting the seed draw one. The value is a family id from GPU models:
  • A persona draws only from the families of its own platform, and never draws SwiftShader. Only this switch selects SwiftShader.
  • A family from another platform is accepted. --fingerprint-explain lists the mismatch under limitations.
  • An id that is not in the table is logged, and the launch presents the host’s values on every surface:

--fingerprint-explain

Prints the composed machine to stdout and exits with status 0 without opening a window. The report goes nowhere else. Run it with the same switches as the launch you want to check:
The header names the persona, the host and the seed. seed source says where the seed came from, and reproduce with is the switch that gives the same machine anywhere. Each row is one choice: layer is what decided it, evidence is where the value comes from, and a line starting with ! explains the row. The report ends with a limitations list of what this launch cannot hide or apply. With a persistent user data directory the report also names the identity file:
In host mode the report says that nothing is composed:
With an --apostate-profile that describes a device, --fingerprint-explain prints nothing in 0.4.3 and the browser starts as normal.

Per-field switches

Each switch sets one value. The seed still draws everything else, so the machine stays consistent around the value you set. GPU strings. Either switch narrows the seed’s choice to the matching models of the family the launch uses, so the WebGL and WebGPU values still belong to that GPU. A string from another family is refused, and the error lists the models the family has:
To use a model from another family, add --fingerprint-anchor with that family’s id. GPU models lists every vendor and renderer string. Cores and memory. A persona never claims more than the host has. Asking for more is refused:
A value outside the range is refused with apostate: --fingerprint-device-memory='0' is not a whole number between 1 and 4096. Chromium rounds the memory a page sees in navigator.deviceMemory to 2, 4, 8, 16 or 32. Screen. Width and height are independent. With only one of them set, the seed’s value stays for the other. The available area (availWidth, availHeight) is worked out from the size and the persona’s taskbar or menu bar. The screen is refused when it is smaller than --window-size or too small for the taskbar:
--fingerprint-explain marks each overridden value and names the value the seed had drawn.

Locale and timezone

The seed never draws the locale or the timezone. The Python and Node packages fill both switches from a GeoIP lookup of the connection’s exit unless you pass locale and timezone. Locale and timezone covers the lookup and what each value changes.

--fingerprint-locale

  • One tag, such as de-DE, sets the browser’s UI locale. Intl, date and number formats and navigator.language follow it. navigator.languages and the Accept-Language header carry Chrome’s own default list for that locale, such as de-DE,de,en-US,en for de-DE.
  • A comma list, such as de-DE,de, sets navigator.languages and Accept-Language exactly. Its first tag is the UI locale.
  • Default. en-US, never the host’s language. Host mode keeps the host’s.
  • The browser writes the UI locale into LANGUAGE, LC_ALL, LC_MESSAGES and LANG before it starts any other process. --lang has no effect on a persona.
  • The speech voices follow the first tag. A Windows persona lists its en-GB voice set for en-GB and its en-US set for every other tag.

--fingerprint-timezone

An IANA timezone name, such as Europe/Berlin. Intl, Date and the timezone a page reads follow it.
  • Default. The host’s timezone. Behind a proxy that is the host’s timezone and not the exit’s.
  • An unknown name is logged as apostate: unknown timezone 'Mars/Olympus'; keeping the host zone, and the host’s timezone stays.
  • The Python and Node packages also write the value into TZ. From 0.4.4 they refuse a name that is not an IANA zone before launch, with the message in Errors. In 0.4.3 an unknown name went into TZ, and a page’s Intl timezone read undefined.

WebRTC

WebRTC explains what these do to calls and how to check for leaks.

--fingerprint-webrtc-ip

An IP address written into the host and server-reflexive ICE candidates in place of the real address. The browser does not check the value. Packets still leave from the real socket, so a peer can read the real source address off the packets it receives. Behind a proxy, the Node package passes the exit address from its GeoIP lookup as this switch. The Python package does not.

--fingerprint-webrtc-udp

Where WebRTC’s UDP traffic goes. Any other value is logged and treated as block.

Readback noise

--fingerprint-noise

Changes canvas and WebGL pixel readback by at most one step per colour channel, on pixels at colour edges, so two personas on one host give different canvas hashes. Solid areas and alpha stay as rendered. The change depends only on the profile and the pixels, so the same seed reads back the same bytes every time, and getImageData, toDataURL, toBlob, convertToBlob and WebGL readPixels agree with each other.
  • Default. Off. It does nothing in host mode.
  • Value. None. The browser checks only whether the switch is present, so --fingerprint-noise=false turns noise on. The Python and Node packages refuse false, 0, no, off, disable and disabled as its value.
  • A page can detect the change, for example by drawing one image at two scales and comparing them. Leave it off unless canvas hashes must differ between personas on one host.

A profile you wrote

--apostate-profile

Runs a profile you wrote instead of composing one. The value is the profile JSON, base64-encoded. Profile schema lists every field, and Custom profiles shows how to write one.
  • Only the sections the profile includes change. Every other value is the host’s.
  • --fingerprint, --fingerprint-platform, --fingerprint-anchor and the per-field switches have no effect, and the browser logs which ones it ignored. The Python and Node packages refuse a profile together with --fingerprint in args.
  • A value that does not decode is refused:
The browser also uses this switch itself. It passes the composed profile to every child process in it, and the packages use it to carry proxy credentials. A value that carries only credentials does not stop composition.

Precedence

Strongest first:
  1. --apostate-profile describing a device: your profile, nothing composed.
  2. --fingerprint=host: the real machine, nothing composed.
  3. Per-field switches: one value each, on top of the seed’s machine.
  4. --fingerprint=SEED.
  5. The seed in DIR/apostate/identity, when --user-data-dir=DIR is set.
  6. A fresh seed from OS entropy.
--fingerprint-platform and --fingerprint-anchor choose the persona and GPU family that the seed composes, at levels 3 to 6. Host mode refuses them and every per-field switch rather than ranking above them.

Chromium switches that matter

Launching the browser with --enable-automation, --headless, --remote-debugging-pipe or --remote-debugging-port leaves navigator.webdriver false.

The proxy credential

Stock Chromium rejects a credential in --proxy-server. Apostate takes it off the switch before Chromium parses it, holds it in memory for the launch, and answers the proxy’s authentication with it. It does not appear in NetLog, error messages, chrome://version or --fingerprint-explain. Child processes receive it inside their --apostate-profile value, so ps on the host shows it base64-encoded.
  • Schemes that take a credential are http, https, socks4 and socks5, and a bare host:port, which means HTTP. Use socks5://, which already resolves names on the proxy. socks5h:// is refused with a credential.
  • Percent-encode /, % and spaces in the credential (%2F, %25, %20).
  • Refused: a credential on direct://, quic:// or socks5h://, a malformed percent escape, a NUL byte, invalid UTF-8, a username or password over 4096 bytes, two proxies with different credentials, and a credential in both the URL and --apostate-profile. For example:
Proxies covers the packages’ proxy option and the GeoIP lookup through the proxy.