# Chrome WebRTC command line flags: the one that exists

> 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.

Source: https://mosaicbrowser.com/blog/webrtc-flags-chromium-ignores

## Two WebRTC flags we used never existed in Chromium

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.

![1 of 3: Three flags on the command line, one in the binary](https://mosaicbrowser.com/covers/webrtc-flags-chromium-ignores.webp)

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.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## 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](https://mosaicbrowser.com/blog/webrtc-local-address-mdns)). 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](https://mosaicbrowser.com/blog/user-agent-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](https://mosaicbrowser.com/blog/chrome-quic-through-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](https://mosaicbrowser.com/features/real-fingerprints).

## Questions

### What is the Chrome command line flag to stop WebRTC leaking my IP?

--webrtc-ip-handling-policy. With the value disable_non_proxied_udp, WebRTC sends no UDP that does not go through the proxy, so a page cannot gather a candidate carrying your real address around it.

### Does --force-webrtc-ip-handling-policy work?

No. It is not a Chromium switch. We searched three Chromium-based binaries with strings and found no such name in any of them; Chrome accepts it without a warning and does nothing.

### How do I check whether a Chrome flag exists?

Search the binary for the name. On a Mac, strings on the Google Chrome Framework file and a grep for the switch name tells you whether the browser contains it. An unknown switch is accepted silently, so the browser starting is not evidence.

### Is disabling RTCPeerConnection better than blocking non-proxied UDP?

No. A Chrome without RTCPeerConnection is a browser that does not exist. A Chrome whose WebRTC gathers no candidates looks like any machine on a network that blocks UDP.

## Related

[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.

[A fingerprint that depended on launch order](https://mosaicbrowser.com/blog/user-agent-launch-order): Chrome's --user-agent flag left Sec-CH-UA-Bitness empty, and a cache empty at every app start decided when we sent it. One profile wore two identities.

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.

[The RTT client hint, read off the wire](https://mosaicbrowser.com/blog/rtt-on-the-wire): ECT, RTT and Downlink headers behind a proxy, measured: a claimed round trip moved JavaScript to 300 and left the RTT header at 50. So we claim neither.

[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.

[Same IPHey score, opposite reasons](https://mosaicbrowser.com/blog/iphey-same-score-different-signal): IPHey Unreliable on a normal Chrome is usually the exit. Our profile and a stock Chrome both got MX 90, for a browser signal and a location signal.

[Why IPHey's location card can be greener on a profile than on Chrome](https://mosaicbrowser.com/blog/iphey-location-card): On one US exit in one minute, stock Chrome got IPHey's masked or inconsistent location data and our profile got green. The difference was the timezone.

## 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.
