# Replace WebRTC IP with proxy IP: what can be done

> A WebRTC public address is what a STUN server saw a packet come from, so it cannot be swapped for the proxy's. What works is gathering no address outside it.

Source: https://mosaicbrowser.com/blog/replace-webrtc-ip-with-proxy-ip

## What "replace WebRTC IP with proxy IP" can actually mean

A WebRTC public address is what a STUN server saw a packet come from, so it cannot be swapped for the proxy's. What works is gathering no address outside it.

![The reflexive address is wherever the packet left from](https://mosaicbrowser.com/covers/replace-webrtc-ip-with-proxy-ip.webp)

"Replace WebRTC IP with proxy IP" is a setting several anti-detect browsers offer, and as worded it cannot exist. The public address in a WebRTC candidate is not a value the browser holds. It is what a STUN server on the internet saw when a UDP packet reached it. If that packet left from your own connection, the server saw your own address, and editing the text afterwards changes what the page is told, not what was observed. What a browser can honestly do is gather no address outside the proxy at all.

We say "cannot" because we shipped an option with exactly that label, and for as long as it existed it did nothing.

## Where the WebRTC public address comes from

A page that opens an `RTCPeerConnection` asks the browser for ICE candidates, the addresses another peer could use to reach it. There are three kinds that matter here. A host candidate is a local interface, which Chrome hides behind a random `.local` name (the subject of [the .local address in your WebRTC test](https://mosaicbrowser.com/blog/webrtc-local-address-mdns)). A server-reflexive candidate, written `srflx`, is the address a STUN server reports back after receiving a packet. A relay candidate is an address on a TURN server that forwards traffic.

The `srflx` row is the one people mean by "WebRTC leak". The browser sends a UDP packet to a STUN server, the server replies with the source address it saw, and the browser hands that address to the page. HTTP proxies carry TCP. Most SOCKS setups in a browser carry TCP too. So with no other rule in place, the UDP packet goes straight out of your network card, and the address that comes back is your home line, even while every page loads through a proxy in another country.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

How it works: Checked before every launch

## What a "replace" setting would have to do

A browser could catch the candidate on its way to the page and swap the address for the proxy's exit. Our objection to that is not taste. It fails on two counts.

The first is that the page does not need to believe the candidate. A site that wants the truth runs its own STUN server, and the server's log already has the real source address of the packet. The edited candidate then disagrees with the site's own record, and a disagreement is a stronger signal than a leak, because no ordinary browser produces one.

The second is that a rewrite done in page script is a patch on a JavaScript API, and those patches are findable in ways an engine change is not. We wrote up why we keep every value in C++ in [why we change fingerprints in C++, not JavaScript](https://mosaicbrowser.com/blog/cpp-not-javascript).

## What RFC 8828 says a browser may do

The IETF wrote down the options in RFC 8828, "WebRTC IP Address Handling Requirements". It describes four modes. Mode 1 gathers every interface. Mode 2 uses the default route plus its local address. Mode 3 uses the default route only. Mode 4, "force proxy", sends WebRTC through the configured proxy. Where the proxy cannot carry UDP, as with HTTP proxies and most SOCKS ones, the RFC has the browser give up UDP and use TCP through the proxy instead.

None of the four modes replaces anything. Mode 4 is the one that matches what people want from a proxied browser, and Chrome's spelling of it is a command-line switch: `--webrtc-ip-handling-policy=disable_non_proxied_udp`.

## Our own "Replace with proxy IP" option

The WebRTC menu in our profile editor had an option called "Replace with proxy IP". Its entire implementation was a launch flag, `--webrtc-ip`. When we finally measured the WebRTC row against a real leak test with a proxy attached, we ran `strings` on three engine binaries, ours, a third-party fork and the machine's Google Chrome, and counted the switch names:

```
webrtc-ip-handling-policy        ours 1   fork 2   stock 2
force-webrtc-ip-handling-policy  ours 0   fork 0   stock 0
webrtc-ip                        ours 0   fork 0   stock 0

```

`--webrtc-ip` exists in none of them. Chromium accepts an unknown switch without a word, so the option was taken in silence on every engine and replaced nothing. The full count of what was wrong, including a second flag that never existed, is in [two WebRTC flags we used never existed in Chromium](https://mosaicbrowser.com/blog/webrtc-flags-chromium-ignores).

Then we measured what actually stops the leak, on our own engine, with one fresh user data directory per row:

```
no policy                 RTCPeerConnection present, 2 candidates
                          (an mDNS host candidate and srflx, the host's real public address)
disable_non_proxied_udp   RTCPeerConnection present, 0 candidates

```

Zero candidates with the constructor still present is what a machine on a UDP-blocked network produces, so it reads as an ordinary network rather than as a browser with something removed ([a browser with no RTCPeerConnection doesn't exist](https://mosaicbrowser.com/blog/disabling-webrtc-is-a-tell)).

## What the option does now

The option kept its place and lost the promise. It is called **Confine to the proxy**, with the hint "No candidates escape a proxied profile". When a profile has a proxy attached, Tesseract Chrome, Mosaic's own Chromium, launches with `disable_non_proxied_udp`, through a privacy setting that is on by default. With no proxy, the policy is deliberately not sent: every packet would count as non-proxied, so it would hide an address the site is already talking to and add a signature for nothing. Chrome's mDNS hiding of the local address stays on, as in stock Chrome.

So the honest wording is that Mosaic stops WebRTC from going around the proxy. It does not route WebRTC through the proxy, and it does not put the proxy's address into a candidate. If you are checking another browser's version of this setting, run a leak test against a page that shows `srflx` rows, with a proxy attached, and look at what comes back. A real address there means the option is a label. No candidates means it works. A proxy address in a `srflx` row, from a proxy that carries no UDP, means something is being edited, and a site with its own STUN server can catch that.

## Questions

### Can WebRTC show my proxy's IP instead of my real one?

Not by editing the candidate. The public address in a WebRTC candidate is the one a STUN server saw the packet arrive from. If the packet left from your own connection, that server saw your own address, whatever the page is later told.

### What does disable_non_proxied_udp do in Chrome?

It stops WebRTC from sending UDP outside the configured proxy. Ordinary HTTP and SOCKS proxies carry no UDP for the browser, so in practice the page gathers no candidates that could carry your address. The RTCPeerConnection API stays in place.

### Is it suspicious if WebRTC returns no candidates?

Far less than a browser missing WebRTC. Zero candidates with the API present is also what any machine on a network that blocks UDP produces, while a Chrome without RTCPeerConnection is a browser nobody runs.

### Does Mosaic replace the WebRTC IP with the proxy IP?

No. Its option, named Confine to the proxy, makes the engine run with --webrtc-ip-handling-policy=disable_non_proxied_udp when a proxy is attached, so WebRTC cannot go around the proxy. Nothing is rewritten.

## Related

[Two WebRTC flags we used never existed in Chromium](https://mosaicbrowser.com/blog/webrtc-flags-chromium-ignores): Chrome has one WebRTC command line flag for leaks: --webrtc-ip-handling-policy. We also sent two that don't exist, and Chromium accepted both in silence.

[A browser with no RTCPeerConnection doesn't exist](https://mosaicbrowser.com/blog/disabling-webrtc-is-a-tell): Deleting WebRTC leaves a Chrome no one runs. Zero candidates with the API still present is every machine on a UDP-blocked network, so that is what we ship.

[The .local address in your WebRTC test is Chrome working](https://mosaicbrowser.com/blog/webrtc-local-address-mdns): A random .local name in a WebRTC leak test is Chrome hiding your LAN address. We once switched it off, and the empty result was the louder tell.

[What happens to QUIC when Chrome has a proxy](https://mosaicbrowser.com/blog/chrome-quic-through-a-proxy): With an HTTP, HTTPS or SOCKS5 proxy set, Chrome reaches sites over TCP tunnels, so a page that gets HTTP/3 direct gets HTTP/2 through the proxy.

[WebRTC and IPv6: the candidate your proxy never carried](https://mosaicbrowser.com/blog/webrtc-ipv6-candidates): A WebRTC IPv6 leak is a candidate gathered over UDP that an IPv4 proxy never sees. Blocking non-proxied UDP closes it for both address families at once.



[Why video calls struggle in a proxied profile](https://mosaicbrowser.com/blog/webrtc-calls-behind-a-proxy): WebRTC sends media over UDP, which an HTTP or SOCKS proxy in Chrome does not carry. With non-proxied UDP blocked, only a TURN relay over TCP is left.



## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
