Skip to content

KillianM00/leakcheck

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

leakcheck

A cross-platform VPN leak auditor that proves whether your tunnel actually does what your provider claims. Runs five independent checks (DNS, IPv6, routing, WebRTC, killswitch) and emits a structured, machine-readable report with plain-English error codes mapped to concrete fixes.

License: MIT Python 3.9+ Platform Tests

Most consumer VPNs claim to block DNS leaks, tunnel IPv6, and ship a working killswitch. Many of them silently don't. leakcheck proves it one way or the other in about thirty seconds, no root required.


What you get

═══ leakcheck report ═══

  baseline (assumed VPN exit):
    IPv4: 185.213.155.74

  [PASS ]  DNS leak                      [DNS-PASS]  DNS looks clean.
  [PASS ]  IPv6 leak                     [V6-PASS]   No IPv6 leak.
  [FAIL ]  Routing leak                  [RT-002]    Some destinations are routed AROUND the VPN.
           why it matters: Most traffic goes through the tunnel, but a 'split-tunnel'
                           rule is carving out specific addresses so they bypass it.
           what to do:     Open your VPN client and check for a split-tunnel feature; disable
                           it. If you didn't configure it yourself, malware may have set it.
           · exceptions: 1.2.3.4/32 → Wi-Fi
  [PASS ]  WebRTC leak                   [WRTC-PASS] WebRTC isn't leaking.
  [PASS ]  Killswitch / disconnect leak  [KS-PASS]   Killswitch held during the disconnect.

  ✗ 1 leak(s) detected, 0 errored, 4/5 pass, 0 skipped

Every finding carries a short error code, a one-sentence headline a non-expert can act on, and the specific fix.


Install

Audience Best for How
End users (Windows) Non-technical users. Double-click installer, Start Menu shortcut, Desktop icon, clean uninstall. Download leakcheck-setup-X.Y.Z.exe from the Releases page and run it.
CLI / CI Servers, scripting, automated security gates. pip install leakcheck
Desktop GUI (cross-platform) macOS / Linux desktops wanting the visual app. pip install leakcheck[gui]leakcheck-gui

All five checks work out of the box. No browser bundles, no playwright install, no apt install of system packages.


Quickstart

CLI:

leakcheck                                  # run all five checks against the active connection
leakcheck --json                           # structured output for CI / automation
leakcheck --check dns,routing              # subset
leakcheck --skip disconnect                # skip the interactive killswitch test
leakcheck --vpn-iface "MyCustomVPN"        # tell leakcheck what your tunnel iface is called
leakcheck --expected-resolver 10.8.0.1     # flag a DNS leak if the resolver differs
leakcheck --non-interactive                # CI-friendly; no prompts

Exit codes: 0 clean, 1 at least one leak, 2 at least one inconclusive (errored) check with no confirmed leaks.

GUI:

leakcheck-gui

One-click audit, live per-check progress, JSON export, copy-output button. Settings panel pins expected IPs/resolver/interface name when you want to be strict.


Error code reference

Every finding maps to a code. Codes are stable and indexable, suited for runbooks, alerting, and triage.

DNS

Code Meaning
DNS-001 Resolver is your consumer ISP (Comcast / Cox / AT&T / Verizon / CenturyLink / Charter / Spectrum / ...). Your ISP can see every website you visit.
DNS-002 Resolver is unrecognized and not co-located with the VPN. Whoever runs it sees your queries.
DNS-003 All three resolver probes failed; check is inconclusive.
DNS-PASS Resolver is a known privacy provider (Cloudflare / Google / Quad9 / OpenDNS / AdGuard / ControlD / NextDNS), or lives inside the tunnel.

Routing

Code Meaning
RT-001 Default route bypasses the VPN entirely. All traffic leaks.
RT-002 Default route OK, but split-tunnel rules carve specific destinations around the VPN.
RT-003 No default route exists; the host has no internet path.
RT-PASS Default route correctly tunneled, no leaking exceptions.

IPv6

Code Meaning
V6-001 Real IPv6 address visible despite VPN. (Many VPNs are IPv4-only by default; v6 traffic escapes.)
V6-PASS Host has no v6 connectivity, or v6 is properly tunneled.

WebRTC

Code Meaning
WRTC-001 STUN reflexive IPv4 ≠ VPN exit. A web page using RTCPeerConnection can read your real public IPv4.
WRTC-002 Real IPv6 exposed via WebRTC (host-global v6 address visible).
WRTC-PASS Reflexive IPs match the VPN exit; nothing useful for a browser to leak.

Killswitch

Code Meaning
KS-001 When the VPN dropped, traffic kept flowing through the underlying ISP. Killswitch failed.
KS-INCONCLUSIVE Test ran but no actual disconnect was observed. Re-run and disconnect mid-window.
KS-PASS During the observed drop, traffic was blocked. Killswitch held.

Full lookup table with "why it matters" and "what to do" lines lives in src/leakcheck/codes.py.


How each check works

DNS leak detection

Issues three independent DNS probes that ask third parties to report which resolver is making queries on the host's behalf:

  • o-o.myaddr.l.google.com TXT (Google)
  • whoami.cloudflare TXT via 1.0.0.1 (Cloudflare)
  • whoami.akamai.net A (Akamai)

Majority-votes the answer. Classifies the egress resolver against (a) a user-supplied --expected-resolver, (b) an allowlist of known privacy providers (Cloudflare / Google / Quad9 / OpenDNS / AdGuard / ControlD / NextDNS), (c) a deny-list of consumer-ISP IP prefixes, or (d) co-location with the VPN exit on the same /16. Anything outside those classes is flagged.

Routing leak detection

Reads the OS routing table directly:

  • Linuxip -4 route show
  • macOSnetstat -nr -f inet
  • WindowsGet-NetRoute (PowerShell, gives clean interface aliases) with a route print -4 fallback

A correctly tunneled host has its 0.0.0.0/0 default route pointing at the VPN's interface. leakcheck decides whether that interface IS the tunnel through three signals:

  1. Explicit override (--vpn-iface NAME) — the user named the tunnel.
  2. Name match — substring lookup against a list covering Mullvad, NordVPN, ProtonVPN, ExpressVPN, Surfshark, CyberGhost, TunnelBear, Windscribe, IVPN, PIA, AtlasVPN, Hide.me, Tailscale, plus generic vpn / tunnel / wireguard / openvpn substrings.
  3. Structural signature — an interface with a /32 host route to an internet-routable IP via a different interface (the WireGuard / OpenVPN / IPSec bootstrap underlay pattern). Catches any custom tunnel by shape, not name.

Once the tunnel is identified, the routing table is scanned for split-tunnel exceptions: routes to internet-routable destinations (RFC1918, link-local, multicast, loopback, broadcast, and CGNAT explicitly excluded) that go via a non-tunnel interface. The VPN's own underlay route (a /32 to the peer endpoint co-located with the exit IP) is also excluded automatically.

IPv6 leak detection

Forces AF_INET6-only HTTP requests to three echo endpoints (api64.ipify.org, ipv6.icanhazip.com, v6.ident.me). If any response arrives and the VPN was declared IPv4-only (default assumption), the returned IPv6 is the host's real address, escaping the tunnel. A host with no IPv6 connectivity at all passes trivially.

WebRTC leak detection

Pure-Python STUN (RFC 5389) implementation — no headless browser, no Chromium download. Sends real binding requests over UDP to multiple public STUN servers (Google, Cloudflare, Nextcloud) over IPv4 and IPv6 separately, parses the XOR-MAPPED-ADDRESS attribute from each response. That's exactly the reflexive address a browser's RTCPeerConnection would expose. Then enumerates the host's own global IPv6 addresses — the other major WebRTC leak vector that browsers happily expose to any page.

Any reflexive address that doesn't match the VPN exit, or any host-side global IPv6 when the VPN is supposed to be tunneling everything, is flagged.

Killswitch validation

Polls a public-IP echo service every 500 ms for a configurable window (default 30 s). The operator disconnects the VPN mid-window. Three outcomes:

  • PASS — some samples come back silent (VPN was down) and zero samples report a non-VPN IP. The killswitch held the connection closed.
  • FAIL — at least one sample during the window returned a non-VPN IP. Traffic leaked through the gap.
  • INCONCLUSIVE — every sample reported the VPN IP. Either the killswitch is perfect and the user actually never disconnected; can't distinguish from this side. Re-run.

--non-interactive records the window without a prompt, suitable for CI.


Architecture

   ┌──────────┐
   │ baseline │ ← collect public IPv4/IPv6 via 3 redundant echo services
   └────┬─────┘
        │
        ▼
   ┌──────────────────────────────────────────────────────┐
   │ five independent checks, each emits a CheckResult    │
   │ {name, title, status, code, summary, details, time}  │
   │                                                      │
   │  ┌────┐ ┌──────┐ ┌─────────┐ ┌────────┐ ┌───────────┐│
   │  │DNS │ │ IPv6 │ │ Routing │ │ WebRTC │ │ Killswitch││
   │  └────┘ └──────┘ └─────────┘ └────────┘ └───────────┘│
   └────────────────────────┬─────────────────────────────┘
                            │
              ┌─────────────┴──────────────┐
              ▼                            ▼
        codes.py lookup              JSON / terminal renderer
        (code → headline,            (color glyphs, error code,
         meaning, fix)                headline, what-to-do, details)

Every check is a pure function. Network-touching code always probes three independent sources and majority-votes. Each check fails gracefully when prerequisites are missing — no exceptions reach the top level.


Building the Windows installer from source

# Python 3.9+ on PATH
pip install -e ".[gui,build]"
python build_exe.py                        # → dist\leakcheck-gui.exe  (~38 MB, single file)

# Compile the install wizard (requires Inno Setup 6+ from https://jrsoftware.org/isdl.php)
iscc installer\leakcheck.iss               # → installer\output\leakcheck-setup-0.1.0.exe

The installer:

  • Installs to %ProgramFiles%\leakcheck\ (requests admin)
  • Adds a Start Menu shortcut (always) and Desktop shortcut (opt-in)
  • Optional: prepend the install dir to system PATH so leakcheck-gui runs from any terminal (opt-in)
  • Registers a clean uninstaller in Apps & Features
  • Shows the MIT LICENSE page during the wizard

Testing

pytest -q

24 tests, no network dependencies for the deterministic checks. STUN tests use a synthetic packet round-trip; one live-network test asserts the WebRTC check completes (PASS or ERROR — never SKIP).


What it isn't

  • Not a VPN. It audits a VPN you've already set up.
  • Not a packet capture tool. No root required for the detection logic.
  • Not magic. A malicious VPN provider forwarding your DNS through its own infrastructure can produce a clean report — the resolver IP looks fine because, technically, it is fine. Tool detects routing & leak shape, not trust.

License

MIT — see LICENSE.

Contact

Killian Miller — killianmiller6@gmail.comgithub.com/KillianM00

About

Cross-platform VPN leak auditor — five checks (DNS, IPv6, routing, WebRTC, killswitch) with plain-English error codes mapped to concrete fixes. CLI + GUI + Windows installer.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors