- Kameleo 4.5.0 and Undetectable both scanned risk 100 on the first attempt; the device flag fired independently of the VPN exit.
- Only two single spoofs trip browserTampering on our bench: a UA that contradicts the engine, and a GPU string the machine cannot back.
- tamperingScore read 0.0029 on the baseline and on every spoof; the boolean discriminates, the score does not, so do not threshold it.
- On a fresh machine a switcher minted distinct ids that linked_accounts did not join; after ~30 sightings the matcher re-joined new ones.
- TLS and HTTP/2 come from the kernel the vendor shipped, not from the profile; Kameleo, Linken Sphere and Multilogin say so themselves.
A profile switcher turns one laptop into hundreds of different-looking devices and pairs each with a clean residential exit. Every value a page can read is set per profile: user agent, GPU strings, canvas hash, font list, timezone. A fingerprint library that hashes those values sees a new visitor each time; an IP-only stack sees a home connection. This post is the index for our lab series on those tools. It rests on two runs: the Kameleo 4.5.0 and Undetectable live scans from June, and the August harness run that took our tampering flag apart one spoof at a time. Every number below is a measurement with its method or a vendor's own documentation, and each table row says which.
Setup
Two runs, two paths into one pipeline. The live scans went through the homepage scanner, which posts to the public /api/verify route — not /v1/evaluate, but the same signal pipeline behind it — so the signals it displays are the ones the API returns. The harness run went through the keyless /api/device-intel path, which returns the scanner's device block without spending a key. Its field names are the scanner's camelCase (browserTampering, tamperingScore, visitorId), labeled as scanner output wherever they appear.
| Run | Tool and version | Profile | Target path | Date | Key type |
|---|---|---|---|---|---|
| Live scan A | Kameleo 4.5.0 on a Windows 11 desktop | "test": Chrome 149 on Windows 10, en-US, no proxy on the profile, Proton VPN running system-wide | Homepage scanner → /api/verify | 2026-06-12 | None (scanner) |
| Live scan B | Undetectable, Chromium-based profile (version not recorded) | Windows 11, Chrome 149; WebGL metadata spoofed, WebGPU masked, fonts and window size emulated, canvas and audio left real | Homepage scanner → /api/verify | 2026-06-12 | None (scanner) |
| Harness | Our WKWebView shim (scripts/antidetect) on a macOS host | none (baseline), macos-safari, win11-nvidia, noisy-canvas, plus eleven single-spoof ablations; fresh storage jar every run | /api/device-intel (keyless) | 2026-08-12 | None (public path) |
Both live results are the first scan each browser produced, not the best of several, as recorded in the Kameleo and Undetectable live test. The harness is a JavaScript shim injected at document start: it changes what a page can read and nothing underneath, the ceiling we return to in the limits.
The tools: engine, what a profile changes, what it cannot
The engine column is the vendor's own description, linked; the middle column is what the vendor documents, or what the profile editor showed where marked. The transport column is the one a profile editor never reaches: the TLS ClientHello and the HTTP/2 SETTINGS frame come from the shipped build. We did not measure that layer; "not documented" means the vendor says nothing. Vendor pages and free-profile counts were checked on 2026-08-30.
| Tool | Engine (vendor's words) | What a profile changes | TLS / HTTP/2 | On our bench |
|---|---|---|---|---|
| Kameleo | Chroma (Chrome, Edge) and Junglefox (Firefox) kernels (Kameleo docs) | In our run: WebGL and WebGPU metadata, "Intelligent" canvas noise, a 466-font list, cores, device memory, WebRTC, timezone, geolocation | Kernel-bound; Chroma tracks Stable Chrome "within 5 days", Junglefox "about every two months" | 2026-06, v4.5.0: antidetect fired (live test) |
| Undetectable | Chromium-based (profile panel, in our run) | WebGL metadata spoofed, WebGPU masked, fonts, window size and speech emulated; canvas, audio, WebGL image and ClientRects left real | Not checked | 2026-06: antidetect and automation fired (live test) |
| GoLogin | Orbita, "based on the Chromium platform" (GoLogin, updated 2023-08-29) | "Browser, operating system, platform, geolocation and 15 more parameters"; WebGL; fonts | Not documented | Queued (series #2) |
| Multilogin | Mimic, Chromium-based; the Firefox-based Stealthfox is retired (Multilogin, updated 2026-06-01) | User agent, canvas, WebGL, fonts (vendor, on Stealthfox) | Stealthfox: no TLS control, per the vendor (quoted below); Mimic not documented | Queued (series #3) |
| Dolphin{anty} | "Runs on the Chromium engine" (Dolphin docs, updated 2026-08-08) | User agent, canvas, WebGL, WebRTC, language, timezone, screen resolution, OS and device information; "more than 50 available characteristics" | Not documented | Not yet run; 5 free profiles |
| AdsPower | SunBrowser (Chrome-based) and FlowerBrowser (Firefox-based) (AdsPower) | Canvas, WebGL, audio, WebRTC, language, resolution, timezone, geolocation, fonts; "50+ customizable parameters" | Not documented | Queued (series #4); 2 free profiles |
| Incogniton | Chromium, exposed over WebSocket, debugger port or WebDriver (Incogniton) | Device model, OS, browser version, timezone, language, plugins, screen resolution, audio and video capabilities | Not documented | Not scheduled; 10 free profiles |
| Octo Browser | "Octium browser kernel", a Chromium build (Octo) | Not itemized on the vendor site | Not documented | Queued in the Octo post |
| Linken Sphere | "The current Chromium engine" (Linken Sphere, 2026-03-06) | Per-profile fingerprints; not itemized on the page we checked | Chrome-equivalent ciphers and extensions, per the vendor (quoted below) | Researched profile queued |
| MoreLogin | Chrome and Firefox kernels (MoreLogin) | User agent, canvas, fonts, resolution; language, timezone and geolocation matched to the proxy region | Not documented | Second tier in the Octo post |
| VMLogin | A modified "Chrome kernel" (VMLogin) | "Dozens of browser fingerprint parameters"; WebRTC, public and internal IP, language | Not documented | Not scheduled |
What fired
Both live scans came back at risk 100 on the first attempt. Kameleo was flagged at the device layer as antidetect and at the network layer as a Proton VPN exit on datacenter infrastructure; Undetectable was flagged as antidetect and as automation, on the same exit. Both scans also read Tor exit: yes on the same address they attributed to Proton VPN; nothing we captured explains that read, so the table reports it as displayed, unexplained. The layers fired independently: the device verdict is computed from what the browser presented, not from the address it arrived on, so switching the VPN off or swapping it for a residential exit does not change it.
| Signal (scanner label) | Live scan A: Kameleo 4.5.0 | Live scan B: Undetectable |
|---|---|---|
| Antidetect (device layer) | ✓ yes | ✓ yes |
| Bot (device layer) | – no | ✓ yes |
| VPN exit (network layer) | ✓ Proton VPN | ✓ same Proton exit |
| Datacenter (network layer) | ✓ yes | ✓ same exit |
| Tor exit (network layer) | ✓ yes | ✓ yes, same exit |
| Risk score | 100 | 100 |
The harness run is the finer instrument because it has a baseline: the none profile loads the shim and applies nothing, so any signal that differs from it is attributable to the spoofing, not to the web view. Fields are scanner output from /api/device-intel.
| Scanner field | none | macos-safari | win11-nvidia | noisy-canvas |
|---|---|---|---|---|
browserTampering | false | true | true | true |
tamperingScore | 0.0029 | 0.0029 | 0.0029 | 0.0029 |
antidetectBrowser | false | false | false | false |
botDetected | false | false | false | false |
osName (follows the UA) | Mac OS X | Mac OS X | Windows | Mac OS X |
visitorId, fresh jar every run | SNRP5DSC… returned again by eight ablation runs | nifHIy4C… stable ×3 | 2uyWPokd… one run recorded | HLEWtoo0… stable ×3 |
Three things to read off that table. browserTampering is the field the API surfaces as device.antidetect and the reason code antidetect_browser; it moved on all three composite profiles, including noisy-canvas, whose platform story is truthful and which only perturbs canvas and audio; the ablation below shows which single spoofs move it on their own. The scanner's separate antidetectBrowser field stayed false throughout, so the tampering flag is the one carrying the verdict. And tamperingScore read 0.0029 on all four, baseline included: the boolean discriminates, the score does not, so nothing here recommends a threshold on device.tampering_score.
The response shape (worked model)
No /v1/evaluate payload was captured for either live scan; the next scan's raw JSON is queued for the live-test post. This is a worked model, not a capture. Its inputs are the documented response fields, the values the scanner displayed for live scan A, the reason codes they imply, and the pipeline's hard-block set. Unobserved values are marked.
Review or block
The pipeline puts antidetect_browser in the same hard-block set as proxy, Tor, automation and emulator, so a session like live scan A returns decision: block before the network layer is counted; a commercial VPN alone returns review. We keep block for signup, bonus claims and checkout, the actions a profile farm exists for; the multi-accounting page covers the per-action policy. Where you want review, set a rule in the dashboard: a matching rule returns your action, most-severe rule wins, and the engine's verdict is kept in engine_decision. Do not build the softening on device.tampering_score; on our bench it does not move.
Put your own browser on the bench: the free scanner on the homepage returns the verdict the API returns, with every reason listed.
Open the scannerAblation: one spoof at a time
Eleven profiles, each changing one category with everything else untouched, plus the unspoofed none baseline, through the same scanner path on 2026-08-12.
| Profile | What it changes | browserTampering | visitorId vs baseline |
|---|---|---|---|
none | nothing | false | baseline |
ab-uasafari | UA to Safari on macOS (consistent with the engine) | false | new id |
ab-ua | UA to Chrome 131 on Windows | true | new id |
ab-webgl | WebGL renderer to "Apple M1 Max" | true | same |
ab-webgl-identity | WebGL hook installed, returns the machine's real strings | false | same |
ab-screen | screen size, availWidth and availHeight, DPR | false | new id |
ab-tz | timezone (Intl and getTimezoneOffset) | false | same |
ab-langs | navigator.languages | false | same |
ab-cores | hardwareConcurrency | false | same |
ab-canvas | canvas pixel noise | false | same |
ab-audio | audio noise | false | same |
ab-plugins | empty plugins and mimeTypes | false | same |
Engine consistency: the user agent is checked against the engine
Swapping the user agent is not itself suspicious. ab-uasafari changes it and passes; ab-ua claims Chrome 131 on Windows while running WebKit and fails. The scanner's browserName and osName labels follow the user agent either way, so on win11-nvidia the label says Windows and the flag says tampered, and the flag is the one telling the truth. Live scan A is the same contradiction at commercial quality: Chrome 149 on Windows 10, on a Windows 11 host, through a kernel that is not Chrome 149.
Render versus claim: the WebGL check is on the value, not the hook
ab-webgl reports a GPU the machine does not have and fails. ab-webgl-identity installs the same getParameter override, returns the real strings, and passes. The interception is invisible; what fails is a renderer string that something measurable contradicts. That cuts both ways: the check holds for values it can contradict and for nothing else. The live scans are the commercial version: Kameleo presented Intel HD Graphics 520 on hardware that is not that, and Undetectable presented NVIDIA WebGL metadata over the host's real canvas and audio output.
Noise passes the flag and never moves the identity
Canvas noise, audio noise, timezone, languages, core count and plugin emptying are each invisible to the flag, and eight of the eleven ablations returned the baseline's visitorId; only the user agent and the screen ever moved it. We then went below JavaScript: a patched WebKit build with perturbed readPixels and getImageData, verified to change the canvas and WebGL hashes, produced two profiles with the same visitor id as before and consecutive sighting counts, 17 and 18. Identification here does not rest on canvas or WebGL rendering. The device identifier is a server-side match that absorbs small divergences: two generated profiles with different resolutions collapsed into one id while a third split off.
Identity across profiles: what device linking survives
macos-safari and noisy-canvas each returned a distinct id that stayed stable across three runs with a wiped storage jar, timesSeen counting 1, 2, 3. Early in the session, then, a switcher minted stable, distinct identities from one laptop, and device.linked_accounts, the signal behind multi-accounting detection, did not join them. On a fresh machine, device linking alone does not survive a profile switcher; the tampering flag one layer up is what catches the switch. Later in the session the trick stopped working: a resolution and Safari version no earlier profile had used came back "seen 12 times", after roughly thirty visits from the machine. The matcher appears to gain confidence with history, which makes a different machine, not a better disguise, the operator's next move.
Network: the second layer, independent of the first
Both live scans flagged the Proton exit as VPN, datacenter and Tor, and neither device verdict depended on it. The variant we have not run is a paid residential exit. Classifying that exit, and naming the pool when known, is the network layer's job (proxy detection); the layered claim is that if an exit does get past it, the device flag still stands. Until that run ships, the claim is argued, not measured. One check we lacked at the time, a third-party checker had: the harness profile claimed America/New_York while leaving through an Estonian exit. The API now takes the browser's zone as tz and raises timezone_mismatch, but not on a VPN, proxy, Tor or datacenter exit, where timezone.checked is false; on that profile it would be information, not a verdict. Residential proxy detection is where the check earns its keep.
Transport: what we did not measure, and what the vendors say
Nothing in either run touched TLS or HTTP/2, and /v1/evaluate returns no field at that layer; the transport contradiction is read at your own edge, as the JA4 post describes. It matters because a profile editor cannot reach it. The JA4 specification hashes the cipher and extension lists "sorted in hex order", ignoring GREASE, and Cloudflare's JA4 write-up explains why: in early 2023 Chromium began shuffling TLS extension order per connection, which broke JA3, so JA4 keys on the set rather than the order (2024-08-12; both checked 2026-08-30). On HTTP/2, RFC 9113 requires a SETTINGS frame from both endpoints at the start of a connection, and Akamai's Passive Fingerprinting of HTTP/2 Clients found clients differ in which parameters they send, in what order, and with what values: Chrome 58 on macOS and on Windows 10 sent an identical 1:65536;3:1000;4:6291456, while Firefox 53 added five PRIORITY frames for streams it had not opened (2017).
Those values come from the kernel, and the vendors say so. Kameleo's kernel documentation lists "cipher suite ordering or ALPN set belongs to an older release than the claimed version" as a risk you fix by picking the matching kernel. Linken Sphere says its profiles send "the same set of ciphers and extensions as a regular Chrome of the corresponding version" because the browser runs current Chromium. Multilogin's retrospective says its retired Firefox engine "lacked control over subtle identifiers like TLS fingerprinting". A profile claiming Chrome 149 over a kernel that emits an older ClientHello is ab-ua one layer down.
What this does not show
- One machine, one connection per run. Every network signal reflects the connection the run used, not the profile. The residential-exit run for Kameleo is queued, not done.
- The harness stops at JavaScript. A shim injected at document start, not a rebuilt engine; it is not Kameleo or Multilogin. Its results say what the tampering flag keys on, not how a commercial kernel fares.
- First scans only, and no raw JSON. The live results are scanner screenshots; Undetectable's version was not recorded and no
/v1/evaluatepayload was captured. Both are queued for the next revision of the live test, as is the unexplained Tor read on the Proton address. - The tampering score does not discriminate. 0.0029 on the baseline and on every spoof. There is no threshold to recommend, and the reference's 0.6 line predates this run.
- No product identification.
device.antidetectis a class flag; the API does not return which tool was used. The tool table is ours, built from vendor documentation. - Nine of the eleven tools have not been on the bench. Their rows are what the vendors publish. A row is not a result.
- Combinations and screen plausibility are unmeasured. Whether two profiles built only from the passing categories stay unflagged and still separate into distinct ids is unknown, and the impossible resolutions a third-party checker flagged are not a check we make.
Reproduce it
Two paths. The short one needs no key: install a free tier (AdsPower, Dolphin and Incogniton list 2, 5 and 10 free profiles, linked in the table), open maskbreak.com inside a profile and read the scanner; it is the pipeline the live scans went through. The longer one runs the profile against your own key. Put the collector on a page; it fills two hidden inputs, monocle for the network layer and sentinel_fp for the device layer (Sentinel was the product's earlier name; the SDK keeps it).
<!-- inside <head> -->
<script async src="https://maskbreak.com/assets/sentinel.js"></script>
<!-- any form you want evaluated -->
<form class="monocle-enriched" id="signup">
<input type="email" name="email">
<!-- the SDK injects name="monocle" and name="sentinel_fp" -->
</form>Submit the form from inside the profile, take the two values off the request, and post them with your personal sk_test_ key: full pipeline, event badged as test, no webhooks, no usage counted. The public sk_test_sandbox key answers only the fixed test_* tokens, so it cannot evaluate a real profile.
curl -s -X POST https://maskbreak.com/v1/evaluate \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"token": "eyJ...", # the monocle input
"fingerprintEventId": "a1b2...", # the sentinel_fp input
"accountId": "lab-profile-01" # optional: makes linked_accounts appear
}' | jq '{decision, risk_score, reasons, device}'Run it three times from one profile and device.times_seen should count up while device.visitor_id stays put; run it from a second profile on the same machine and, early on, you should get a second id, the gap the tampering flag covers. Our bench is the WKWebView harness in the repository; these commands produced the ablation table. A local server without vendor keys returns an empty allow for every profile, so the run has to reach an environment where the device layer is live.
scripts/antidetect/build.sh # parses spoof.js with jsc, builds both binaries
scripts/antidetect/antidetect selftest macos-safari # what the page sees about itself
scripts/antidetect/antidetect run win11-nvidia https://maskbreak.com --wait=8
SENTINEL_LAB_KEY=sk_test_YOUR_KEY \
scripts/antidetect/antidetect verdict noisy-canvas https://maskbreak.com # posts to /v1/evaluateNext in this series are GoLogin and Multilogin in the live-test format, then the residential-exit run for Kameleo, then AdsPower. Each ships with its setup table, first-scan result and raw response, and each gets a row update here. The rest of the detection research section follows the same rule: no run, no report.
Questions people ask
- Can antidetect browsers be detected?
- At the device layer, in every run we have published: Kameleo 4.5.0 and Undetectable were both flagged as antidetect on their first scan. On our own bench the three composite profiles (macos-safari, win11-nvidia, noisy-canvas) tripped browserTampering and the unspoofed baseline did not; of eleven single spoofs, only a user agent that contradicts the engine and a GPU string the machine cannot back fired on their own. Nine of the eleven tools in the table have not been run.
- Which antidetect browser is undetectable?
- None that we have run scanned clean, and we do not rank the ones we have not run. The API returns device.antidetect as a class flag; it does not name the product, and this post does not claim it can. The two tools with results are Kameleo (antidetect fired) and Undetectable (antidetect and automation fired). GoLogin, Multilogin and AdsPower are queued in that order.
- Does a residential proxy hide an antidetect browser?
- It can hide the network layer, not the device layer. Both live scans flagged the Proton exit as VPN, datacenter and Tor, and neither device verdict depended on it: the antidetect flag is computed from what the browser presented. Classifying a residential exit is the network layer's job; if one gets past it, the device flag still stands. That run is queued for the Kameleo post; until it ships, treat the layered claim as argued, not measured.
- What does the tampering score mean in the API?
- device.tampering_score is documented as 0 to 1. On our bench the scanner's tamperingScore read 0.0029 on the unspoofed baseline and on every spoofed profile, while the boolean browserTampering moved on the three composite profiles and on two of the eleven single spoofs. So the boolean carries the verdict and the score does not discriminate. Do not build a rule on the score until a run shows it moving; route on decision and the antidetect_browser reason code instead.
- How do I test an antidetect browser against my own API key?
- Put the collector script on a page with a monocle-enriched form, submit it from inside the profile, take the monocle and sentinel_fp values off the request, and POST them to /v1/evaluate with your personal sk_test_ key. That key runs the full pipeline, badges the event as test and never counts toward usage. Run it three times: times_seen should climb while visitor_id stays put.
Run the same check on your traffic
The free scanner on the homepage returns the verdict the API returns: network and device signals in one call, with every reason listed. What each signal means: <a href="/bot-detection">bot detection</a> and <a href="/device-fingerprinting">device fingerprinting</a>.