On a desktop, Chrome sends Sec-CH-UA-Model as an empty string. On Android it sends the phone's model name, SM-S918U or moto g05, to any site that asks for it with Accept-CH: Sec-CH-UA-Model. The User-Agent string no longer carries that name, which is why the header matters. Our first Android patch sent an empty model, and it took a second patch to send the phone's.
What the Sec-CH-UA-Model header carries on Android
Chrome's User-Agent reduction froze the Android part of the string. Every Chrome on every Android phone now writes Linux; Android 10; K, whatever the real version and whatever the device. Before the freeze a site could parse the model straight out of the string. After it, the K tells you nothing.
The model did not go away. It moved into the client hints, behind an explicit request. A site that wants it sends Accept-CH: Sec-CH-UA-Model on a response, and from then on Chrome adds the header to its requests over HTTPS. A script can read the same field with navigator.userAgentData.getHighEntropyValues(['model']). Low-entropy hints such as Sec-CH-UA-Mobile and Sec-CH-UA-Platform arrive without asking. The model is high-entropy, so it waits to be asked, and that is the privacy gain of the reduction.
Chromium's own BuildModelInfo() explains the desktop side: on a release Android build it returns the hardware model, and everywhere else it returns nothing. So a desktop profile's honest answer is "", and a phone that answers "" contradicts itself.
Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.
Mosaic is a desktop app
Email yourself the link and open it on your computer.
How it works: Machines that exist
The patch that read the freeze the wrong way
0137-android-platform.patch taught Tesseract Chrome, our engine, to claim Android on every path a phone is read by. There are six of them: the platform token, the frozen unified string, the client-hint metadata, Mobile Safari/537.36 in place of Safari/537.36, the UA-CH mobile bit (which a desktop build compiles out), and navigator.platform as Linux armv81.
For the model, 0137 wrote metadata->model = "". The reasoning was in the patch description: Chrome 110 and later report the literal Android 10; K and no device name, so a real model string would be "a value no Chrome emits". The first half was right about the string. It was wrong about the header, because the reduction moved the model rather than deleting it.
The result was a profile answering mobile: true, platform: "Android" and model: "" in one object. A detector can read all three in one call, and no phone gives that combination.
We did not fix it from memory, because reasoning about what Chrome reports was how the bug got in. The fix in 0138-android-ua-model.patch rests on three checks. Chromium's source returns the hardware model on Android unconditionally. Its GetCpuBitness() returns an empty string on a non-desktop Android, so our claim was also wrong about bitness: it reported the host Mac's 64 beside an empty architecture. And three phones read directly:
SM-S918U {"architecture":"","bitness":"","model":"SM-S918U", ...}
moto g05 {"architecture":"","bitness":"","model":"moto g05", ...}
SM-S936U {"architecture":"","bitness":"","model":"SM-S936U", ...}
Every one has a model, and every one has an empty architecture and an empty bitness. 0138 sets the model from a new switch, --fingerprint-ua-model, and sets bitness to empty for a claimed Android. form_factors needed nothing: Chromium computes it from the mobile bit, which 0137 already answered, so it reads ["Mobile"].
Why the model comes from a captured phone
The model is a switch, not a table keyed on the platform, because it belongs to one device. Mosaic's Android catalogue holds those three captured phones, whole: screen, pixel ratio, thread count, memory, touch points, Android version, the GPU renderer with that renderer's own extension list, and the fonts. A profile draws a row and takes all of it. The model it sends is the name that row was captured under.
A model name drawn on its own would recreate the fault the catalogue exists to prevent. A Samsung model code beside another phone's GPU and screen is a phone nobody makes, and a site holding a device table can see it. (The same rule for desktop machines is on real fingerprints.)
On a desktop profile the switch is never sent. Mosaic passes --fingerprint-ua-model only when the profile has a model, so desktop profiles report "" like every desktop Chrome.
What a site sees from a Mosaic Android profile
The model and the platform are set in C++, so they hold in every frame and worker, the same as the User-Agent string itself. We measured one freshly drawn Android profile on an Apple Silicon Mac, with no proxy. Pixelscan passed and named the browser as Chrome on Android, IPHey read Trustworthy at 100/100, bot.sannysoft showed 58 of 58 green, and CreepJS reported 0% headless and 0% stealth.
That profile is still desktop Chromium in device mode, not a phone. It has no app store and no cellular radio, and sites that need the TikTok or Instagram app rather than their websites need a real device. What the profile does is answer a website's questions the way the captured phone answered them, model included. The values a page reads first are listed under browser fingerprint.




