DEV Community

Santiago Blaine
Santiago Blaine

Posted on Originally published at antibrow.com

Your Desktop Anti-Detect Setup Doesn't Survive Mobile

When I added mobile personas to a browser that already had desktop ones working, I assumed the work was mostly string substitution: swap the user agent, set touch points to 5, shrink the viewport, done. That assumption survived about a day.

Mobile fingerprints aren't a variant of desktop fingerprints. They're a different set of constraints, and most of the desktop toolkit either doesn't apply or actively gives you away. Here's what I ran into, in the order it hurt.

The user agent has almost no room left

Desktop anti-detect work still has a habit of treating the UA string as a place to be creative. On mobile, UA reduction has frozen the format into a small number of shapes. There is essentially one correct form for Chrome on an Android phone, one for an Android tablet, and one for desktop Windows. Everything else — every extra token, every version in the wrong segment — is a mismatch against a string the detector can generate itself.

This turned out to be the single most effective filter when I was building a library of real device profiles. I collect fingerprints from consenting real visitors, and the first rule is: the UA must match one of the known-good forms character for character. A whitelist, not a blacklist. It rejects Edge, Samsung Internet, Opera and Yandex builds in one move — which matters because the engine reports Chrome branding, so a Samsung Internet UA sitting in the same profile row is a contradiction, not a variation.

It also catches something the collection side gets wrong: in-app webviews. Facebook's, Instagram's and TikTok's embedded browsers announce themselves in the UA (; wv) plus an app token), and my collector's own webview flag missed a batch of them. The UA string didn't. Never trust the capture layer's classification when the raw string is right there.

Webviews matter because their font sets and available APIs are the host app's, not a stock browser's. A profile captured from one is a profile of a thing your browser can't be.

Client hints are where mobile identity actually lives

On desktop you can often get away with ignoring high-entropy client hints. On Android you cannot, because model and platformVersion are the two fields that say which phone this is, and they have to agree with everything else.

The failure mode is subtle. If your persona lacks these values, something has to fill them in — and a generated placeholder like a round 15.0.0 platform version with an empty model is a fingerprint of your generator, not of a phone. So my import rules drop any captured device missing them. Those records are still a real device; they're just no longer usable as one.

Same reasoning applies to what you might call the "identity cross-check": navigator.platform, the client-hint platform, and the UA all have to tell the same story. Any disagreement is an automatic reject on the way in, because a contradiction shipped to a user is worse than a smaller library.

The GPU is the tell

Android GPUs are Adreno, Mali, PowerVR, Xclipse. They report through OpenGL ES with a characteristic renderer string, a specific set of supported extensions, and specific shader precision ranges.

Two rejects I check for explicitly:

  • Software renderers. SwiftShader or llvmpipe in a mobile profile means it came from an emulator or a headless run, not a phone. Those get dropped.
  • Direct3D on Android. A profile claiming to be an Android phone whose WebGL layer reports a D3D11 backend is a Windows machine wearing a mobile UA. This is exactly what a desktop-first spoofing setup produces when it only rewrites strings, and it's trivially detectable.

That second one is the general lesson: mobile spoofing fails on the surfaces that are computed rather than declared. You can declare a UA. You cannot declare the shader precision ranges the driver reports, unless the substitution happens deep enough in the browser to replace the real answer.

Worker threads and iframes have to agree

The check that catches the most half-finished spoofing setups is also the cheapest to run: ask the same question from a Web Worker, and from an iframe, that you asked on the main thread.

A userland override installed on the main thread's navigator does not exist inside a freshly created worker. So a page reads "Android, model X" on the main thread and "Linux x86_64, no model" from the worker, and it doesn't need any other evidence. I reject captured devices where the two disagree, for the same reason — that device was itself running something that only patched the main thread, and I'd be importing someone else's broken disguise as ground truth.

Anything that only lives above the engine has this problem structurally. Every new realm — worker, iframe, about:blank document — is a fresh set of natives your injection has to race and re-apply.

Screen geometry is a joint constraint

Desktop personas can get away with a viewport that's approximately plausible. Mobile ones can't, because the values are coupled:

  • Screen dimensions, device pixel ratio, available screen area and viewport size are all related through one device's actual hardware.
  • navigator.maxTouchPoints must be non-zero, and a "mobile" persona with a mouse-shaped pointer media query is a contradiction.
  • Tablets and phones have different UA forms and different aspect ratios, and mixing the two is a common generated-persona failure.

The efficient answer here is not to generate these values but to sample them together from a real device, as one unit. Which is why I run a real-device library at all rather than a generator: the joint distribution is the hard part, and real hardware gives it to you for free.

That library has a number I like quoting because it's the honest cost of the rules above: after all the filters, roughly one in nine collected devices is usable. Everything else fails on a webview UA, a mismatched platform, a missing client hint, a software renderer, or a worker-thread disagreement. Collecting a fake is much worse than collecting nothing, because a fake gets served to users under the label "real device."

What mobile still can't do

Being straight about the ceiling: the platforms people most want mobile personas for — the big social apps — largely don't care about your browser fingerprint, because their real defenses are in their native apps. Request signing, device attestation and behavioral models built on native telemetry are not things a web browser can present, no matter how good the persona is. Community reports about account survival on those platforms are consistent and unflattering to every product in this category, mine included.

Where mobile personas do earn their keep is everything else: mobile-only or mobile-preferential web experiences, testing responsive behavior against real device profiles rather than devtools emulation, and any target whose mobile web path is less hostile than its desktop one.

Just don't reach for it thinking it's a desktop profile with a different UA. The UA is the one part you almost can't get wrong, because there's only one right answer. It's everything computed downstream that decides.

Top comments (0)