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
Filter by name or effect, or narrow to one subsystem. Click a switch to read what it does and see its shortest valid form.
Master seed (int or string). Drives canvas, WebGL, audio, fonts and client-rect noise. Same seed ⇒ same identity.
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'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:
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