Skip to content

Fingerprint flags

Identity is controlled with Chromium command-line switches. Pass them as args to your launcher.

The seed model

Everything keys off --fingerprint=<seed>. The seed (an integer or any string) deterministically derives canvas, WebGL, audio and font noise (text metrics and client rects are reported truthfully — on Chrome's native 1/512-px grid — rather than perturbed):

  • Same seed ⇒ same identity across launches — a returning visitor.
  • New seed ⇒ a fresh, plausible identity.
  • Signals are derived per site (inspired by Brave's farbling), so the same seed is decorrelated across different domains rather than globally linkable.

Prefer a real machine's identity over the synthetic seed-derived one? Capture a donor Chrome with the collector and import it via --fingerprint-profile=<gzip+base64 JSON> (or the SDK's fingerprint_profile / fingerprintProfile option, which does the gzip+base64 for you). Fields present in the profile override the seed-derived persona; absent fields fall back to the --fingerprint seed, so partial profiles stay coherent. See the Playwright guide for how to import a captured profile from the SDK.

Coherent secondary surfaces

Beyond the noised signals, the engine keeps the persona's secondary surfaces agreeing with a real Chrome-on-Windows desktop, so nothing contradicts the seed under deeper inspection:

  • WebGL limits (WebGL1 + WebGL2) report the canonical ANGLE/D3D11 caps a real Windows GPU returns, not the host's software-renderer values.
  • UNMASKED_RENDERER / UNMASKED_VENDOR are session-constant — one GPU on every site, tracking the persona, instead of a per-origin tell.
  • navigator.getBattery() reports a coherent desktop (charging, level 1.0, no discharge), and navigator.connection a residential profile (effectiveType 4g, rounded rtt/downlink, saveData off).
  • navigator.keyboard.getLayoutMap() returns a clean US-QWERTY map, and AudioContext sampleRate / baseLatency / outputLatency match Windows WASAPI.
  • window.getScreenDetails() reports a single coherent monitor; @media (pointer: fine) / (hover: hover) match a desktop with a mouse.
  • new URL("C:/").protocol returns file: (Windows-coherent), and navigator.share / canShare are exposed to match the Windows UA.
  • WebGPUnavigator.gpu adapter info + limits/features track the same GPU as WebGL (no host discrete-GPU leak under a seed-only persona).
  • Locale & speechIntl (main thread + workers) resolves to the same language as navigator.language (no en-GB leak), and speechSynthesis serves the persona's voice set.
  • Codecs & devicesMediaCapabilities.decodingInfo() reports a persona codec matrix, and enumerateDevices() a persona media set (seed-stable ids, empty labels pre-permission).
  • UA-CH high-entropybitness=64 / wow64=false / model, plus navigator.storage.estimate() reporting a realistic on-disk quota.

Current pre-release caveat: Accept-Language, navigator.language, and the Intl locale are pinned coherently, but the full navigator.languages array is not fully implemented yet; only the primary language tag is exposed there today.

Experimental: real-GPU canvas bridge

Optionally forward canvas / WebGL ops to a remote real-GPU host so readbacks (getImageData / toDataURL / readPixels / measureText) are coherent with the GPU you present — even on hardware that can't render it locally. Because it forwards the operations (not pre-recorded images) it handles arbitrary canvases. Enable with --canvas-bridge-url=ws://host:port (plus --no-sandbox); unset means fully local, exactly as before. The render server is a headless Clearcote on the real-GPU host. See the canvas-bridge guide for the full setup.

Native metadata overrides & light_stealth

Alongside the seed-derived persona, a set of native single-value overrides let you spoof individual navigator/screen values directlyhardwareConcurrency, deviceMemory, colorDepth, devicePixelRatio, maxTouchPoints, and (opt-in) the screen/avail* dimensions. Each is read straight by its getter with the precedence flag > --fingerprint persona > real host, so a hardcoded value wins over any seed and the overrides work with or without a --fingerprint seed — they never engage the persona machinery.

The SDK bundles the safe subset behind one flag, light_stealth: it applies a coherent, seed-derived set of just the metadata axes that survive the strictest bot-detection checks (hardwareConcurrency, deviceMemory, colorDepth, devicePixelRatio, maxTouchPoints) via those native switches only. Rendering surfaces (canvas / WebGL / audio / fonts), the TLS ClientHello, and the real browser version are all left untouched, so nothing contradicts the host. screen dimensions are deliberately not spoofed by default (opt-in via screenWidth etc.), because a faked screen that can't be reconciled with the real render surface is a reliable block trigger. It is the lightest identity variation that still passes strict detection; an explicit option always wins over the preset.

javascript
import { launch } from "clearcote";
// one coherent metadata identity, rendering/TLS/version left real:
const browser = await launch({ lightStealth: true, fingerprint: "my-seed" });

// or set individual values by hand (no --fingerprint needed):
const b2 = await launch({
  hardwareConcurrency: 8,
  deviceMemory: 8,
  devicePixelRatio: 1.25,
  maxTouchPoints: 0,
});

All switches

Filter by name or effect, or narrow to one subsystem. Click a switch to read what it does and see its shortest valid form.

Identity & persona

Master seed (int or string). Drives canvas, WebGL, audio, fonts and client-rect noise. Same seed ⇒ same identity.

Hardware & screen
GPU & WebGPU
Network & locale
Noise
Canvas bridge

Examples

These are raw command-line flag sets. For complete Python and Node workflows, see Examples.

A full, coherent Windows identity:

bash
--fingerprint=acme-tenant-7 \
--fingerprint-platform=windows \
--fingerprint-brand=Chrome \
--timezone=America/New_York \
--fingerprint-hardware-concurrency=8

A macOS identity with custom GPU strings:

bash
--fingerprint=42 \
--fingerprint-platform=macos \
--fingerprint-gpu-vendor="Apple" \
--fingerprint-gpu-renderer="Apple M2"

Pin a location together with its timezone so geolocation and clock agree:

bash
--fingerprint=nyc-1 \
--timezone=America/New_York \
--fingerprint-location=40.7128,-74.0060

Import a real machine's values

Instead of a synthetic seed, you can report the exact values a real Chrome reported — GPU strings + getParameter table, screen, fonts, voices, audio. The values are substituted; the rendering is still done by your own machine, so a profile does not give two accounts different canvases — a per-account --fingerprint seed does. Grab one from the curated clearcote-profiles library (thousands of real-machine profiles, GPU-vendor tagged), or capture your own with the collector, then load it and prove it loaded:

javascript
import { launch } from "clearcote";
// path to a captured .json profile, a profile object, or a JSON string
const browser = await launch({ fingerprintProfile: "./real-machine.json" });
// fields present in the profile override the seed-derived persona;
// absent fields fall back to --fingerprint, so partial profiles stay coherent.

Capture flow: open the collector page in a real Chrome you want to clone, export the JSON, then pass it via fingerprintProfile (SDK) / --fingerprint-profile (engine). Verify with tools/fingerprint-collect/verify_profile.py.

What each patch does

Every switch above maps to a readable engine patch. The full map — all 33 diffs and what each one changes — is in docs/PATCHES.md, and the diffs themselves live in patches/.

Coherence matters more than any single value: keep platform, timezone, locale and GPU plausible together. See Architecture for how the engine keeps them consistent, and How detection works for why this beats JavaScript spoofing.