Fingerprint spoofing
Each session is assigned a device profile taken from a real device and spoofs more than 50 individual signals to match it. The values are set inside Chromium’s own code, and they agree with one another. Both parts decide whether a page loads.
Consistency is what gets checked
Section titled “Consistency is what gets checked”Setting a valid User-Agent, patching canvas and WebGL, and stopping there is enough to be caught. Detection systems cross-reference signals against each other and look for a profile that describes one real machine.
A Windows User-Agent with macOS fonts is flagged. A phone screen resolution
beside a desktop GPU is flagged. A Europe/Berlin timezone with an en-US locale
and a US exit IP is flagged. Each value on its own is plausible; together they
are not.
Patched in the engine, not injected into the page
Section titled “Patched in the engine, not injected into the page”Most stealth tools run JavaScript in the page before the site’s own scripts, to
overwrite navigator properties and wrap canvas calls. A page can find that
work: an overwritten getter, a changed prototype or a function whose
toString() is not native code are all visible from JavaScript, and detection
scripts look for them.
rayobrowse changes Chromium’s C++ source instead. Blink, V8, the network stack, WebRTC and the GPU process are patched, so a spoofed value comes back from the same native code path a real browser uses. There is no injected script for a page to find.
- One profile, every process. The profile is loaded once and handed to the renderer, web workers and the GPU process. The main page, iframes, workers and WebGL all describe the same device, so a worker never reports different hardware from the page that started it.
- Canvas is real device output, not noise. A canvas read
(
toDataURL,getImageData,toBlob) returns what a real device with this profile renders for that drawing. The usual alternative is random noise, and noise is detectable: draw the same canvas twice and the pixels differ, which a real GPU never does. - Fonts belong to the claimed OS. The session gets that OS’s font set and
never falls back to fonts from the machine it runs on, so a request for Arial
never renders in a substitute. Font lists,
document.fonts.check()and the default serif, sans-serif and monospace fonts all match, including for CJK locales. - Text measures like the claimed OS. Glyph widths and positioning follow the claimed OS’s text rendering, so width-based font probes return the numbers a real machine of that type returns.
- WebGL down to the parameter table. The renderer and vendor strings, the
full
getParameter()set, the extension list and shader precision are set in the engine and describe one GPU. Extensions that GPU lacks report unsupported. - Timezone, locale and geolocation follow the proxy. At launch the browser
looks up its own exit IP through the proxy and sets all three from it. Change
the proxy’s country and
Intl.DateTimeFormat().resolvedOptions().timeZonechanges with it. - WebRTC reports the proxy’s exit IP, never the address of the machine the browser runs on.
- An attached CDP client leaves no trace.
navigator.webdriveris false. The side effects Playwright and Puppeteer normally leave in the page when they attach, such as automation markers in the page and side effects of the debugger connection, are removed.
The patch set tracks upstream Chromium and is carried forward to each new release, so the browser’s real behavior matches the version its user agent claims.
What is spoofed
Section titled “What is spoofed”Each row covers several individual signals.
| Category | Covered |
|---|---|
| User-Agent and client hints | Full UA string, Sec-CH-UA headers, userAgentData, platform and architecture hints |
| OS and platform | navigator.platform, oscpu, platform-specific behavior differences |
| Screen and display | Resolution, color depth, device pixel ratio, available area, media queries |
| WebGL | Renderer, vendor, extensions, shader precision, the getParameter() table |
| WebGPU | Adapter info and limits |
| Canvas | Output a real device renders for the same drawing, not random noise |
| Fonts and text | Font list, font availability checks, default font mappings and text measurement, all for the claimed OS |
| WebRTC | Reports the proxy’s exit IP, media device enumeration |
| Timezone, locale, geolocation | Set from the proxy’s exit IP, Intl and Date consistency |
| CPU and memory | hardwareConcurrency, deviceMemory from realistic hardware profiles |
| Audio | AudioContext output, sample rate, channel configuration |
| Speech | The speechSynthesis voice list for the claimed OS |
| Media capabilities | Codec and decoding support answers, DRM behavior |
| CSS system colors | The claimed OS’s default colors |
| Language | navigator.language, languages, Accept-Language |
| Media devices | Camera and microphone enumeration |
| Battery | Battery API state consistent with the device type |
| Network | navigator.connection, downlink, effective type |
| Permissions | Consistent permission states and feature policy responses |
| Plugins and MIME types | Chromium-accurate plugin list and MIME support, doNotTrack |
| Visibility and focus | With force_visibility=true, a background tab still reports visible and focused |
| Automation signals | No navigator.webdriver, no Playwright or Puppeteer artifacts, no CDP traces |
How a profile is chosen
Section titled “How a profile is chosen”Every session draws a complete profile from a database of fingerprints collected
from real devices. os, browser_version_min, browser_version_max,
screen_width_min and screen_height_min narrow the draw, and everything else
comes from the one device the draw lands on.
Two sessions with the same parameters get different devices. A test that
asserts on navigator.userAgent or a pixel-exact screenshot fails on the second
run for that reason. See
Automated testing for what to pin.
Picking an OS
Section titled “Picking an OS”os |
Coverage |
|---|---|
windows |
The default, and the most tested |
android |
Mobile profiles, well covered |
macos |
Available, fewer profiles |
linux |
Desktop Linux profiles. Available, fewer profiles |
Don’t reach for os=macos expecting the same pass rate as Windows. The profile
pool behind it is small.
os=linux presents as Chrome on a Linux desktop: the user agent says
X11; Linux x86_64, and the GPU, fonts and screen describe the same machine.
Version matching
Section titled “Version matching”browser_version_min and browser_version_max default to the major version of
Chromium the browser is running. Leave them alone. Set them lower and the
profile claims a version whose behavior the engine does not have, which is
exactly the kind of cross-signal mismatch detection looks for.
A fixed device
Section titled “A fixed device”For a test that needs the same device on every run, email [email protected].
Was this page helpful?
Thanks — that helps us fix it.