The Mosaic blog
News from the product, the engineering behind it, and the reasons we made the calls we made.

Headless is not a way to hide, it is a way to be seen
Headless Chrome names itself HeadlessChrome in its user agent and drops the real GPU. Why our background work runs headed, off-screen, instead.

Half a spoof is a contradiction
Partial fingerprint spoofing detection is simple: ask one question two ways. We cut five spoofs that changed one route to a value and left the other.

Chromium's mock keychain, and why we encrypt the store
Chromium's --use-mock-keychain keeps cookie encryption on but uses a password from its own source. We read a live session cookie off disk, then sealed it.

The RTT client hint, read off the wire
ECT, RTT and Downlink headers behind a proxy, measured: a claimed round trip moved JavaScript to 300 and left the RTT header at 50. So we claim neither.

The warm-up robot that left a bot signature
Our warm-up robot scrolled with window.scrollBy, so pages saw isTrusted false and a scroll with no wheel. How a page detects that, and what we changed.

Why we built our own Chromium instead of injecting JavaScript
Mosaic changes fingerprint values in C++ inside its own engine. Three measured differences between Chrome and plain Chromium explain why that matters.

Reading a proxy seller's grammar off one pasted line
Proxy sellers put country, session and lifetime in the username. Mosaic reads the grammar off the line you paste and keeps every token it does not know.

Importing other browsers' libraries by shape, not file name
To migrate anti-detect browser profiles, Mosaic reads which product wrote the file from its fields, keeps what that product recorded and draws the rest.

The values every profile inherited from this Mac
A browser fingerprint value inherited from the host is identical across two unrelated profiles and equal to the computer. That test found dark mode and more.

Trashing a profile closes its browser, at every door
Delete a running profile in Mosaic and its browser closes first. If the browser will not close, the profile stays on the list, with its Stop button.

Why navigator.webdriver is true, and why false proves little
navigator.webdriver is true because automation set it on purpose. We measured a debug-attached Chrome reading true and still scoring 100 on IPHey.

Letting an AI assistant run browser profiles over MCP
Mosaic's MCP server lets Claude or any MCP client create, start and drive browser profiles. Three faults we found when a model was the caller.

fingerprint-scan.com bot score: 0 before anything was measured
fingerprint-scan.com shows Bot score 0/100 while its hash still reads collecting. Read the score only after the hash exists, and expect 15, not 0.

A retired bot test can still answer 200 and look like a pass
Antoine Vastel's fp-scanner demo now serves his homepage with HTTP 200, which a challenge reader scores as a pass. His tests moved; here is where.

Why Mosaic profiles never claim Edge
Our Edge profiles sent either a full build in the User-Agent or an empty Sec-CH-UA-Bitness client hint. No real Edge sends either, so we refuse Edge.

Behaviour tests score your mouse, not just your browser
A behavioral bot detection test scores mouse paths and dwell next to browser attributes. Load one and touch nothing, and the score describes your stillness.

An IPv4 and an IPv6 address from one proxy is one exit
A proxy that shows both an IPv4 and an IPv6 address is a dual-stack exit. Sites see either one, so look both up before you trust the location.

49 browser fingerprint detectors, and how we read them
Our browser fingerprint detectors list: 49 public test pages in five groups, 40 read by a script and 9 kept as reference because they print no verdict.

Your dark mode is sent as a request header
Sec-CH-Prefers-Color-Scheme sends your dark mode setting to any server that asks. On our profiles it carried this Mac's own settings, until each drew its own.

A DNS server in another country is not always a leak
Our US exit resolved through Google in Germany, and nothing leaked. A foreign resolver in a DNS leak test matters only if it is your own ISP's.

setUserAgentOverride without metadata wipes your client hints
Without userAgentMetadata, setUserAgentOverride left our page with empty brands while a worker reported both. We set the User-Agent at launch instead.

Our profiles said x86 and the browser said arm
Chrome on an Apple silicon Mac sends Sec-CH-UA-Arch "arm". Our profile records said "x86", read by nothing, so we fixed the record, not the browser.

Why video calls struggle in a proxied profile
WebRTC sends media over UDP, which an HTTP or SOCKS proxy in Chrome does not carry. With non-proxied UDP blocked, only a TURN relay over TCP is left.

BrowserLeaks gives you values, not verdicts
BrowserLeaks prints a canvas signature, a renderer and a JA3 hash, and no pass or fail. Each value means something only beside Chrome on the same machine.

Sec-CH-UA-Model is empty on a desktop and a phone's name on Android
Chrome on Android freezes the model out of the User-Agent but still sends it in Sec-CH-UA-Model. Our first Android patch sent an empty one.

Why Cover Your Tracks sends you to firstpartysimulator.org
firstpartysimulator.org is an EFF domain. Cover Your Tracks loads it mid-test as a stand-in first-party site, so a hang there leaves the test running for ever.

Four 504s on one session: rotate, don't retry
On a sticky residential proxy a session token is an exit, so retrying it asks a dead exit three more times. Rotate to a fresh session instead.

18.28 bits: what Cover Your Tracks' number measures
Bits of identifying information is log2 of how rare your browser is in the EFF's sample. 18.28 bits is about one in 318,000, and it is not a tracking score.

A browser with no RTCPeerConnection doesn't exist
Deleting WebRTC leaves a Chrome no one runs. Zero candidates with the API still present is every machine on a UDP-blocked network, so that is what we ship.

A fingerprint that depended on launch order
Chrome's --user-agent flag left Sec-CH-UA-Bitness empty, and a cache empty at every app start decided when we sent it. One profile wore two identities.

Two WebRTC flags we used never existed in Chromium
Chrome has one WebRTC command line flag for leaks: --webrtc-ip-handling-policy. We also sent two that don't exist, and Chromium accepted both in silence.

fingerprint.com wouldn't load through our proxy, and that was a result
Through one residential exit the FingerprintJS demo hung; from the same Mac with no proxy it loaded. Our runner blamed itself. The exit was why.

A full Chrome version with no build number is one no Chrome shipped
Our engine reported uaFullVersion as "153". Real Chrome always sends four numbers. Sending the full build moved FingerprintJS's confidence to stock's 0.99.

A canvas readback is not a rendering check
Forcing Chrome's colour profile to hdr10 lifted a page's dark background and dimmed its white on macOS. Four checks missed it; a screen capture did not.

Scoring a rotating proxy scores one draw from the pool
A reputation check on a rotating proxy scores whichever exit the pool handed out that second. Pin a sticky session to score the address you will browse from.

The .local address in your WebRTC test is Chrome working
A random .local name in a WebRTC leak test is Chrome hiding your LAN address. We once switched it off, and the empty result was the louder tell.

A FingerprintJS pass has two halves
A FingerprintJS visitor ID should differ per profile and hold across launches of one. Colliding ids link profiles; ids that change flag a spoofer.

Why our reputation check doesn't ask Spamhaus
Spamhaus refuses queries that arrive through public resolvers, so asking it from an ordinary computer returns a confident wrong answer. We left it out instead.

The control that settles "is it our patches?"
Unpatched Chrome for Testing got the same IPHey verdict as our engine, so our patches were not the cause. Its voice list still differs from Chrome's.

A CONNECT to a raw IP means your machine resolved the name
To check if DNS goes through your proxy, read what the proxy is asked for. CONNECT example.com:443 means it resolved the name; a raw IP means you did.

Same fonts, different measurements: per-profile glyph advances
Font fingerprint randomization can hide fonts or change how wide glyphs measure. Hiding read as masking; per-profile glyph advances did not.

Is a Google DNS server in your leak test a leak?
Google DNS in a leak test is not a leak by itself. It depends on where you browse from: the resolver your own ISP runs is the leak, someone else's is not.
Pixelscan calls a stock Chrome masked 3 times in 20
A Pixelscan false positive, measured: an untouched Google Chrome read 'Masking detected' on 3 of 20 runs. Our fresh profiles read it once in 74.

"400 Target website error" and one observation of a pool
A proxy's 400 Target website error usually means the pool gave you an exit that could not reach the site. One answer like that does not make the proxy dead.

--host-resolver-rules: the first match wins
Chrome's --host-resolver-rules tries MAP rules in order and stops at the first match. EXCLUDE is not ordered. A wildcard put first ate our extension's API call.

Runtime.enable flips a detector, so we don't call it
Runtime.enable detection, measured: one DevTools command turned a clean Mosaic profile into isBot=true on deviceandbrowserinfo. We removed all five calls.

Why we leave the TLS and HTTP/2 fingerprints alone
A TLS fingerprint mismatch is a User-Agent claiming a browser the handshake did not come from. We avoid it by never claiming a Chrome the engine is not.

baseLatency is shared by every profile and moves with your headphones
AudioContext baseLatency and outputLatency come from the real output device, so every browser profile on one computer reports the same pair.

Chrome shuffles its ClientHello, so compare sets
Chrome TLS extension permutation reorders the ClientHello on every connection. Only the extension order moves, so a client that repeats its order is not Chrome.

Why 30% of our profiles use dark mode
The prefers-color-scheme dark mode share in real traffic is far below survey figures. Every profile on our Mac said dark; now each draws dark 30% of the time.

DNS prefetching, the lookup that skips your proxy
Chrome's DNS prefetcher resolves names through your own resolver even with a proxy set. Mapping every name to NOTFOUND makes the leak fail closed.

Sorting the property list hid the question
Property enumeration order is a fingerprint that sorted diffs can't see. Compared unsorted, our engine differed from Chrome only in its 20 field-trial entries.

A green tick that means the socket opened is worthless
A proxy checker can pass while sites fail, because most test plain HTTP and the browser needs a CONNECT tunnel. Exit and stability are separate answers too.

A proxy that answers HTTP and refuses CONNECT
A proxy that works for HTTP but not HTTPS is refusing CONNECT, the tunnel every HTTPS page needs. A check that fetches a plain-HTTP page cannot see it.

Same IPHey score, opposite reasons
IPHey Unreliable on a normal Chrome is usually the exit. Our profile and a stock Chrome both got MX 90, for a browser signal and a location signal.

344 profiles said "denied": one value, everywhere
Notification permission became a fingerprint: Notification.permission read denied on all 344 of our profiles, where an unconfigured Chrome says default.

"Rotation URL opened" is not "the IP changed"
Mobile proxy change-IP link not working? It worked if it answered 2xx and the exit differs once the modem redials. Our own Proxies page checked neither.

socks5 and socks5h: where Chrome resolves the name
In curl, socks5h sends the hostname to the proxy and socks5 resolves it locally. Chrome's socks5 already sends the name; the leak is elsewhere.

A profile in Austria claimed New York
Browser location does not match IP: a no-proxy profile made in Austria claimed New York. One failed lookup was never retried, and a table filled the gap.

An audio spoof that changed the sound every time it was read
getChannelData returns the buffer's own memory, so our audio noise multiplied samples on every read: v·f, then v·f². A seeded byte fixed it.

Why IPHey's location card can be greener on a profile than on Chrome
On one US exit in one minute, stock Chrome got IPHey's masked or inconsistent location data and our profile got green. The difference was the timezone.

A spoof that answers once: the geolocation bug we shipped
A geolocation spoofing timeout we shipped: the first getCurrentPosition got the exit's coordinates, every later call failed with code 3. One latch was to blame.

IPHey's score moves on unmodified Chrome. Read the card.
A low IPHey MX score is the sum of five cards. Untouched Chrome scored 100, 100, 70 and 0 in one afternoon, so the red card tells you more than the number.

Our proxy relay told the gateway it was a script
Chrome sends Proxy-Connection: keep-alive, lower case, to every proxy. Our relay sent Keep-Alive, no User-Agent and Connection: close, until we copied Chrome.

Every profile on one Mac drew the same WebGL image
A WebGL fingerprint is the same in every browser profile on one machine because the GPU draws it. Ours was too, until per-profile noise moved the hash.

Running 49 fingerprint tests unattended, and what that takes
To automate browser fingerprint testing, tell a failed profile from a dead site, an unreadable page and your own bug. Our runner names each across 49 rows.

Header order is a fingerprint, and a tunnel cannot touch it
Sites read the order of Chrome's HTTP headers as well as their values. Through a CONNECT tunnel a relay carries ciphertext, so it cannot reorder a thing.

Twenty APIs Chrome doesn't have: Chromium's field trials
Unbranded Chromium runs its field-trial testing config at every launch. Against Chrome 153 that was 20 API differences, and one switch moved them all.

Chrome opens connections before your test starts
We counted 1, 0 and 0 CONNECTs in three launches and thought Chrome was bypassing the proxy for HTTPS. Our counter was reset after Chrome had started.

What happens to QUIC when Chrome has a proxy
With an HTTP, HTTPS or SOCKS5 proxy set, Chrome reaches sites over TCP tunnels, so a page that gets HTTP/3 direct gets HTTP/2 through the proxy.

Why we change fingerprints in C++, not JavaScript
A JavaScript spoof is one realm deep. An iframe read the real source of 33 of our patched functions; the same values changed in C++ agree everywhere.

41 of 45 hand-written Windows renderer strings were wrong
Chrome's ANGLE Direct3D11 renderer string carries the GPU's PCI device id. Our hand-typed Windows rows left it out, a string no real driver produces.

IPv4 from one probe, IPv6 from the other: one exit, not two
A proxy that shows an IPv4 address on one checker and an IPv6 address on another is usually one dual-stack exit. Rotation only shows within one family.

ERR_PROXY_CONNECTION_FAILED is about the first hop
Chrome's proxy connection failed error means it never reached the proxy address it was given. Check the host, the port and anything local before the exit.

Same proxy session on multiple profiles: forty profiles, forty exits
The same proxy session on multiple profiles means one exit IP for all of them. A batch of ours did it in September 2026; now every profile mints its own.
Pixelscan reads sixty WebGL fields, and the renderer string is one
Pixelscan's WebGL fingerprint is a render hash plus about sixty driver fields beside the renderer string. A chip that never existed failed like a real one.

A WebGL renderer string is a claim and the limits are the evidence
WebGL MAX parameters, shader precision formats and the extension list come from the real driver. Our engine moves them only with data from a real device.

Comparing two TLS handshakes: compare the set, not the order
To compare two TLS fingerprints, label GREASE and compare the extension set sorted. Our first diff against Chrome showed two differences, both noise.

What ERR_TUNNEL_CONNECTION_FAILED is telling you
Chrome's tunnel connection failed error means the proxy refused the CONNECT tunnel. In our runs the usual cause was a sticky session the gateway had retired.
When Pixelscan never stops scanning
Pixelscan stuck on scanning for 90 seconds was, in our runs, a profile claiming Windows on a Mac. A scan that never returns is a non-verdict, not a slow pass.

UNMASKED_RENDERER is null until a page asks for it
Chrome returns null for the unmasked WebGL renderer until the page calls getExtension, so a spoof that answers it earlier is a two-line tell.

Widevine device certificate privacy: one key for every profile
Widevine device certificate privacy needs a Google-signed browser. Without it, the first licence message is 1704 bytes where Chrome sends 2.

199 voices: how a speech list tells Chromium from Chrome
speechSynthesis.getVoices() lists 199 voices in Google Chrome on our Mac and 180 in any unbranded Chromium. A page reads the difference in one call.

The CreepJS proxy test is not about your proxy
CreepJS's proxy page looks for JavaScript Proxy objects wrapped around native functions. A flag there names a spoofing script, not your IP address.

Reading a browser's ClientHello with a one-minute listener
A raw TCP listener that reads the first TLS record and hangs up captures a browser's ClientHello. Ours and branded Chrome's matched in every field.
readPixels and toDataURL read a canvas from opposite ends
readPixels starts at the bottom row and toDataURL at the top, so position-keyed WebGL noise disagreed with itself. Keyed on colour, 4096 of 4096 pixels agree.

What CreepJS's prototype page tells you about JavaScript spoofing
CreepJS's prototype page checks each native function's keys, source and errors. A value patched in JavaScript trips it; a value changed in C++ leaves nothing.

JA4 moves with the Chrome major, and that is not a tell
Chrome 152 read t13d1517h2 and 153 read t13d1518h2: one TLS extension apart. Compare a JA4 only against a Chrome of the same major version.

Why our engine has no TLS fingerprint setting
You can't change Chrome's TLS fingerprint from a setting, and we chose not to patch it: our engine's JA4 matched Google's own build byte for byte.

The port number told us whether a proxy was SOCKS5
Nothing in host:port:user:pass says HTTP or SOCKS5, and curl assumes HTTP. At CatProxies we asked the port: 11000 answered SOCKS5, 10000 HTTP.

The four-line probe that catches position-keyed canvas noise
To detect canvas noise injection, draw one shape twice at two offsets and diff them. A real browser gives 0 bytes; our old noise gave 76.

What a CreepJS lie is, and why stock Chrome has none
CreepJS lies detected means an API behaved unlike the native one: wrong toString, wrong descriptor, or a worker that disagrees. Stock Chrome has none.

Why Chrome's JA3 hash changes on every connection
Chrome's JA3 changes because Chrome shuffles its TLS extension order on every connection, and JA3 hashes that order. JA4 sorts first and holds still.
Nothing here yet. The first one is coming soon.
Every account its own browser. One price for all of them.
Free holds 3 profiles and 10 profile creations, with no card. Pro is $49 a month for any number, or $490 a year with 2 months free. Try it for 14 days; cancel before day 14 and you pay nothing.