Repository navigation
What is in this release
Every artefact is built from this tree and its checksums are in SHA256SUMS.txt.
Security
Command injection through the phone pairing token, on all three platforms. A phone that had
paired - six digits, then a 256-bit token - could have arbitrary commands run as administrator on
the PC. The proxy address it sent was interpolated straight into a PowerShell script that ran
elevated. The daemon had the same shape on Linux and macOS, and its HTTP API needs no
authentication at all, so there the value came from anyone on the network.
Fixed by validating that a proxy address can only be an address, and by passing the value to
the script as an environment variable rather than interpolating it, so PowerShell cannot re-parse
it as code. The daemon passes one argument per element now, and every interface name, resolver
address and service name is validated first.
Every machine-rooted path in the repository is gone - 24 of them. Two were constants in
shipped code, and both silently disabled a feature on every install except the developer's: the
flag marking an active share pointed at a directory that exists on one machine, so the recovery
that restores your own network settings after a crash never ran; and the file the CI installer
job reads could not exist anywhere but that machine.
The pairing token is encrypted at rest with a key in the Android Keystore. It was a bare
string in a JSON file, which is readable by anything on a rooted phone, by any code running under
the app's UID, and by an adb backup. Existing installs keep working and are re-sealed on first
load.
The LAN proxy caps concurrent connections. It binds 0.0.0.0 and held each for up to 20 seconds,
so a few hundred idle opens from one host exhausted the phone's file descriptors and took the
tunnel with them. No credentials were needed.
Correctness
- The tunnel now answers every DNS query in a segment, not just the first. Pipelining is legal
over TCP and is what the length prefix exists to support; the second query used to sit
unanswered, with the client already ACKed, waiting on a resolver that was working correctly. - DNS replies are matched against the transaction id. Any packet arriving on the socket used to be
accepted as the answer, including a spoofed one from anywhere on the path. - A TCP flow's receive buffer is bounded. A client that never acknowledged anything grew it until
the phone ran out of memory. - The Android foreground service uses the right type, and the tunnel reader closes its file
descriptors on every exit path. - Settings are fsync'd before and after the rename, and a failed write is logged instead of
swallowed.
Verification
The injection fix is measured end to end, not just unit tested. The pre-fix script shape prints
OK and writes a marker file; the shipped binary refuses the same payload with
mv_bad_proxy and writes nothing. Tools/Test-Injection.ps1 drives the real executable over
the real HTTP API.
125 Windows, 107 Core, 82 Daemon and 177 Android tests pass, plus lint.
Not verified
The Linux and macOS GUI binaries have never been rendered - there is no emulator and no
device available - so those are in the separate v1.2.3-linux candidate release rather than
here. The daemon is covered by CI on real ubuntu-24.04 and macos-14 runners.
Android signing - read this before uploading to Play
The APK here is signed with the same key as 1.2.0, which is what lets it install as an
update over it. A differently-signed build is rejected by the store, so this is required rather
than preferred.
That key is also the one that was briefly committed to this public repository. It is still
downloadable by its blob id, so treat it as compromised - but that does not affect this upload,
because Play rejects the update either way until the key is rotated properly.
In order:
- Check Play Console -> Test and release -> Setup -> App integrity -> App signing.
- A Google-held signing certificate means Play App Signing is on and you are protected. The
leaked key can only produce an upload, not something installable. Go to step 3. - Your own certificate means it is not on. The exposure is live; go to step 2.
- A Google-held signing certificate means Play App Signing is on and you are protected. The
- Enable Play App Signing. Google verifies you hold the current key and retains it for exactly
this situation. - Replace the upload key with the fresh one already generated
(netpilot-release-2026.jks, 3072-bit, random 24-character password). Once Play holds the
signing key, the upload key cannot install anything on its own, so rotating it is free.
Full detail, including the working blob URL, is in docs/KEYSTORE-ROTATION.md.
An .aab built and signed with the 1.2.0 key is also in the myket/ folder - Play has required
bundles for new apps and updates since August 2021.