Every antidetect browser makes the same promise — a different, believable fingerprint for every profile. The interesting question is where that fingerprint gets manufactured. There are three places you can do it, and they are an order of magnitude apart in how hard they are to catch.
Three tiers of masking
You can intervene at one of three layers, from cheapest and most detectable to hardest and most robust:
- Tier A — JavaScript injection. A stock Chromium plus
addInitScriptthat overridesnavigator,canvas,WebGLgetters after the page context exists. It is the cheapest option, and the override action itself leaves marks. - Tier B — someone else's patched kernel. Ship a pre-forked engine. Medium cost, but your versions, your dimensions and your release cadence are all hostage to an upstream you don't control.
- Tier C — fork and compile Chromium yourself. Maintain your own patch series against the C++ source and compile your own builds. Highest resistance, fully controllable, no third-party black-box tells — at the cost of a permanent Chromium version-tracking tax.
MaskOle is Tier C. Every masked value — canvas, WebGL, audio, fonts, timezone, navigator — is produced inside Chromium's own C++, not sprinkled on top in JavaScript.
What a JavaScript patch leaves behind
When you overwrite a browser API from JavaScript, the overwrite is observable from the same JavaScript environment a detection script runs in. The classic tells:
Function.prototype.toStringon a patched method no longer returns"[native code]".- A
Proxyused to trap property access can be sniffed through timing andtrapinvariants. - Prototype chains and property enumeration order shift when you redefine getters.
- Error stacks captured inside a spoofed call reveal the injected wrapper frames.
None of these are exotic. Open-source fingerprinting suites like CreepJScheck for exactly this kind of tampering. A patch that lives in the page can always, in principle, be caught from the page.
Masking below JavaScript
MaskOle answers the fingerprint call before any page script runs. When an anti-bot script asks for a canvas hash or a WebGL renderer string, the request is served by the engine — in C++ — and returns a single, coherent, real-device value. There is no wrapper on the prototype, no toString mismatch, no trap to detect. The property order stays native because nothing in the JavaScript environment was touched.
Concretely, every dimension follows the same two-step pattern inside the fork:
- A one-time framework patch registers a family of switches and parses a per-profile fingerprint config (
--fp-config=<path.json>) into a globally readable object at process start. - Each fingerprint surface — canvas readback, WebGL
getParameter, the audio render path, font enumeration, the timezone controller — reads that config and returns the masked value (or applies deterministic noise to the real one).
A profile is just a JSON file describing a whole device. One process, one identity. Profiles never share state, and the mask is chosen before the first byte of page JavaScript executes.
Two rules the engine enforces
Determinism
The same config, launched a hundred times across machines, must produce a byte-identical fingerprint. All noise is a pure function of a seed, never true randomness. If a profile's fingerprint drifted between launches, that drift would itself be the anomaly. (The next post is entirely about why this rule matters more than it looks.)
Coherence over coverage
We would rather mask one dimension fewer than let two dimensions contradict each other. A user agent that says Windows while navigator.platform says Mac, or a WebGL renderer that reports an Apple GPU on a "Windows" profile, is what detectors actually hunt for. UA, UA-CH, platform, WebGL vendor, timezone and language all come from one persona.
The part that doesn't change: your tooling
Going Tier C doesn't cost you your automation stack. The kernel still exposes a standard CDP endpoint, so Playwright, Puppeteer and Selenium connect over connectOverCDP with zero new concepts. And when no config is passed, the build behaves exactly like upstream Chromium — which makes it trivial to prove a given signal came from our patch and not from Chromium itself.
Engine-level masking is more expensive to build and carries a real maintenance tax. We think that is exactly why it's worth doing: the ceiling on how undetectable you can be is set by the layer you mask at, and JavaScript is the wrong layer.