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 =
/(bd{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 revealstyp 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:
- 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
});
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.
답글 남기기