Chrome has one command line flag that controls which addresses WebRTC may use: --webrtc-ip-handling-policy, and for a browser behind a proxy the value is disable_non_proxied_udp. Our launcher sent that flag and two others, --force-webrtc-ip-handling-policy and --webrtc-ip. The other two are not Chromium switches. Chromium took them without a word, and one of them was the entire implementation of a setting.
How we found out
The WebRTC settings had never been measured against a leak test with a proxy attached, so we did that. The protection itself worked. Everything around it was broken, and every fault pointed the same way: towards looking safer than it was.
The first question was whether the flags existed at all. Chromium accepts any switch you give it, known or not, and prints nothing, so a browser starting up proves nothing about its flags. The names are compiled into the binary, so we ran strings on three Chromium builds on the machine, our engine, another Chromium-based build and the machine's own Google Chrome, and counted:
webrtc-ip-handling-policy ours 1 other 2 Chrome 2
force-webrtc-ip-handling-policy ours 0 other 0 Chrome 0
webrtc-ip ours 0 other 0 Chrome 0
One real switch. The other two had been on every launch since the day they were written, doing nothing.
The one that was a whole feature
--force-webrtc-ip-handling-policy was a duplicate of the real one, so losing it cost nothing. --webrtc-ip was different. It was all there was behind the profile editor's "Replace with proxy IP" option, on every engine, for as long as the option had existed. A profile set to replace its WebRTC address with the proxy's had nothing replaced.
We did not build a replacement, because there is nothing for one to do. The public address WebRTC reports, the server-reflexive candidate, is whatever address the UDP packet came from. Behind a proxy that blocks non-proxied UDP no such candidate is gathered at all, and that, not a substituted address, is what protects a proxied profile.
Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. 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.
How it works: Machines that exist
The most protective option was the least protective
The editor had a third mode, disabled, labelled "No RTCPeerConnection at all". The deletion that delivered it lived in an injected script, and on our own engine the injected script is switched off entirely, because we change fingerprint values in C++ and not in page JavaScript. So disabled did nothing. Measured through the leak test, on our engine:
webrtc: 'disabled' 2 candidates: an mDNS host candidate and
a srflx candidate carrying the host's real public address
leak test fail: "set to disabled but still produced 2 candidates"
It was worse than inert. Choosing disabled also put WebRTC on the list of surfaces the engine stops changing, so it switched off the engine's own local-address claim. A profile on the safest-sounding setting was less protected than one on the default.
The honest version was one real switch away. One fresh profile directory per row, no proxy, the same probe in each:
ours Google Chrome
no policy RTC present, 2 RTC present, 2
disable_non_proxied_udp RTC present, 0 RTC present, 0
Zero candidates with RTCPeerConnection still present is what every computer on a network that blocks UDP looks like. A Chrome with no RTCPeerConnection at all is a browser that does not exist. So disabled now sends the policy, the script deletion is gone, and the leak test went from 2 candidates and a fail to 0 and a pass.
The leak report was flattering too
The leak test is the screen people trust instead of measuring, so a wrong line there is worse than no line. It had three, all reassuring or alarming in the wrong place. On a profile with no proxy it said "Only the proxy exit appeared in ICE candidates" about the machine's own address. It reported 0.0.0.0 as an exposed address, when 0.0.0.0 is what Chrome writes to withhold the local one, so the protection working raised a warning. And it told a profile with no proxy to "run a proxy check first". All three are fixed. The report is now covered by 15 assertions in the build, and each fix was proved by putting the fault back and watching the assertion quote the wrong sentence.
Which Chrome WebRTC command line flag to use
If you launch Chrome yourself behind a proxy, the flag is:
--webrtc-ip-handling-policy=disable_non_proxied_udp
Leave mDNS alone. The random .local name in a leak test is Chrome hiding your LAN address, and turning it off puts that address back in the candidate list. We once switched it off, and an unproxied profile came back with an empty SDP and no addresses at all, an answer no stock Chrome gives (what the .local address is). Send the policy only when a proxy is attached: without one, every packet is non-proxied, so it hides an address the site is already talking to and leaves only the oddity. And before trusting any switch, search the binary for its name, the way we should have the day these were written. A flag that changes the User-Agent has its own side effects on client hints, in a fingerprint that depended on launch order. WebRTC is not the only thing in Chrome that wants UDP; what happens to HTTP/3 behind a proxy is in what happens to QUIC when Chrome has a proxy.
In Mosaic the policy goes on every launch of a profile set to disabled, and on every launch with a proxy attached while the editor's "Block non-proxied UDP (WebRTC)" switch is on, which it is by default. Mosaic stops WebRTC from going around the proxy. It does not route WebRTC through it. The rest of what a profile reads, drawn from one real device, is on real fingerprints.






