Named for Ferris, Rust's unofficial crab mascot.
Lightweight Rust SOCKS5 proxy that binds outgoing connections to a specific network interface via macOS IP_BOUND_IF, enabling domain-based split tunneling where IP-based routing falls short.
Built for WireGuard on macOS, where the Network Extension intercepts all packets before the routing table — making traditional split tunneling (AllowedIPs, route add) unreliable for domain-based exclusions.
When running a VPN for security testing, bug bounty, or general privacy, certain sites block or restrict traffic from known VPN/datacenter IP ranges (CAPTCHAs, bot checks, degraded service). For sites like YouTube or Reddit, anonymization isn't a priority — and it may actually be preferable to keep personal traffic off VPN IPs that are associated with security testing. crabbyproxy lets you selectively route specific domains through your home connection while keeping everything else tunneled.
- Browser PAC file routes target domains to the local SOCKS5 proxy
- Proxy resolves DNS via DoH — bypasses VPN DNS, gets geo-correct CDN IPs
- Proxy binds outgoing sockets to the physical interface (
IP_BOUND_IF) - macOS honors the binding even with VPN active — traffic goes direct
- All other traffic goes through the VPN as normal
- Domain-based split tunneling — route by domain, not IP ranges
- DNS-over-HTTPS — Cloudflare, Google, and Quad9 with automatic fallback
- TTL-aware DNS cache — minimal DoH queries
- Interface auto-detection — finds en0/en6/en1 automatically
- Zero config WireGuard — no AllowedIPs changes, no PostUp/PostDown
- PAC file HTTP server — serves proxy.pac over HTTP on port 1081 (Chrome compatible)
- WireGuard watcher — detects when WireGuard becomes active, injects PAC into SCDynamicStore so Chrome picks it up automatically
- LaunchAgent — starts at login, auto-restarts on crash
- ~2MB binary — async Rust with tokio
git clone https://github.com/digital-shokunin/crabbyproxy.git
cd crabbyproxy
./install.shThis builds the binary, installs it to ~/.local/bin/, sets up default configs, and starts the LaunchAgent.
cargo build --release
cp target/release/crabbyproxy ~/.local/bin/
mkdir -p ~/.config/crabbyproxy
cp doh.conf.default ~/.config/crabbyproxy/doh.conf
cp proxy.pac ~/.config/crabbyproxy/proxy.pac
cp com.digisho.crabbyproxy.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.digisho.crabbyproxy.plistSet http://127.0.0.1:1081/proxy.pac as the automatic proxy configuration URL in every browser:
- Firefox: Settings > Network Settings > Automatic proxy configuration URL
- Chrome/Safari: System Settings > Network > Wi-Fi > Details > Proxies > Automatic Proxy Configuration
When WireGuard is active, crabbyproxy automatically injects the PAC URL into the system network state (SCDynamicStore) so Chrome/Safari pick it up without any manual configuration.
All settings live in ~/.config/crabbyproxy/config.toml:
[doh]
servers = [
"https://1.1.1.1/dns-query", # Cloudflare
"https://8.8.8.8/dns-query", # Google
"https://9.9.9.9:5053/dns-query", # Quad9
]
[proxy]
socks_port = 1080
pac_port = 1081
domains = [
"*.youtube.com",
"youtube.com",
"*.example.com", # add your domains here
]The PAC file is generated in memory from the domains list — no separate proxy.pac needed.
Restart after changes: launchctl kickstart -k gui/$(id -u)/com.digisho.crabbyproxy
WireGuard's AllowedIPs is IP-based, not domain-based. Services like YouTube use thousands of dynamic CDN IPs across dozens of subnets. Excluding them all creates hundreds of CIDR ranges that can break the tunnel. The macOS WireGuard app's Network Extension also intercepts packets before the routing table, making route add commands ineffective.
crabbyproxy operates at the application layer (browser proxy) instead of the network layer, sidestepping these limitations entirely.
launchctl bootout gui/$(id -u)/com.digisho.crabbyproxy
rm ~/.local/bin/crabbyproxy
rm ~/Library/LaunchAgents/com.digisho.crabbyproxy.plist
rm -rf ~/.config/crabbyproxy