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 ✗
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);
});
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.")
);
}
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:
- Open
about:config - Set
media.peerconnection.enabledtofalse— this completely disables WebRTC - Or set
media.peerconnection.ice.default_address_onlytotrue— 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
});
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)