Windows 10 & 11
A WPF app that never elevates, and a LocalSystem service that owns the adapter, the routing table and DNS. They talk over a named pipe whose ACL admits the interactive user and SYSTEM, and nobody else.
Android & iOS
Flutter, using the platform's own VPN machinery — Android's
VpnService and iOS's Network Extension — because the
tunnel has to live in a process the OS controls. That is what makes
the consent dialog mean something.
Every client
Device key on first sign-in, fresh WireGuard keypair per connection, ticket redeemed against the node before anything is routed, and teardown at both ends on disconnect.
The three ways a VPN is quietly not a VPN
AllowedIPs = 0.0.0.0/0, ::/0 captures everything, and
then three things can still walk around it. All three are handled by
the Windows client from the start rather than treated as polish.
1 · DNS resolved outside the tunnel
Setting the adapter's DNS server is not enough — Windows will happily query the physical adapter's resolver in parallel. The client installs NRPT rules so all name resolution is forced through the tunnel.
2 · IPv6 escaping a v4-only node
If the node has no IPv6 and the client leaves v6 on the physical adapter, every dual-stack site bypasses the VPN entirely. IPv6 is routed into the tunnel and blackholed instead.
3 · A crashed tunnel leaking in the clear
The kill switch is a firewall filter that blocks all non-tunnel traffic while the tunnel is meant to be up, so a crash fails closed rather than silently reverting to your ISP. Off by default, one toggle to turn on.
Models generated from the contract, not transcribed
The master and each node publish an OpenAPI document, and the mobile client's Dart models are generated from that pair. Continuous integration regenerates them and fails on any diff.
Hand-writing forty records in a second language works right up until somebody adds a field — and then it fails silently, because the client deserializes what it knows and drops the rest.
- The sign-in form asks which server to use, above the credentials, so nobody types a password without seeing where it is going
- Only
httpsis accepted, and changing server clears the stored session — a refresh token minted by one master is a credential to hand a stranger - Each node's CA is pinned for that node alone, rather than added to the machine's roots where every public authority would inherit the trust
- A version skew between app and master answers with one clear error instead of crashing out of a button press
Ordering matters
The master claims an address and a concurrency slot when it issues a ticket. A client that gives up after a failed redemption without telling the master would lock its own user out until the lease lapsed — so it always says so.
Teardown at both ends
The node's teardown is what stops the traffic. The master's is what frees the slot if the node has died. Disconnect does both, which is why a crashed node never costs you your session limit.
What is written, and what has been run
This is a self-hosted platform, and knowing what has actually been exercised is part of deciding whether to run it.
Master & console
Complete Built and tested, including an end-to-end suite that runs the master and a node as real processes over real TLS.
Linux nodes
Complete Agent and data plane, tested end to end.
Windows nodes
Not yet executed Written and under test for everything that can be tested off Windows; the driver interop itself has not been run.
Windows client
Not yet executed Core, service and shell are written, and the cross-platform logic is tested. No installer yet.
Android & iOS
Not yet on a phone Built with a Dart test suite and tagged builds that produce installable APKs. iOS still needs its Network Extension target.
Get an account on this master
Register, and an administrator activates you. Until they do, the account exists and can connect to nothing.