Back to blog
NO.
009
DATE
Updated 2026-07-20
READ
~10 min
KIND
Guide
STATUS
Reviewed

TAGS: Projects

WebRTC Leak Check: Is Your Real IP Getting Out?

WebRTC can expose your real IP without a normal request, invisible in DevTools. The mechanism, test pages, and a console script you can paste and run.

WebRTC leakage is probably the most underestimated class of privacy exposure, because it does not look like a network request at all.

You assume that behind a proxy or VPN a page can only see your exit IP. But the moment a page uses WebRTC, the browser starts gathering a set of network candidates in the background to establish a peer connection — and those can carry your LAN address, or even your real public IP. The key detail: this negotiation is not a normal request, and the DevTools Network panel never shows it. Which is why most people have never noticed it, even people who otherwise care about privacy.

This is not a pitch for a solution — plenty already exist. It is an explanation, plus a self-check you can run yourself, so you can see a surface you have not been looking at.

How the leak happens

While negotiating, WebRTC collects ICE candidates in roughly three kinds:

  • host candidate: an address on a local network interface, usually your LAN address.
  • srflx candidate: the "public mapped address" as seen through a STUN server.
  • relay candidate: an address relayed through a TURN server, which only appears if you configured TURN explicitly.

The good news is that the situation is much tighter than a few years ago. Modern Chromium browsers (Chrome, Edge, Brave) replace the local address in host candidates with a random .local hostname by default (mDNS anonymization), and Firefox and Safari have their own mitigations. In the default state, plain LAN address leakage is largely suppressed.

Two traps remain.

Permissions change collection behavior. Once you grant a site camera or microphone permission, the browser may stop applying mDNS anonymization for it and hand over the real LAN address. Handling differs across browsers and versions, which is the usual reason for "my machine doesn't leak but yours does."

srflx can still expose your real public IP. srflx is not itself a vulnerability — on a direct connection it is your public IP, and there is nothing to hide. The problem appears when a proxy or VPN is configured incompletely: if you believe all traffic goes through the proxy, but WebRTC's STUN query is a UDP packet bypassing a browser proxy that only handles HTTP/TCP, then srflx carries your real public IP rather than the proxy's exit. The page sees the proxy's exit IP while WebRTC sees yours — the mismatch is the leak.

Worth repeating the part most easily missed: these negotiations are not fetch, not XHR, not document or resource requests, so the Network panel shows nothing (use chrome://webrtc-internals to see them). Being invisible is probably the main reason this has stayed underestimated.

How to check yourself

The check is cheap and requires installing nothing. Two routes: a test page, or a script pasted into the console.

Test in the browser and proxy/VPN configuration you actually use day to day, not in a clean environment — what you are verifying is whether your everyday setup leaks.

Using a test page

The two most direct:

  • browserleaks.com/webrtc
  • ipleak.net

On browserleaks.com/webrtc, read two fields:

  • Local IP Address (the host candidate): a hostname ending in .local means mDNS anonymization is working and the LAN address is hidden. A literal 192.168.x.x, 10.x.x.x, or 172.16–31.x.x means the local address is leaking — commonly because you once granted that site camera or microphone permission.
  • Public IP Address (srflx): compare it against the exit IP you believe you have. If you are behind a proxy or VPN and this shows your real public IP, WebRTC bypassed the tunnel.

ipleak.net works similarly: it lists the WebRTC-detected IP separately, to be compared with the "your IP" line at the top. A mismatch means something is leaking outside the expected path. Check both the IPv4 and IPv6 rows — IPv6 is routinely the one people forget.

If both fields show nothing at all (not a .local, not a LAN address — blank): do not immediately conclude the test page is broken. More likely your browser or an extension is blocking ICE candidate collection entirely, which is usually protection working. The test page simply cannot distinguish "nothing detected" from "detected nothing." The console section below shows which it is.

Using a console script

To see the raw output, or to check whether the test page is telling the truth, paste this into the browser console (F12 → Console) and press enter. It creates an RTCPeerConnection, collects candidates, and prints them one by one, usually within a few seconds:

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

pc.addEventListener("icecandidate", (e) => {
  if (!e.candidate) {
    console.log("— collection finished —");
    return;
  }
  // Modern browsers expose structured fields; older ones may return null, so fall back to the raw string
  const { type, address, protocol } = e.candidate;
  const line = address ? `${type}\t${protocol}\t${address}` : e.candidate.candidate;
  if (seen.has(line)) return;
  seen.add(line);
  console.log(line);
});

// A STUN server is required to see srflx (public mapped) candidates; a public one is used here as an example
pc.createDataChannel("probe");
pc.createOffer().then((offer) => pc.setLocalDescription(offer));

How to read the output:

  • The host line shows an xxxxxxxx.local hostname: mDNS is working, the LAN address is anonymized. This is the healthy state.
  • The host line shows 192.168.*, 10.*, or 172.16–31.*: your local address is leaking.
  • The srflx line's public IP does not match your proxy/VPN exit (it is your real public IP): WebRTC bypassed the tunnel.
  • No relay line: normal. No TURN server is configured in the example, and a self-check would never produce one. Do not read its absence as a fault.
  • host present, srflx missing: host candidates are generated purely locally and need no network. A missing srflx means UDP to the STUN server is blocked — common on restrictive networks or firewalls, or when the proxy only handles TCP. Try another network before concluding "no leak."
  • No host either, only "— collection finished —": this is not the same as the previous case and deserves more attention. Host candidates are generated locally without waiting for the network, so they normally appear almost immediately. If they do not, this is probably not a network problem but a browser or extension blocking ICE collection at an earlier layer — typical of extensions with WebRTC protection enabled (such as the uBlock Origin option below), Brave's Shields, or WebRTC being disabled in the browser. To confirm: open chrome://webrtc-internals (about:webrtc in Firefox) and look for records, or temporarily disable the suspect extension or Shields and run the script again. If candidates then appear, the protection was working and what you measured was good news. If a clean environment (private window with all extensions disabled) still produces nothing, then investigate whether WebRTC itself is switched off (for example Firefox's media.peerconnection.enabled set to false).

The script and the test page should agree — including when both are empty: if the page's fields are blank and the script prints only "— collection finished —," it is most likely the same cause (above), not two independent failures. When they disagree, trust the one you can reproduce by hand.

How to read your result

The same output means different things to different people:

Direct connection (no proxy or VPN): care about the host row. A .local hostname is fine; a LAN address means your local address is exposed, usually because some site holds camera or microphone permission. srflx equalling your real public IP is expected — and worth knowing that a page can obtain it through WebRTC without issuing a single ordinary request.

Proxy users (browser-level HTTP/SOCKS proxy): watch whether srflx matches the proxy exit. Browser-level HTTP proxies frequently do not handle UDP at all, so one STUN packet leaks your real public IP while the page still displays the proxy's. That "consistent on the surface, inconsistent underneath" case is the nastiest.

VPN users (system-level): a system tunnel usually carries STUN too, so srflx normally equals the VPN exit. Watch for three exceptions: split tunneling can route WebRTC outside the tunnel; a VPN covering only IPv4 while WebRTC obtains srflx over IPv6 exposes your real IPv6 directly; and there is a brief window of exposure during a reconnect. So beyond checking srflx, look specifically for unexpected IPv6 addresses.

Many people are mildly surprised the first time they run this — the thing they assumed was covered turns out to be half open. Seeing it is the point of this post.

Mitigation: mature options already exist

Once you confirm a leak, do not build your own. Pick one that fits your browser:

  • uBlock Origin: extension icon → gear in the panel (dashboard) → Settings tab → tick Prevent WebRTC from leaking local IP addresses → apply. The effect is exactly the case described above: host candidates stop appearing and the script prints only "— collection finished —". If you already have this on, measuring "nothing" is the expected result, not a bug.
  • Brave: Settings → Privacy and security → search "WebRTC" for the IP handling policy. Options include "default public interface only" (suppresses host while keeping srflx) and the stricter "disable non-proxied UDP" (any UDP not going through the proxy, STUN queries included, simply fails — again producing no detectable candidates).
  • Firefox: enter about:config, set media.peerconnection.enabled to false to disable WebRTC entirely (at the cost of anything that depends on it, such as in-browser video calls). To keep the feature while narrowing exposure, two settings suffice: media.peerconnection.ice.default_address_only set to true (expose only the default route address, removing extra exposure on multi-NIC machines) and media.peerconnection.ice.no_host set to true (do not generate host candidates at all).
  • Chromium enterprise policy: WebRTC IP handling can be pushed through group policy or managed configuration (field names have evolved — older documentation calls it WebRTCIPHandlingPolicy, and some cases are now covered by finer-grained policies such as WebRtcLocalIpsAllowedUrls). This targets managed IT fleets; individuals generally do not need it.
  • Some VPN clients: a built-in "WebRTC protection" toggle, which underneath is one of the above (blocking STUN egress at the system level, or injecting logic similar to the uBlock rule). After enabling it, do not trust the toggle alone — re-verify with a test page or the script. A switch existing does not guarantee it works on your browser version.

Two things actually matter: pick one mitigation you trust rather than stacking several (stacked, you cannot tell which layer is doing the work, and troubleshooting gets harder), and re-verify after changing anything. Seeing "host gone, script finishes instantly" is evidence the change took effect. Do not stop at the reassurance of having flipped a setting.

About NodePurity

I maintain a small tool called NodePurity — not a browser extension, but a local command-line audit script used before registering a long-lived account: check how "clean" the proxy/VPN exit node you plan to use actually is. It tests exit geography, ASN/operator type (residential and mobile IPs beat datacenter IPs), DNS resolver exit, whether IPv6 bypasses the tunnel, and how strongly Google/YouTube react. WebRTC/STUN leakage carries the highest weight: if the real public IP leaks through STUN, the node is rejected regardless of any other score — a single veto, because a leak there means the proxy might as well not exist. I have no intention of building the Nth WebRTC protection inside it; the mature options above are enough. NodePurity takes a different angle: it emits a report listing each deduction and its reason, rather than a vague "optimized."

The emphasis on report is deliberate, because a report matters more than a silent fix. A security or privacy tool that only tells you "optimized" is worth little; it should state what it did, what it did not manage, and where the remaining risk is, handing judgment back to you.

For the same reason I deliberately avoid phrases like "complete fingerprint protection," "100% anonymous," and "bypasses all detection." WebRTC is one entrance among many exposure surfaces — canvas, fonts, time zone, language, device information, and extension traces all participate in identification, and cleaning up WebRTC does not make an environment "clean." The honest description is: reduce the address exposure WebRTC causes, and make the result something you can verify yourself.

So what this post is really trying to leave you with is not a tool but a habit: spend two minutes checking, and find out whether the thing you assumed was covered actually is.

Comments →

CC BY-NC-SA 4.0

Comments

Comments are powered by GitHub Discussions. Sign in with GitHub to comment. Open the matching Discussion