Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

perbrowser

One VPN per browser. Give a single browser its own WireGuard exit, with its own cookie jar, and DNS and WebRTC inside the tunnel. The rest of your Mac, and your other VPNs, stay untouched. No root, no kernel extensions.

brew install portdeveloper/tap/perbrowser
perbrowser add sweden ~/Downloads/se-sto-wg-001.conf --browser brave
# → launch "perbrowser - Sweden (Brave)" from Spotlight

Works with any WireGuard .conf that has a PrivateKey and an Endpoint: Mullvad, Proton, IVPN, self-hosted. This is not a Mullvad tool.

Why

macOS has no good app-level split tunneling:

  • VPN apps tunnel your whole machine. Their split tunneling is absent or exclude-only on macOS, and two VPN network extensions (say, Mullvad plus Tailscale) fight over routes and DNS.
  • Proxy extensions leak WebRTC, share your normal browser's cookies, and need a proxy endpoint you still have to build yourself.
  • VMs / separate user accounts work, but are heavyweight and awkward.

perbrowser takes the boring path: a userspace WireGuard client (wireproxy) exposes the tunnel as a SOCKS5 proxy on loopback. The tunnel is an ordinary user process making outbound UDP, so your routing table and network interfaces stay untouched. A dedicated browser profile points at it from the first launch, through command-line flags that a website can't undo.

How it works

normal apps ─────────────────► default route (Tailscale etc. untouched)

sweden ── wireproxy (launchd) ── SOCKS5 127.0.0.1:1080 ── WireGuard/UDP ─► 🇸🇪
us     ── wireproxy (launchd) ── SOCKS5 127.0.0.1:1081 ── WireGuard/UDP ─► 🇺🇸

"perbrowser - Sweden (Brave)".app
                    →  brave --user-data-dir=…/sweden/profile
                             --proxy-server=socks5://127.0.0.1:1080
                             --host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE 127.0.0.1"
                             --force-webrtc-ip-handling-policy=disable_non_proxied_udp

Each instance is fully independent: its own launchd-supervised tunnel process (restarts on failure and at login), its own port, its own browser profile, and its own Spotlight-launchable app in ~/Applications.

These three flags do the leak prevention:

Flag What it prevents
--proxy-server=socks5://… traffic bypassing the tunnel
--host-resolver-rules="MAP * ~NOTFOUND , …" DNS resolving locally, so names resolve through the tunnel
--force-webrtc-ip-handling-policy=disable_non_proxied_udp WebRTC voice/video exposing your real IP

Install

brew install portdeveloper/tap/perbrowser

Or without Homebrew, because it's a single POSIX shell script:

git clone https://github.com/portdeveloper/perbrowser && cd perbrowser
./perbrowser add …

The only binary dependency, wireproxy, is downloaded on first add from its pinned release and verified against a hardcoded SHA256 before it ever runs.

Requirements: macOS (Apple Silicon or Intel), a Chromium-family browser (Brave, Chrome, Edge, Chromium), and a WireGuard .conf from your provider (e.g. Mullvad: account page → WireGuard configuration → download).

Commands

perbrowser add <name> <wg.conf> [--browser brave|chrome|edge|chromium]
perbrowser list
perbrowser update-config <name> <new.conf>
perbrowser doctor [name]
perbrowser rm <name> [--keep-profile]
perbrowser version

add builds the whole stack (tunnel daemon, launchd agent, launcher app), then proves it works by fetching your exit IP through the proxy. If real traffic doesn't flow, it fails loudly.

list shows every instance with a live tunnel check:

NAME         BROWSER    PORT   AGENT       TUNNEL
sweden       brave      1080   running     185.65.135.1 SE
us           chrome     1081   running     170.62.100.18 US

update-config is for key rotation (e.g. you deleted a device on your provider's dashboard): swap in the new .conf, restart, re-verify. The whole swap takes about 15 seconds.

doctor checks each instance end to end: agent loaded, process running, port listening, file permissions, real traffic flowing, DNS through the tunnel, launcher flags intact. Safe fixes are applied automatically; destructive ones are printed for you to run. It exists because a rotated or deleted WireGuard key still logs handshakes, so every check ends in real traffic through the proxy.

Security posture

  • Your .conf (which contains a private key) is stored chmod 600 in a 700 directory. add and update-config offer to delete the source file after copying it; a non-interactive run prints a reminder instead.
  • wireproxy is version-pinned and SHA256-verified; it is never auto-updated silently. A bumped pin only takes effect on a fresh install, so delete ~/.local/bin/wireproxy to force a re-download.
  • The SOCKS5 listeners bind loopback only; nothing on your LAN can use your tunnels.
  • The whole tool is one auditable POSIX shell script, short enough to read before you run it.
  • It runs entirely as an unprivileged user process and never edits your system network config, so Tailscale and corp VPNs keep working.

Caveats

  • Voice/video calls in the VPN browser won't connect. WebRTC media is UDP, which this path does not carry. The WebRTC flag fails closed by design; it never falls back to your real IP. Everything over TCP works fine, including streaming video.
  • If your network blocks WireGuard UDP itself, the tunnel won't come up (add will tell you). You'd need your provider's obfuscation transport, which is out of scope here.
  • Providers cap WireGuard keys (Mullvad: 5 devices). Each downloaded conf is one device; revoke unused ones on their dashboard.

FAQ

Firefox? Out of scope for now. Firefox configures proxies through prefs, which needs a different and less tamper-proof mechanism. See CONTRIBUTING.

Linux? Planned, with the same design and systemd user units instead of launchd. Also in CONTRIBUTING.

Can I route other apps through a tunnel? Unofficially, yes: each instance is an ordinary SOCKS5 proxy on 127.0.0.1:<port>, and anything that speaks SOCKS5 can use it: curl --proxy socks5h://127.0.0.1:1080 …, Telegram's proxy setting, and so on. But perbrowser's leak guarantees (DNS through the tunnel, WebRTC failing closed, isolated state) come from the browser flags. An app you point at the port yourself may still resolve DNS outside the tunnel. The guarantees are scoped to the browser, which is where the flags apply.

Is my normal browser affected? No. The VPN instance uses its own --user-data-dir; your regular browser, its cookies, and its traffic are untouched.

How do I verify there are no leaks? In the VPN browser, visit browserleaks.com/ip. IP, DNS and WebRTC should all show the VPN exit, never your ISP.

Why not my VPN app's split tunneling? On macOS it usually doesn't exist, or it can only exclude apps from the tunnel. Either way it can't give the browser an isolated cookie jar.

Credit

perbrowser manages instances of wireproxy, which does the actual WireGuard-in-userspace work. This tool wires it to launchd and Chromium correctly, N times.

License

MIT

About

One VPN per browser: isolated WireGuard exit per browser on macOS.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages