Skip to content

About

Kill switch for macOS that works with any VPN app (WireGuard, OpenVPN, IKEv2…): the Mac reaches the internet only on networks you trust, through a VPN tunnel, or not at all. Uses the built-in pf firewall, a small Go daemon and a menu bar app. Install with Homebrew.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

Repository files navigation

EgressGuard

A kill switch for macOS that works with any VPN app. The Mac reaches the internet only through networks you trust, or through a VPN tunnel, or not at all. It is not tied to any VPN client: it lets out only what can leak nothing, the packets that go to a tunnel's server, and stays out of the tunnel's own interface.

                 packet leaving the Mac on a physical uplink (en0, en5, ...)
                                       |
                 is this uplink on a trusted network (router IP + router MAC,
                 confirmed by a fresh ARP check)?
                      |                                   |
                     yes                                  no
                      |                                   |
         passes, but only from the address      only to tunnel endpoints, plus DHCP,
         it had when the network was checked    IPv6 neighbour discovery and ping to
                                                the LAN; everything else is dropped

   VPN interfaces (utun*, ipsec*) and local ones (lo0, awdl0, bridge*, ...) are untouched.
  • Any VPN whose server it knows. WireGuard, IKEv2, OpenVPN or any other client with a tunnel mode: the switch only needs to know where the tunnel's server is. It takes the server of every VPN configuration in macOS by itself; any other server you add by hand. See Limits for VPNs that hide their server.
  • Trusted networks are your own routers, identified by router address and router MAC (as macOS identifies networks), optionally tied to an interface.
  • Nothing trusted is a valid mode: VPN only, even at home.
  • Quick off. egressguard off 15 or the menu bar shield, no password. It guards against leaks, not against its own user.
  • No Apple developer account needed. It uses the packet filter built into macOS (pf) and a root daemon in Go (standard library only). Homebrew builds it from source; nothing is notarized.

How it fails safe

  • Every change of network, sleep, wake, settings change or restart of the daemon first closes all uplinks and drops their pf states, then re-checks the network from scratch.
  • A trusted uplink may send only from the address it had when it was checked. On another network the address differs and the kernel drops the packets, even if the daemon is slow or dead.
  • The network is sealed before the Mac sleeps and stays sealed until a real wake.
  • pf being disabled by anyone is undone within a second.

The full design, its event list and its known gaps are in docs/design.md.

Install

Requires macOS 14 or newer. Homebrew builds it with Go and the Xcode Command Line Tools (xcode-select --install).

brew tap sentiens/tap
brew install egressguard
egressguard detect              # this network's router and MAC
sudo egressguard setup          # installs the root daemon and the menu bar app

setup copies the egressguard binary into /Library/Application Support/EgressGuard (root-owned: launchd never runs code from the Homebrew prefix, which your user can write to) and registers com.sentiens.egressguard with launchd, running egressguard daemon.

A first install leaves EgressGuard off. setup asks in the terminal whether to trust the network you are on and whether to turn EgressGuard on. If you say no, or run it without a terminal, the menu bar shield opens a setup window that asks the same. No network is trusted and nothing is turned on without your answer; until then the shield says Not set up yet.

It will not leave the Mac offline:

  • it needs the internet before it starts;
  • it dry-runs the decision and installs with the switch off if this network is neither trusted nor tunnelled;
  • it checks the internet with the switch on and turns the switch off if that check fails.

After brew upgrade egressguard, run sudo egressguard setup again: the running daemon is a copy and is not replaced by Homebrew. egressguard status warns when the two differ.

Use

egressguard                      status
egressguard off 15               off for 15 minutes, then on by itself
egressguard off                  off until turned on
egressguard on
egressguard trust-current [name] trust the network this Mac is on now
egressguard endpoints            every allowed tunnel endpoint and where it came from
egressguard detect               each uplink's router and MAC
egressguard test                 a few seconds with nothing trusted: a direct request must fail
sudo egressguard leaktest [seconds] [--safari]
                                 capture every packet leaving the uplinks while nothing is
                                 trusted, generate traffic and count what got past the rules

The menu bar shield shows the state and offers on and off (15 minutes, 1 hour, until turned on). Settings… edits:

  • trusted networks, with Trust this network;
  • the VPN servers the Mac may reach on other networks: those of the VPN configurations in macOS, listed with their configuration, and your own (ip:port, TCP or UDP), marked when a configuration already has them;
  • No trusted networks: VPN only, even at home.

The window writes ~/Library/Application Support/EgressGuard/settings.json; the daemon checks every entry and keeps the last good settings if the file is bad. An administrator can add entries that the window cannot remove in /Library/Application Support/EgressGuard/config.json (see config/config.example.json).

Check it on your Mac

sudo egressguard leaktest 30 --safari

With nothing trusted for 30 seconds it captures every packet leaving the physical uplinks while it makes requests (curl, ping, DNS, optionally Safari), and it fails on any packet that is not to a tunnel endpoint. Run it with the VPN off and on, at home and on a phone hotspot. The full test plan is in docs/testing.md.

Limits

  • Boot. Between boot and the daemon's start pf has no rules. Turn off auto-join for Wi-Fi networks other than your own.
  • Bridged VMs bypass pf. The status warns about bridged uplinks; use NAT mode.
  • The same address on another network cannot be told apart by the address pin; there safety rests on the daemon reacting to the link change.
  • pf is not an Apple-supported API for apps (TN3165). Some macOS releases let some Apple processes past pf; leaktest --safari checks yours.
  • Captive portals and LAN services on untrusted networks are blocked; turn the switch off for a portal.
  • VPNs that hide their server. Many VPN apps, commercial ones especially, keep their real server out of the macOS configuration, or pick one from a pool that changes. EgressGuard cannot see those servers: add them by hand, and expect no internet outside trusted networks when the app picks one you have not added. (0.1 and 0.2 could learn servers from a VPN app's connections at home; that only ever knew servers already used there, and 0.3 dropped it.)
  • Router MACs can be spoofed by someone on your local network.

Uninstall

sudo egressguard uninstall      # daemon, pf rules, menu bar login item
brew uninstall egressguard

Your settings.json stays for a later reinstall.

Develop

make               # build/egressguard and build/EgressGuard.app (ad-hoc signed)
make test          # gofmt, go vet, shellcheck, go test -race
./build/egressguard detect   # the CLI works from a checkout

The code is Go with no dependencies outside the standard library (cgo only for IOKit's sleep notifications and the clocks) and a small Swift menu bar app:

Path What it is
internal/guard the daemon: policy, observing the network, pf rules, the decision loop
internal/leak reading packet captures for leaktest
cmd/egressguard the CLI; egressguard daemon runs the daemon
app/ the menu bar app
scripts/ setup.sh and uninstall.sh, run through the CLI as root

See docs/design.md and docs/testing.md.

License

MIT, see LICENSE.

About

Kill switch for macOS that works with any VPN app (WireGuard, OpenVPN, IKEv2…): the Mac reaches the internet only on networks you trust, through a VPN tunnel, or not at all. Uses the built-in pf firewall, a small Go daemon and a menu bar app. Install with Homebrew.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages