privacy

Three WebRTC Modes and What Each One Shows a Website

Agilelogin's three WebRTC modes decide which ICE candidates a Profile exposes: what each shows, why privacy is default, and what none cover.

Updated 7 min read Agilelogin product team

Key takeaways

  • A page can open a WebRTC connection attempt without asking for camera or microphone permission, and the candidate list it collects can contain a real public IP even when the browser is proxied.
  • Agilelogin ships one per-Profile setting, webrtc.mode. The editor offers three modes: privacy, replace and disable. The kernel keeps two legacy values, forward_stun and allow, for existing Profiles and debugging; a new Profile cannot select them.
  • privacy has been the product default since 2026-07-27: no host or server-reflexive candidates leak, which matches what Chrome’s own enterprise policy calls disable_non_proxied_udp.
  • The editor refuses replace on a rotating proxy, and the legacy forward_stun is refused on a proxied Profile, because both would hand out a candidate list the network assignment can’t back up.
  • The mode is read once at launch and holds for the whole session; it doesn’t reach every WebRTC code path a page can use, and no mode hides the real address from a site that runs its own STUN server.

What a website learns before a page ever asks permission

WebRTC was built to let two browsers exchange audio, video, or data directly, without routing every packet through a server. To make that direct connection possible, each side has to tell the other how to reach it, and that negotiation — Interactive Connectivity Establishment, or ICE — runs the moment a page opens an RTCPeerConnection, before any camera or microphone permission prompt appears. A RTCPeerConnection with a single data channel is enough to trigger it, and a site doesn’t need the user to accept anything for that to happen.

ICE gathers what it calls candidates, and a page reading icecandidate events can collect all of them. A host candidate describes an address on the machine’s own network interface; modern Chromium hides the real one behind a randomly generated .local mDNS name instead of exposing a LAN IP directly. A server-reflexive (srflx) candidate is what a STUN server tells the browser its address looks like from the outside — normally the real public IP, discovered whether or not the browser is otherwise proxied, because STUN traffic doesn’t have to go through an HTTP proxy the way page requests do. A relay candidate, when TURN is involved, is an address on a relay server the traffic actually flows through. Put together, this is the well-known “WebRTC leak” path: a browser can be routing its HTTP traffic through a proxy while its ICE candidates still hand a site the underlying public IP.

Three modes and two legacy values, side by side

Agilelogin exposes exactly one setting that governs this, webrtc.mode, set per Profile. The editor offers three of its values: Privacy (privacy), Replace IP (replace) and Block connections (disable). The other two, allow and forward_stun, are legacy values the kernel keeps for existing Profiles and debugging. The editor shows one only on a Profile that already has it, so a new Profile cannot select either. The table lists all five kernel values:

ModeHost / mDNSServer-reflexiveRelay (TURN)What a site sees
privacy (default)nonenonepossible, only if the site offers TCP/TLS TURNproxy exit IP, or nothing
replace—rewritten to the proxy’s exit IP—proxy exit IP
disablenonenonenone, no TURN connections at allno candidates — the API still exists
allow (legacy)mDNS candidatereal public IP, if the site’s STUN server is reachable—real IP, via srflx
forward_stun (legacy)—real public IP—real IP, forced

allow is unmodified Chromium behavior: nothing about the candidate gathering is touched. disable goes furthest the other way — no candidates of any kind reach the page, and no TURN connection is ever opened, though the RTCPeerConnection object itself is still there for a script to construct; a page can’t tell the API is being held back from where it’s called. forward_stun and replace both target the srflx candidate specifically: the first lets the real address through, the second rewrites it to the Profile’s proxy exit IP before the page ever sees it.

Why privacy is the default

privacy sits between the two extremes by design, and it’s been the setting written explicitly into every Profile template since 2026-07-27. It suppresses both host and server-reflexive candidates outright — no mDNS name, no real IP via STUN — while still allowing a relay candidate to form if the site’s own TURN server happens to run over TCP or TLS, in which case the address a page sees is the proxy’s exit IP, not the machine’s. That’s not an invented behavior: it’s the same effect Chrome’s enterprise policy documents under WebRTCIPHandlingPolicy when set to disable_non_proxied_udp — Agilelogin’s default matches a mode Chrome itself already ships for managed deployments, applied per Profile instead of per machine.

The reasoning behind making it the default is consistency, not concealment. A Profile that’s already assigned a proxy for its HTTP traffic and its browser fingerprint should show a site the same network identity everywhere that identity is checked, WebRTC included — an operator running several Profiles side by side wants each one to look like one coherent, self-consistent environment, not one where the address bar says one country and a WebRTC candidate says another. That consistency matters just as much when a Profile is driven by an automation workflow rather than a person clicking around, since a script has no way to notice on its own that one signal quietly disagrees with the rest. That’s the property privacy protects, and it’s worth being precise about what it isn’t: it doesn’t disable WebRTC as a capability, and it doesn’t make a Profile’s network path look like a restricted or filtered one — it just stops the one specific leak path that host and server-reflexive candidates open.

Two combinations the editor refuses

The two values that touch the server-reflexive candidate each come with a guardrail. forward_stun exposes the real public IP on purpose; a new Profile cannot select it, and the Profile editor refuses it on a Profile with a proxy assigned; forcing a real IP out through WebRTC while every other signal comes from a proxy would just recreate the leak the rest of the setup exists to close.

replace has a narrower guardrail: it’s refused on a Profile using a rotating proxy. Rewriting the srflx candidate to “the proxy’s exit IP” only means something if that exit IP is a fixed, known value the whole session can be pinned to; a rotating proxy doesn’t have one. And even on a static proxy, if the kernel doesn’t yet know that proxy’s exit IP at the point ICE gathers candidates, replace falls back to behaving like privacy rather than emitting a stale or wrong address — an unrewritten guess is worse than no candidate at all.

What these modes do not cover

webrtc.mode is read once, over a private stdin channel, when the kernel launches a Profile — it can’t be changed while that browser instance keeps running, so a Profile that needs a different mode needs a restart, not a live toggle. For replace and forward_stun, the rewritten or forwarded values stay consistent across every place a page could go looking for them — RTCPeerConnection.getConfiguration(), icecandidateerror.url, and the candidate URLs surfaced in getStats() and iceTransport events all agree with what actually went out. That closes the obvious cross-check a page could otherwise run, but it has a hard limit: a site that runs its own STUN server sees the address each request actually arrived from, and can compare it with the candidate the page was handed. replace cannot change what that server observes. It is a privacy and consistency control for the page-side view, nothing more.

Two more limits are worth stating plainly rather than glossing over. IPv6 candidate behavior hasn’t been separately verified for any webrtc.mode value, so this post makes no claim about it either way. And these modes don’t govern every WebRTC-adjacent surface Chromium exposes — a newer RtcTransport API exists in the kernel that sits outside webrtc.mode’s reach entirely, though it isn’t exposed in release builds today, so it isn’t a path a page can reach yet. All of this is documented behavior of the Polychrome kernel, measured on its M150 build; there’s no public download of the desktop app to point at yet, so treat this as what the kernel does today, not as a feature to go try in a shipped release.