How It Works

The shape of it

Three pieces talk to each other:

[Android phone]   --UDP+AES-256-GCM-->   [Go relay]   --WebSocket+inner AES-GCM-->   [Browser]
   (sender,                              (this server,                              (viewer,
    owns inner key)                       cannot decrypt)                            has inner key
                                                                                     from URL fragment)

The Android app captures GPS fixes, encrypts them with a fresh per-session key, and ships them to the relay over UDP. The relay's only job is to fan that encrypted bytestream out to whoever is watching the right WebSocket on the viewer side. The viewer decrypts with the same key - which it pulled from the URL fragment, the bit after the #.

Two layers of crypto, distinct purposes

There are two encryption layers running back-to-back, and the distinction matters:

So when the relay opens the outer envelope, what it gets is another sealed envelope it has no key for. The position data is sealed end-to-end between phone and browser.

Why the relay can't open the inner envelope

The inner key has to get from the phone to the browser somehow, without going through the server. The trick: it travels in the URL fragment.

https://your-relay/?id=<accessKey>#<encryptionKey>
                       │             │
                       │             └── browsers do NOT send the fragment to servers
                       └── server sees this; it's just a routing identifier

When the viewer opens the link, their browser keeps everything after the # client-side. The server only sees the request path and query (?id=...), which routes the WebSocket to the right encrypted stream. The encryption key, sitting in the fragment, is never sent anywhere over the wire.

This is a standard browser behavior the project leans on heavily. If you compromise the relay, scrape its logs, or own its operator - you still don't get plaintext positions out of any of those because the relay genuinely never had the key.

Server identity verification (MITM mitigation)

The outer ECDH handshake by itself doesn't prove you're talking to the right server - a sufficiently positioned attacker could intercept the UDP and re-do the exchange with you, themselves. To close that gap:

UDP, on purpose

The wire protocol is UDP, not TCP. Three reasons:

Two happy consequences of the design that show up the moment you take the app outside:

The maps

The viewer renders Protomaps tiles served from this server - not from Google Maps, not from Mapbox, not from any third party. The tile data is a single static file built from OpenStreetMap, downloaded once by the server operator at install time. After that, every tile request goes to your relay only.

The viewer fetches tiles via HTTP range requests against that file. The server isn't written to log which byte ranges were requested - but the HTTP framework underneath sees the request URI at handler time, so a determined operator could add such logging in a handful of lines. There's no obfuscation; the handler lives in server/main.go, read it yourself. What categorically isn't there, in any form, is a third-party CDN, a Google Fonts pull, or an analytics beacon - the whole map pipeline stays on your server.

What the relay can know

Being honest:

What the wire shows

On-demand semantics

Nothing runs in the background unless you flip a toggle on the phone. No silent GPS collection, no "background sharing for convenience". When the share timer expires or you toggle the session off, the phone tells the server, the server tells every active viewer "share ended", and the connection is torn down. No retention.

Source

Everything above is verifiable by reading the source. There's no minified JavaScript, no proprietary blob, no third-party SDK pulling in tracking. Pull the repo, read the code, run it yourself.

Licensing

The whole project ships under the Beerware License (Revision 42). In full:

"THE BEERWARE LICENSE" (Revision 42):
DubbaThony wrote this code. As long as you retain this
notice, you can do whatever you want with this stuff. If we
meet someday, and you think this stuff is worth it, you can
buy me a beer in return.

🍻

That's the entire license. Read it, agree to it, fork it. There is no Section 14(c), no patent grant carve-out, no "approved use case" clause. Use it, modify it, run your own instance, hand it to your friends. The only optional obligation is a beer, and it's contingent on meeting in person.