privacy

三種 WebRTC 模式,網站分別能看到什麼

Agilelogin 三種 WebRTC 模式決定 Profile 暴露哪些 ICE 候選:各模式顯示什麼、為何 privacy 預設,以及不涵蓋什麼。

更新於 7 分鐘閱讀 Agilelogin product team

要點

  • 網頁不需要請求攝影機或麥克風權限,就能發起一次 WebRTC 連線嘗試,而它收集到的候選位址清單,即使瀏覽器已經走了代理,也可能包含真實公網 IP。
  • Agilelogin 提供一個逐 Profile 生效的設定 webrtc.mode。編輯器提供三種模式:privacy、replace 和 disable。核心為既有 Profile 和除錯保留兩個舊值 forward_stun 和 allow;新建的 Profile 無法選擇它們。
  • 自 2026-07-27 起,privacy 一直是產品預設值:不會洩露 host 候選或 server-reflexive 候選,這與 Chrome 自身企業政策中的 disable_non_proxied_udp 效果一致。
  • 編輯器會拒絕在輪換代理上使用 replace,舊值 forward_stun 也不能用在已配置代理的 Profile 上,因為這兩種組合給出的候選位址清單都撐不住背後的網路配置。
  • 該模式在啟動時讀取一次,整個工作階段期間保持不變;它並不涵蓋網頁可以使用的每一條 WebRTC 程式路徑,對於自己架設 STUN 伺服器的網站,沒有任何一種模式能隱藏真實位址。

網頁在你同意之前就能得知什麼

WebRTC 最初是為了讓兩個瀏覽器之間直接交換音訊、視訊或資料而設計的,不必讓每個封包都繞經伺服器。為了讓這種直連成為可能,雙方需要告訴對方如何聯繫到自己,而這套協商──也就是互動連通性建立(ICE)──會在網頁開啟一個 RTCPeerConnection 的那一刻就開始運作,早於任何攝影機或麥克風權限提示出現之前。哪怕只是一個只帶資料通道的 RTCPeerConnection,也足以觸發它,網站根本不需要使用者同意任何事。

ICE 會收集所謂的候選位址,監聽 icecandidate 事件的網頁可以把它們全部蒐集起來。host 候選描述的是本機網路介面上的一個位址;現代 Chromium 會用一個隨機產生的 .local mDNS 名稱取代直接暴露區網 IP。server-reflexive(srflx)候選,是 STUN 伺服器告訴瀏覽器「從外部看你的位址是什麼」──通常就是真實公網 IP,無論瀏覽器其他流量是否走了代理都會被發現,因為 STUN 流量不必像網頁請求那樣經過 HTTP 代理。而 relay 候選,在涉及 TURN 時,是流量實際經過的中繼伺服器上的一個位址。合起來看,這正是眾所皆知的「WebRTC 洩漏」路徑:瀏覽器的 HTTP 流量可能正走著代理,而它的 ICE 候選位址卻仍然把底層的公網 IP 交給了網站。

三種模式與兩個舊值並排比較

Agilelogin 只提供一個設定來管理這一切,也就是逐 Profile 生效的 webrtc.mode。編輯器提供其中三個值:Privacy(privacy)、Replace IP(replace)和 Block connections(disable)。另外兩個 allow 和 forward_stun 是核心為既有 Profile 和除錯保留的舊值:編輯器只在 Profile 目前已是該值時才顯示它,所以新建的 Profile 無法選擇。下表列出核心的全部五個值:

模式Host / mDNSServer-reflexiveRelay (TURN)網站看到什麼
privacy(預設)無無僅當網站提供 TCP/TLS TURN 時才可能出現代理出口 IP,或什麼都沒有
replace—改寫為代理出口 IP—代理出口 IP
disable無無無,完全不建立 TURN 連線沒有任何候選位址──API 本身仍然存在
allow(舊值)mDNS 候選真實公網 IP(若網站的 STUN 伺服器可達)—真實 IP,經由 srflx
forward_stun(舊值)—真實公網 IP—真實 IP,被強制暴露

allow 就是未經改動的 Chromium 原生行為,候選位址收集的任何環節都不會被觸碰。disable 走向另一個極端──沒有任何類型的候選位址會到達網頁,也不會建立任何 TURN 連線,儘管 RTCPeerConnection 物件本身仍然存在,指令碼照樣可以建構它;網頁無法從呼叫方式上看出這個 API 正被刻意壓制。forward_stun 和 replace 都專門針對 srflx 候選下手:前者放行真實位址,後者會在網頁看到之前,把位址改寫成該 Profile 的代理出口 IP。

為什麼 privacy 是預設值

privacy 之所以處在兩個極端之間,是有意為之──自 2026-07-27 起,它就被明確寫進了每一個 Profile 範本。它徹底壓制 host 和 server-reflexive 兩類候選──沒有 mDNS 名稱,也不會經由 STUN 暴露真實 IP──同時如果網站自己的 TURN 伺服器恰好跑在 TCP 或 TLS 上,仍然允許出現一個 relay 候選,此時網頁看到的位址是代理的出口 IP,而不是本機位址。這並非憑空發明的行為:它和 Chrome 企業政策中 WebRTCIPHandlingPolicy 設為 disable_non_proxied_udp 時的效果完全一致──Agilelogin 的預設值,對應的正是 Chrome 自己已經為受管理部署提供的一種模式,只是把它下放到了每個 Profile 而不是每台機器上。

把它設為預設值,考慮的是一致性,而不是隱匿。一個已經為其 HTTP 流量與瀏覽器指紋配置了代理的 Profile,理應在每一個會被檢查身分的地方都向網站展示同一個網路身分,WebRTC 也不例外──同時執行多個 Profile 的操作者,希望每一個都呈現為一個連貫、自洽的環境,而不是網址列顯示一個國家、WebRTC 候選位址卻顯示另一個國家。當 Profile 由自動化工作流程驅動而非由人工點選操作時,這種一致性同樣重要,因為指令碼沒有辦法自己察覺到某一個訊號正悄悄地和其餘訊號不一致。

編輯器拒絕的兩種組合

會觸及 server-reflexive 候選的兩個值,各自都配有防護機制。forward_stun 是刻意暴露真實公網 IP,新建的 Profile 無法選擇它,Profile 編輯器也不允許已配置代理的 Profile 使用它;如果每一個其他訊號都來自代理,卻單單透過 WebRTC 強行放出真實 IP,那就重新製造了整套設定本該堵住的那個洩漏點。

replace 的防護機制範圍更窄:它在使用輪換代理的 Profile 上會被拒絕。把 srflx 候選改寫成「代理的出口 IP」,前提是這個出口 IP 得是一個固定、已知、整個工作階段都能錨定的值;輪換代理沒有這樣一個值。即便是在靜態代理上,如果核心在 ICE 蒐集候選位址的那一刻還不知道該代理的出口 IP,replace 也會退化為 privacy 的行為,而不是給出一個過時或錯誤的位址──不改寫的猜測,比完全沒有候選位址還要糟糕。

這些模式都不涵蓋什麼

webrtc.mode 只在核心啟動一個 Profile 時,透過一條私有的 stdin 通道讀取一次──它無法在該瀏覽器執行個體運作期間被動態改變,所以需要不同模式的 Profile,需要的是重新啟動,而不是即時切換。對於 replace 和 forward_stun,被改寫或轉發的值在網頁可能去查詢的每一處都保持一致──RTCPeerConnection.getConfiguration()、icecandidateerror.url,以及 getStats() 和 iceTransport 事件中暴露的候選位址 URL,都和實際發出去的一致。這堵住了網頁原本可以用來交叉核對的明顯手段,但它有一條硬性限制:一個自己架設 STUN 伺服器的網站,能看到每個請求實際來自哪個位址,並可以把它與網頁拿到的候選位址進行比對。replace 無法改變那台伺服器觀察到的內容。它是針對網頁端視圖的一項隱私與一致性控制,僅此而已。

還有兩個限制值得直說,而不是含糊帶過。任何一個 webrtc.mode 值下 IPv6 候選位址的行為都尚未單獨驗證過,因此本文對此不作任何斷言。而且這些模式並不管轄 Chromium 暴露的每一個 WebRTC 相關介面──核心中存在一個更新的 RtcTransport API,它完全在 webrtc.mode 的管轄範圍之外,不過它目前尚未在正式發行版本中開放,所以現階段網頁還接觸不到這條路徑。以上都是 Polychrome 核心中已有文件記錄的行為,測量基於 M150 建置;目前還沒有可供下載的桌面應用程式正式版本,所以請把本文當作核心當下的實際行為來看待,而不是當作一個可以在已發行版本中嘗試的功能。