# Geolocation spoofing timeout: the bug that answered once

> 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.

Source: https://mosaicbrowser.com/blog/geolocation-answered-once

## 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.

![1 of 5: The first call answered, the next four timed out](https://mosaicbrowser.com/covers/geolocation-answered-once.webp)

A geolocation spoofing timeout is what our engine shipped until 4 October 2026. A profile behind a Lisbon exit answered its first `getCurrentPosition` with coordinates near the exit, then failed every call after it with error code 3, TIMEOUT. A watch started after that one-shot timed out too. The first answer was right, and that is why nobody saw the bug: every manual test is a first call.

## It was reported as missing

The report said geolocation did not work. It did. Patch `0114-geolocation` has implemented the `--fingerprint-location` switch since the first build of the engine, and the launcher draws the coordinates from the profile's measured exit. On the Lisbon profile the page received `38.73076,-9.36132` against an exit at `38.6875,-9.3151`, a few kilometres apart, which is what we intend.

So we asked the page a second time. It waited, and then it failed with code 3. So did the third call, and the fourth. A watch opened after the one-shot never received a position either.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## Why the geolocation spoof timed out: a latch never cleared

A position watch in Chromium asks for the next position after it delivers one. If our patch answered that request every time, a watch would spin forever, handing the page the same fix in a tight loop. So the patch sets a flag, `fingerprint_position_sent_`, once it has delivered a fix, and stays quiet after that.

The flag was correct. Nothing ever cleared it. The comment beside it argued that one fix and then silence "is what a real device reports when it has not moved". That holds for a watch. A phone that has not moved stops sending new watch events. It does not hold for a fresh `getCurrentPosition`: a phone sitting still on a desk still answers every time a page asks.

The fix is one line in `StartRequest`. That function is reached only by `getCurrentPosition`, by `watchPosition`, and by the page becoming visible again, and each of those is a request nobody has answered yet. The watch's own follow-up request arrives by a different path, `UpdateGeolocationState`, and still hits the latch, so the loop stays closed.

![A Mosaic profile fingerprint with timezone, language and geolocation drawn for the exit](https://mosaicbrowser.com/shots/fingerprint-detail.png)
*Geolocation is drawn from the exit, like the timezone*

## Why answering once is worse than not answering

A browser that refuses geolocation is ordinary. Many people say no to the prompt, and many sites never ask. A browser that answers once and then times out for ever matches no real device. A site that polls position, as delivery maps and store finders do, reads that pattern without trying to.

And the pattern sits on the surface the patch was written to protect. The point of claiming the exit's coordinates in C++, rather than in page JavaScript (we explain that choice in [C++, not JavaScript](https://mosaicbrowser.com/blog/cpp-not-javascript)), is that the browser behaves like one machine in one place. Half-working behaviour gave away more than the real coordinates would have.

## What the check holds now

`check:geolocation` launches the engine with a location nowhere near our Mac and asks four questions:

1. Five `getCurrentPosition` calls in a row: all five answer.
2. A watch opened after a one-shot: it answers.
3. A standing watch over six seconds: exactly one update, because the profile has not moved.
4. The claimed coordinates: they arrive in the page.

Before calling the fix done we put the bug back, rebuilt the engine and ran the check against it. It reported `1 of 5 consecutive getCurrentPosition calls answered` and named the watch pairing, while the first read still returned the test coordinates. A check that asks a second time is the one this bug needed.

## The jitter we kept small

While we were in that code, we were asked for the coordinates to be moved by about 50 kilometres instead of the ±0.05 degrees (roughly 5 km) they move now. We kept 5 km. Fifty kilometres takes a point outside the metropolitan area the IP itself resolves to, and our own realignment already treats more than 0.5 degrees from the exit as a mismatch worth correcting. A bigger jitter would create the disagreement the feature exists to prevent. Five kilometres is enough that no two profiles share a point and none reports the computer's exact position.

How the coordinates fit with the timezone, language and the rest of the machine is on [real fingerprints](https://mosaicbrowser.com/features/real-fingerprints), and the list of values a page can read is in [browser fingerprint](https://mosaicbrowser.com/glossary/browser-fingerprint). The question to ask of every one of them is the one that found this bug: does it work twice?

## Questions

### Why does a spoofed geolocation time out?

In our case a latch that stops a position watch from looping was set after the first answer and never cleared. Every later getCurrentPosition call waited for a fix that never came and failed with code 3, TIMEOUT.

### What does error code 3 mean in the Geolocation API?

Code 3 is TIMEOUT: the browser did not produce a position within the time the page allowed. Code 1 is permission denied and code 2 is position unavailable.

### Is a geolocation that times out worse than one that is blocked?

Yes. Plenty of real browsers refuse geolocation. A browser that answers once and then never again matches no real device, and any site that asks twice can see it.

### Where do Mosaic's coordinates come from?

From the profile's measured exit: the proxy's location, or the computer's own address when there is no proxy. They are moved by up to 0.05 degrees, a few kilometres, so no two profiles report the same point.

## Related

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.

[Why we change fingerprints in C++, not JavaScript](https://mosaicbrowser.com/blog/cpp-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.

[Browser fingerprint: what a site can read about your machine](https://mosaicbrowser.com/glossary/browser-fingerprint): A browser fingerprint is the set of values a site reads about your browser and computer, which together recognize you without cookies. What goes into one.

[Twenty APIs Chrome doesn't have: Chromium's field trials](https://mosaicbrowser.com/blog/twenty-apis-chrome-lacks): 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.

[The RTT client hint, read off the wire](https://mosaicbrowser.com/blog/rtt-on-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.

[Same IPHey score, opposite reasons](https://mosaicbrowser.com/blog/iphey-same-score-different-signal): 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.

[Why IPHey's location card can be greener on a profile than on Chrome](https://mosaicbrowser.com/blog/iphey-location-card): 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.

[Header order is a fingerprint, and a tunnel cannot touch it](https://mosaicbrowser.com/blog/chrome-header-order-through-a-proxy): 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.

## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. 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.
