Location Privacy by Construction: Coordinate Blurring and the Gulf of Guinea Bug
- privacy
- geolocation
- React Native
- Supabase
- JavaScript
- North
Most apps treat location privacy as an access-control problem: collect precise data, store it, then gate who can read it. North takes the stronger position for its "blurred" sharing mode: data that never leaves the phone needs no protection.
Blur on-device or it isn't blur
North's precision setting offers exact or approximate sharing. The approximate mode is almost embarrassingly simple — blurCoord rounds latitude and longitude to two decimal places before upload. Two decimals of latitude is about 1.1 km, so the member appears inside a roughly neighborhood-sized cell: "near downtown," not "at this address."
The entire privacy property lives in where the rounding happens: on the device, before the network call. The server, database, backups, logs, admin tools, and any future breach all contain only the coarse value — there is no precise coordinate to leak, subpoena, or be tempted to use "just for debugging." Blurring server-side would be a policy; blurring client-side is a fact. When you can choose between enforcing a privacy promise and making it structurally unbreakable, choose structural.
(Rounding to a fixed grid does have a known wrinkle: someone hovering near a cell boundary can flip between two cells, which leaks slightly more than one cell's worth of information over time. For a consenting-family product the trade is fine — the goal is "don't share my exact position," not anonymity against a motivated adversary.)
Pausing means the server forgets
The same construction-over-policy principle governs the pause feature. When a member pauses sharing, the client doesn't just set sharing_paused = true — it wipes the coordinates from the server row. Paused isn't "hidden from the UI but still tracked"; the data is gone. The roster shows a dimmed avatar at the last position known before the pause, clearly marked.
The Gulf of Guinea bug
That wipe produced my favorite bug in the project. With coordinates nulled, viewers' clients would read the update and members would teleport to latitude 0, longitude 0 — the ocean off West Africa, the place where all unhandled null-island bugs go to swim.
The culprit is one of JavaScript's classic coercions:
Number(null) === 0 // true
The store's update path numerically coerced incoming coordinate fields, and null slid silently into a perfectly valid-looking 0. No crash, no NaN, no warning — (0, 0) is a real place, which is exactly what makes the bug so quiet. The fix in applyLocationUpdate intercepts the sharing_paused flag before coordinate handling: paused members keep their last-known position with a "paused" status, and null coordinates are never treated as numbers.
The general lessons hiding in that bug:
Number(null)is 0,Number(undefined)is NaN. The one that doesn't fail loudly is the dangerous one.- Sentinel-laden domains need explicit branches. "No location" is a state, not a value, and the moment you encode states in nullable value fields you owe every consumer an explicit check.
- Privacy features create new data shapes. The wipe was a privacy win that introduced a payload (valid row, null coordinates) the client had never seen before. Privacy code paths deserve the same adversarial testing as auth code paths — they change invariants.
The pattern
Blur on-device. Delete on pause. Branch on absence before coercing values. Each is small, but they share a spine: the privacy promise is implemented as what data exists, not as who's allowed to see it — the difference between a policy you maintain forever and a property that holds by itself. It's the same philosophy as honest staleness — the app's value isn't maximal information; it's calibrated, consented information. What North deliberately doesn't show is most of the product.