wj-firewall
A macOS PF (Packet Filter) firewall configuration tool with both an interactive terminal UI and a command-line interface.
wj-firewall is not a firewall itself — it configures macOS's built-in PF packet filter. It makes it easy to block incoming traffic on specific network interfaces while keeping others open.
Why?
Many macOS services listen on 0.0.0.0, meaning they accept connections on every network interface. If you're on public Wi-Fi, anyone on the same network can reach those services. wj-firewall lets you block incoming traffic on untrusted interfaces (like Wi-Fi) while keeping trusted ones open (like Tailscale or Ethernet).
Features
- Interactive TUI — curses-based terminal UI for visual interface management
- CLI — scriptable command-line interface for automation and quick changes
- Per-interface blocking — block incoming traffic on selected interfaces
- Port exceptions — allow specific ports through on blocked interfaces (TCP + UDP)
- ICMP control — optionally block ICMP (ping, traceroute) on blocked interfaces
- PF anchors — rules stored in
/etc/pf.anchors/wj-firewall, surviving OS updates - Boot persistence — launch daemon re-enables PF at boot so rules survive reboots
- Clean removal —
removecommand undoes all PF modifications - Zero dependencies — Python 3.12+ stdlib only
Requirements
- Python 3.12+
- macOS (uses PF and pfctl)
- Administrator privileges (sudo) for applying firewall rules
Quick Start
Install
pip install wj-firewall
Or run directly with uv:
uvx wj-firewall
Launch the TUI
wj-firewall
Or use the CLI
wj-firewall status
wj-firewall block en0
wj-firewall allow en0 22
wj-firewall remove
See the Usage page for full details on both the TUI and CLI.
How It Works
wj-firewall uses macOS PF anchors to manage firewall rules. This approach has several advantages over editing pf.conf directly:
- Non-destructive — your existing
pf.confrules are untouched - Survives OS updates — the anchor file in
/etc/pf.anchors/is preserved - Clean separation — wj-firewall rules live in their own namespace
The tool writes rules to /etc/pf.anchors/wj-firewall and ensures pf.conf contains the anchor declaration and load rule. Reading configuration does not require sudo; only writing and reloading PF does.
For each blocked interface, PF rules are generated in this order:
- Pass outgoing — all outbound traffic and return traffic is allowed
- Pass DHCP — DHCP discovery is always allowed
- Pass ICMP — ping, traceroute, and PMTU discovery (unless ICMP is blocked)
- Pass ports — per-port exceptions (both TCP and UDP)
- Block incoming — everything else is silently dropped
All rules use quick (first-match-wins) and block drop (silent discard) to avoid RST/ICMP unreachable responses that can trigger macOS Private Relay to disable itself.