Your packets are nobody's business but yours.
Wi-Fi is a shared medium. Cafes, hotels, dorms, LAN parties. Anyone on the same network can see your traffic. Dish was built with that in mind.
Threat model
Dish is built to stay safe on networks you don't fully trust:
- A roommate or neighbor on the same Wi-Fi shouldn't be able to inject controller inputs into your game.
- Someone running
tcpdumpon the router shouldn't see what you're playing or how you're playing it. - A captured packet shouldn't be replayable an hour later.
- Pairing should fail closed. Never a silent fall-back to plaintext.
- The configuration surface shouldn't be reachable from the LAN. Only the encrypted gamepad path is.
Pairing
The first time you connect Dish to Satellite, Satellite throws a 4-digit PIN up on your gaming PC. The PIN lives for five minutes, then expires. You type it into Dish on your phone, laptop, or other client device.
Pairing happens over HTTPS on port 9443, on a self-signed certificate Satellite generates locally. The transport protects against passive eavesdroppers; the PIN protects against anyone tricking Satellite into trusting them. The client sends a 32-byte X25519 public key, Satellite computes the shared secret with crypto_scalarmult, and from then on both ends hold a 256-bit symmetric key derived without ever sending it on the wire. The PIN itself is destroyed once pairing finishes.
The shared key is stored locally on both ends and used to authenticate every subsequent reconnect. On the Satellite side it lives in your platform's per-user config directory, mode-protected to your account (Windows DPAPI on V1); on the Android client side it lives in the app's private SharedPreferences, sandboxed by Android to the Dish app and excluded from cloud backup and device transfer. No key ever leaves either device.
Per-packet AEAD
Every controller packet (gamepad input, heartbeat, controller-add, rumble, motion, battery, touchpad, lightbar) is sealed with ChaCha20-Poly1305 (IETF), the same AEAD that TLS 1.3, WireGuard, and SSH use. ChaCha20 is fast and constant-time on every CPU we care about, including Android's ARMv8 and Apple Silicon.
The wire format is small and rigid: 4-byte session token, 4-byte counter, ciphertext, 16-byte Poly1305 authentication tag. The token doubles as additional authenticated data (AAD), so swapping it into a replayed packet causes verification to fail. The nonce is the counter zero-padded to 12 bytes, so no nonce reuse is possible without rolling the session.
Replay protection
Satellite stores the highest counter it has accepted on each connection. Any packet whose counter is less than or equal to that high-water mark is dropped before it ever reaches the gamepad backend. Captured ciphertext from yesterday's session, or even from one second ago, can't be re-played to trigger a button press.
What you don't have to worry about
- Eavesdropping. Nobody on the LAN learns which buttons you pressed.
- Spoofing. Without the session key, no attacker can inject a "B button" press, even if they know your IP.
- Replay. Yesterday's "fire" packet won't fire today. Neither will yesterday's heartbeat or pairing packet.
- Downgrade. No plaintext fallback. If crypto verification fails, the packet dies silently.
- Remote config tampering. The admin API (
localhost:9877) binds to127.0.0.1only. The LAN-facing HTTPS API on 9443 only exposes pair and session endpoints, all gated by a paired device ID.
Zero analytics, zero ads
Dish and Satellite collect no usage data, no identifiers, no analytics, no advertising IDs. The only outbound TinkerNorth-touched flow on Dish for Android is opt-out Firebase Crashlytics for crash and ANR reports: stack trace, device model, install UUID, and that's it. Never gamepad input, never Satellite IPs, never Wi-Fi SSIDs. There is a one-tap opt-out in the app's Settings under Diagnostics, honoured before any crash can be uploaded on the next start. Satellite itself ships with no crash-reporting SDK of any kind. The full scope is on the Dish for Android privacy policy.
We don't run an account system. There's no cloud. Nothing to breach because there's nothing on a server to leak.
Auditable supply chain
Every tagged release ships with:
- A SHA-256 checksum file for every artifact, signed keyless via
cosignand the OIDC identity of the GitHub Actions release workflow. - A SLSA Level 3 provenance attestation proving the artifact was built from the tagged commit in CI.
- A software bill of materials in both SPDX and CycloneDX formats, so you can audit every transitive dependency.
The verify-blob recipe lives in SECURITY.md in the Satellite repo. If a download doesn't match the published checksum, don't install it.
Open source and auditable
Every line of crypto-touching code lives in a public repo. Satellite uses libsodium; Dish for Android uses libsodium-stable built via the NDK and Android's standard Conscrypt for the HTTPS pairing transport. No homebrew crypto. No "we wrote our own ChaCha."
Distributed under the GNU Lesser General Public License v3.0 or later. Found something off? SECURITY.md in the Satellite repo has our disclosure policy.