DEV Community

ricco020
ricco020

Posted on

Detecting WebRTC IP leaks in the browser: how it works and how to test it

WebRTC is a powerful browser API for real-time audio, video, and data communication. But there's a privacy trap hiding inside it: even when you're behind a VPN, WebRTC can expose your real IP address to any webpage that asks.

This is not a theoretical risk. It's actively exploited by fingerprinting services and leak-check tools. Let's dig into exactly why it happens and how to detect it with JavaScript.

Why WebRTC leaks your real IP

When WebRTC establishes a peer-to-peer connection, it uses a protocol called ICE (Interactive Connectivity Establishment). ICE gathers a list of candidates — potential network paths the connection can use. These candidates include:

  • Host candidates: your local network interfaces (LAN IP, e.g. 192.168.1.42)
  • Server reflexive candidates: your public IP as seen by a STUN server
  • Relay candidates: IP addresses from TURN relay servers

The leak happens at the STUN step. STUN (Session Traversal Utilities for NAT) is a protocol that asks an external server "what's my public IP?". WebRTC does this automatically, and it uses the OS network stack — bypassing your VPN tunnel entirely on many systems.

The result: a webpage can call RTCPeerConnection, trigger ICE gathering, and read your actual public IP from the STUN response — even if your browser routes all HTTP traffic through a VPN.

Browser (VPN active)
  │
  ├─► HTTPS request → goes through VPN tunnel → masked IP ✓
  │
  └─► RTCPeerConnection + STUN → direct UDP to stun.l.google.com → real IP ✗
Enter fullscreen mode Exit fullscreen mode

This is sometimes called a WebRTC leak and it affects Chrome, Firefox, Safari, and most Chromium-based browsers to varying degrees.

Detecting it with JavaScript

Here's a minimal snippet that collects all ICE candidates and extracts IP addresses from them:

async function detectWebRTCLeaks() {
  const ips = new Set();

  const pc = new RTCPeerConnection({
    iceServers: [
      { urls: "stun:stun.l.google.com:19302" },
      { urls: "stun:stun1.l.google.com:19302" },
    ],
  });

  // Create a dummy data channel to trigger ICE gathering
  pc.createDataChannel("");

  pc.onicecandidate = (event) => {
    if (!event.candidate) return;

    const candidate = event.candidate.candidate;
    // ICE candidate line looks like:
    // "candidate:... IP PORT typ host ..."
    const ipRegex =
      /(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b|[0-9a-f:]{2,39})/gi;
    const matches = candidate.match(ipRegex);
    if (matches) {
      matches.forEach((ip) => {
        // Filter out link-local and loopback
        if (!ip.startsWith("0.") && ip !== "0.0.0.0" && ip !== "127.0.0.1") {
          ips.add(ip);
        }
      });
    }
  };

  // Trigger ICE gathering
  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);

  // Wait for gathering to complete or timeout
  await new Promise((resolve) => {
    pc.onicegatheringstatechange = () => {
      if (pc.iceGatheringState === "complete") resolve();
    };
    setTimeout(resolve, 3000); // fallback timeout
  });

  pc.close();
  return Array.from(ips);
}

// Usage
detectWebRTCLeaks().then((ips) => {
  console.log("IPs found via WebRTC:", ips);
});
Enter fullscreen mode Exit fullscreen mode

Run this in your browser console while connected to a VPN. If you see an IP that isn't your VPN exit node, you have a leak.

What you'll typically find

Candidate type Example IP What it reveals
typ host 192.168.1.42 Local LAN address
typ srflx (server reflexive) 82.64.XX.XX Your real public IP
typ relay VPN relay IP Usually safe

The srflx candidates are the dangerous ones — they show your true public IP as seen from outside your network.

Parsing the SDP offer directly

An alternative approach reads the SDP blob directly instead of waiting for onicecandidate:

async function leakFromSDP() {
  const pc = new RTCPeerConnection({ iceServers: [] });
  pc.createDataChannel("");
  const offer = await pc.createOffer();
  pc.close();

  // SDP contains "c=IN IP4 X.X.X.X" or "a=candidate:..." lines
  const ipRegex = /\b(\d{1,3}(?:\.\d{1,3}){3})\b/g;
  const sdp = offer.sdp || "";
  const matches = [...sdp.matchAll(ipRegex)].map((m) => m[1]);
  return [...new Set(matches)].filter(
    (ip) => ip !== "0.0.0.0" && !ip.startsWith("127.")
  );
}
Enter fullscreen mode Exit fullscreen mode

Note: this only catches IPs already embedded in the SDP at offer creation time, which may miss some server-reflexive candidates gathered later.

How to prevent WebRTC leaks

Browser settings

Firefox gives you direct control:

  1. Open about:config
  2. Set media.peerconnection.enabled to false — this completely disables WebRTC
  3. Or set media.peerconnection.ice.default_address_only to true — forces WebRTC to use only the default route (your VPN interface)

Chrome / Chromium: There's no built-in setting to restrict WebRTC routing. Options:

  • Use an extension like uBlock Origin (has WebRTC leak protection option under Settings > Privacy)
  • Use WebRTC Network Limiter (official Chrome extension by Google)
  • Use a browser with better defaults (Brave blocks WebRTC leaks natively)

Brave: Navigate to brave://settings/privacy and set "WebRTC IP Handling Policy" to "Disable non-proxied UDP" — the most aggressive option.

At the application level

If you're building a web app and want to avoid exposing users' IPs during WebRTC calls, constrain the ICE policy:

const pc = new RTCPeerConnection({
  iceServers: [...],
  iceTransportPolicy: "relay", // Only use TURN relay candidates
});
Enter fullscreen mode Exit fullscreen mode

Using iceTransportPolicy: "relay" forces all traffic through your TURN server and prevents direct peer connections — no local or server-reflexive candidates are shared. The tradeoff is higher latency and server costs.

VPN-level mitigation

Some VPN clients include a WebRTC leak blocker at the network driver level. Check your VPN's settings. If your IP is still leaking after enabling it, or if you want to understand exactly what's exposed, a practical guide to securing an exposed IP walks through the full remediation steps including DNS leak checks and browser hardening.

Quick self-test checklist

Before shipping any privacy-sensitive web feature, run through this:

  • [ ] Open browser console with VPN active
  • [ ] Run the detectWebRTCLeaks() snippet above
  • [ ] Check if returned IPs match your VPN exit node or expose your real IP
  • [ ] Test in Chrome, Firefox, and Brave — behavior differs
  • [ ] Test with VPN disconnected as a baseline
  • [ ] If building WebRTC features: consider iceTransportPolicy: "relay" and document privacy implications for users

WebRTC leaks are one of the most overlooked privacy issues in browser development. They're easy to detect, and understanding the underlying ICE/STUN mechanism helps you make informed decisions — whether you're building a video chat app or just want to know what your VPN is actually hiding.

Top comments (0)