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:/").protocolreturnsfile:(Windows-coherent), and navigator.share / canShare are exposed to match the Windows UA.- WebGPU —
navigator.gpuadapter info + limits/features track the same GPU as WebGL (no host discrete-GPU leak under a seed-only persona). - Locale & speech —
Intl(main thread + workers) resolves to the same language asnavigator.language(noen-GBleak), andspeechSynthesisserves the persona's voice set. - Codecs & devices —
MediaCapabilities.decodingInfo()reports a persona codec matrix, andenumerateDevices()a persona media set (seed-stable ids, empty labels pre-permission). - UA-CH high-entropy —
bitness=64 /wow64=false /model, plusnavigator.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 directly — hardwareConcurrency, 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.
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
| Flag | Value | Description |
|---|---|---|
| --fingerprint | <seed> | Master seed (int or string). Drives canvas, WebGL, audio, fonts and client-rect noise. Same seed ⇒ same identity. |
| --fingerprint-platform | windows|linux|macos|android | Spoof the operating system reported to the page (navigator.platform + UA-CH). android is a best-effort mobile persona (seed-selected Pixel/Galaxy device: Android UA, Sec-CH-UA-Mobile ?1, touch/maxTouchPoints, pointer:coarse/hover:none, mobile screen+DPR, Mali/Adreno WebGL, phone viewport). On a desktop engine the GPU render + fine geometry stay desktop — pair with the canvas bridge for render coherence. SDK: platform. |
| --fingerprint-platform-version | <v> | Platform version reported via User-Agent Client Hints. |
| --fingerprint-brand | Chrome|Edge|Opera|Vivaldi | Browser brand presented in the UA and UA-CH brand list. |
| --fingerprint-brand-version | <v> | Brand version string. |
| --fingerprint-tls-profile | match-persona|native|chrome-<major> | Keep the TLS ClientHello coherent with the persona's claimed Chrome version — the network layer follows the UA instead of emitting the build's native TLS. Only version-variant fields change (post-quantum key-share group, ALPS codepoint); the cipher list, version bounds and per-connection extension permutation stay exactly real-Chrome. Chromium-core (the brand lives in headers/UA-CH, not the ClientHello). Default match-persona follows --fingerprint-brand-version's major; native/off leaves native TLS; chrome-<major> (or a bare number) pins a specific major. SDK: tlsProfile. |
| --fingerprint-gpu-vendor | <string> | Override the WebGL UNMASKED_VENDOR string. |
| --fingerprint-gpu-renderer | <string> | Override the WebGL UNMASKED_RENDERER string. |
| --fingerprint-hardware-concurrency | <n> | Value reported by navigator.hardwareConcurrency. Native single-value override: read directly by the getter (flag > --fingerprint persona > real host), so it works with or without a --fingerprint seed. SDK: hardwareConcurrency. |
| --fingerprint-device-memory | <gb> | navigator.deviceMemory in GB (spec-clamped to 8 — larger values report as 8). Native single-value override (flag > persona > real). SDK: deviceMemory. |
| --fingerprint-color-depth | <n> | screen.colorDepth (e.g. 24). Native single-value override (flag > persona > real). SDK: colorDepth. |
| --fingerprint-device-pixel-ratio | <r> | window.devicePixelRatio (e.g. 1, 1.25, 1.5). Native single-value override (flag > persona > real). SDK: devicePixelRatio. |
| --fingerprint-max-touch-points | <n> | navigator.maxTouchPoints (0 on a mouse-only desktop). Native single-value override (flag > persona > real). SDK: maxTouchPoints. |
| --fingerprint-screen-width | <px> | screen.width in CSS px. Native single-value override (flag > persona > real). Note: spoofing screen dimensions is opt-in and NOT part of the light_stealth preset — a faked screen size that can't be reconciled with the real window/render surface is a reliable block trigger on the strictest anti-bots. Best when the host's real display matches. SDK: screenWidth. Pair with --fingerprint-screen-height / --fingerprint-avail-width / --fingerprint-avail-height (SDK: screenHeight/availWidth/availHeight). The window's outerWidth/Height + screenX/Y clamp to a spoofed avail* so geometry stays coherent. |
| --fingerprint-location | <lat,lng> | Geolocation coordinates returned to the page. |
| --timezone | <IANA> | IANA timezone, e.g. America/New_York. Sets the JS timezone coherently. |
| --accept-lang | <langs> | Accept-Language, e.g. en-US,en. Sets the server-side Accept-Language header plus the primary navigator language coherently (native Chromium switch; no ;q= weights). |
| --webrtc-ip | <ip> | WebRTC reports this IP (your proxy egress). The engine fabricates the server-reflexive candidate at it and sends no real STUN, so the real IP never leaks — not a relabel. |
| --fingerprinting-canvas-image-data-noise | — | Enable canvas image-data noise. |
| --fingerprint-profile | <gzip+base64 JSON> | Import a profile captured from a real Chrome (or the open dataset) as gzip+base64-encoded JSON — the SDK encodes it for you. Profile fields override the seed-derived persona; absent fields fall back to the --fingerprint seed. |
| --disable-gpu-fingerprint | — | Disable the GPU/WebGL fingerprint spoof for this session. This is a bundle: real GPU strings + the real parameter table + the readPixels farble stood down + persona field suppression. To move only the strings, use --disable-gpu-string-spoof. SDK: disableGpuFingerprint. |
| --disable-fingerprint-noise | — | Turn OFF the per-site canvas/WebGL/audio farbling noise (the identity spoof stays on) — for sites whose ML scores the noise itself as tampering. This is also a bundle: canvas 2D and WebGL and the rest, all at once. To un-noise canvas 2D alone, use --disable-canvas-noise. SDK: fingerprintNoise: false. |
| --disable-gpu-string-spoof | — | r12+. Report the host's REAL WebGL UNMASKED_VENDOR/RENDERER while leaving everything else --disable-gpu-fingerprint would touch alone — the parameter table, the extension list, the WebGL1 limits and the readPixels farble all stay as they were. For hosts where a claimed GPU cannot survive being rendered against (a container, or a cloud VM rasterising through WARP/SwiftShader), it is more honest to show the real string than to claim silicon the machine does not have. Caveat: navigator.gpu still follows the wide flag, so with this switch alone WebGPU keeps describing the persona GPU while WebGL reports the real one — readable in a single page. SDK: gpuStringSpoof: false (0.25.0+). |
| --disable-canvas-noise | — | r12+. Turn off the canvas 2D farble (getImageData and a 2D canvas's toDataURL) while leaving the WebGL readPixels farble on. For sites that score canvas noise itself as tampering but still expect a rendering surface that varies. Does not cover toBlob(), which farbles under --fingerprint alone and is gated by neither noise switch. SDK: canvasNoise: false (0.25.0+). |
| --fingerprint-storage-quota | <MB> | navigator.storage.estimate().quota in megabytes — set a realistic on-disk size so the machine doesn't read as a tiny test/incognito profile. SDK: storageQuota. |
| --canvas-bridge-url | ws://host:port | Experimental: forward canvas/WebGL ops to a remote real-GPU host so readbacks match the GPU you present. Requires --no-sandbox; unset ⇒ fully local. See the canvas-bridge guide. |
| --canvas-bridge-auth | user:secret | Experimental: HTTP Basic credentials for the canvas-bridge endpoint. |
| --canvas-bridge-mode | off|all|allow|deny | Experimental: per-origin canvas-bridge policy. Use allow/deny with the eTLD+1 lists below to bridge only the sites where canvas coherence matters. |
| --canvas-bridge-allow | a.com,b.com | Experimental: eTLD+1 allow-list used when --canvas-bridge-mode=allow. |
| --canvas-bridge-deny | a.com,b.com | Experimental: eTLD+1 deny-list used when --canvas-bridge-mode=deny. |
| --canvas-bridge-fallback | block|local | Experimental: cold cache-miss behavior. block waits for the bridge; local serves the fast local render instead of stalling. |
| --fingerprint-webgpu-vendor | <string> | Advanced: override WebGPU GPUAdapterInfo vendor. Normally derived from the WebGL persona or imported profile. |
| --fingerprint-webgpu-architecture | <string> | Advanced: override WebGPU GPUAdapterInfo architecture. Normally derived from the WebGL persona or imported profile. |
| --fingerprint-webgpu-device | <string> | Advanced: override WebGPU GPUAdapterInfo device. |
| --fingerprint-webgpu-description | <string> | Advanced: override WebGPU GPUAdapterInfo description. |
| --fingerprinting-canvas-measuretext-noise | — | Low-level canvas text-metric noise switch retained by the engine; most users should prefer --fingerprint or fingerprint profiles. |
| --fingerprinting-client-rects-noise | — | Low-level client-rect noise switch retained by the engine; most users should prefer --fingerprint or fingerprint profiles. |
Examples
These are raw command-line flag sets. For complete Python and Node workflows, see Examples.
A full, coherent Windows identity:
--fingerprint=acme-tenant-7 \
--fingerprint-platform=windows \
--fingerprint-brand=Chrome \
--timezone=America/New_York \
--fingerprint-hardware-concurrency=8A macOS identity with custom GPU strings:
--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:
--fingerprint=nyc-1 \
--timezone=America/New_York \
--fingerprint-location=40.7128,-74.0060Import a real machine
Instead of a synthetic seed, you can adopt the exact identity of a real Chrome — GPU + getParameter table, screen, fonts, voices, audio. 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:
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.
Related reading