隱私測試如何做到不留資料
隱私測試是一種向你展示網站能從你的瀏覽器了解到什麼的工具——畫布與 GPU 訊號、字型、螢幕、時區、語言、網路線索,以及讓網站在你再次造訪時認出你的安靜識別符——這帶來一個可信度問題:一個演示監控技術的工具,其自身的資料做法就是產品的一部分,不能是事後補上的政策頁。我們瀏覽器指紋測試背後的設計規則一句話就能說清:瀏覽器能讀到的,伺服器永遠不看。本文解釋這條規則的代價、它在哪裡彎曲、又在哪裡止步。若想先看訊號清單,請從網站對你了解多少開始;本文講的是測試之下的管道,不是訊號本身。
用戶端優先
瀏覽器本身提供的每個訊號都在本地、在你的機器上、由分頁中的頁面程式碼讀取並呈現。畫布渲染、WebGL 渲染器字串、音訊上下文屬性、字型探針、螢幕與視窗幾何、色深、觸控支援、儲存估算、對比度與動態偏好的媒體查詢——全部在被發現的地方計算,並畫進你能看到的頁面。頁面程式碼是唯一曾持有完整圖景的東西。沒有腳本把面板收集成載荷,沒有信標把它送回家,沒有分析事件總結它,因為一份完整指紋一旦離開瀏覽器,這個測試就變成了它所警告的東西。
這個順序——本地優先、伺服器最後,且伺服器只做瀏覽器確實無法獨自完成的那幾件事——不是架構的一個功能;它就是架構。大多數隱私工具反其道而行:先集中收集一切,再承諾刪除、匿名化或短暫保存。那些承諾也許會兌現,但它們是承諾,讀者無法稽核一次刪除。一個從不傳輸資料的設計,用關於程式碼路徑的事實取代了承諾,而關於程式碼路徑的事實可以在網路分頁裡核查——這正是隱私工具應當邀請的標準。
代價是真實的,並且被設計所接受。沒有任何跨裝置的東西:沒有跟著你的帳戶,沒有同步的歷史,沒有“你另一台筆記本上的指紋”對比,因為這些都需要伺服器按人保存某些東西。對比只限於你自己的瀏覽器在本地記住的東西,以及下文所述的匿名每日計數器。當一個功能需求需要在伺服器上放完整指紋時,答案不是更謹慎的保留政策——而是不做。這個約束就是產品。
三個小端點,三個不同的工作
有三件工作確實無法在頁面內完成,每件都得到它自己狹窄的小端點。第一個是 /api/check/whoami,它只返回任何伺服器在取得頁面的請求裡已經看到的東西:你的網路位址到達時的樣子、你的瀏覽器隨之發送的標頭,以及附在該位址上的粗粒度地理。向你展示這些值不增加新的暴露,因為你造訪的每個伺服器無論顯不顯示都會收到它們;這個端點只是把單向披露變成可見的披露,並不在回應之外保留任何東西。
第二個是 /api/check/etag,它只為演示基於快取的識別而存在,下一節會解釋:它發出一個隨機快取識別符,並且什麼都不保留——沒有已發數值的表格,沒有誰問過的記錄。第三個是計數器端點:它接受一次完成運行中的少數粗粒度值並返回累計總數,這樣頁面能說你的配置有多常見,而無需任何人保存你的造訪。每個端點都像本站其他檢測器一樣被限流,因為小而誠實不等於無限,一個能被猛敲的演示端點是披著好意的外衣的濫用面。
頁面還明確說明它從你的瀏覽器直接聯繫的外部服務,因為這些服務也看得到你的位址,對它們誠實是設計的一部分。測試讀取 Cloudflare 追蹤端點,查詢 DNS 回聲服務以揭示哪個解析器為你應答,並連接兩個公共 STUN 伺服器以展示 WebRTC 式查詢能學到什麼。每一個都在頁面本身上、在它產生結果的旁邊被點名——與我們在VPN 洩漏與偽裝檢查中應用的同一套披露紀律。一個悄悄聯繫第三方的隱私測試會不及格它自己的課。
ETag 把戲,誠實解釋
ETag 是 HTTP 快取的一個普通部件:伺服器給回應附上一個驗證器,瀏覽器把它與快取副本一起保存,下次造訪時瀏覽器把驗證器送回,以便伺服器回答“未修改”並節省傳輸。把戲在於驗證器不必描述內容。伺服器可以向每個訪客發出一個唯一的驗證器,瀏覽器會在每次重新驗證時忠實地返回那個唯一值——到那時伺服器認出瀏覽器的可靠程度與使用 Cookie 完全相同,卻從未設置過 Cookie。快取驗證之所以能兼作識別符,是因為瀏覽器正在正確地做它的工作。
這就是為什麼清除 Cookie 清不掉它:Cookie 與快取是分開的儲存,由分開的控件、按分開的時間表清空。這也是為什麼隱私或無痕視窗能打破這個把戲——隱私視窗使用它自己的獨立快取,視窗關閉時即被丟棄,驗證器隨工作階段而亡。我們的端點演示這個來回過程並且什麼都不存:識別由 304 回應本身證明,由伺服器看到自己的隨機值回來證明,而不是由造訪之間保留的任何記錄證明。演示在頁面關閉的瞬間結束,而這正是現實中使用這項技術的追蹤者所不提供的特性。
有一個誠實的皺褶值得點名:邊緣快取。如果回應經過共享的 CDN 快取,第一個訪客的識別符可能被發給所有人,演示就會顯示一群人共享一個 ID,而不是每個瀏覽器持有自己的。因此這個端點完全退出邊緣快取——每個請求都到達源站,每個訪客都收到一個全新隨機值,沒有共享快取曾持有任何一個。這是一行小小的設定,但它是一場證明機制的演示與一場意外證明相反結論的演示之間的區別。
用計數器代替日誌
有一個問題確實需要伺服器:“我有多不尋常?”一個能說你的 GPU 今天在 M 次運行中出現了 N 次的頁面,遠比一個只能列出訊號的頁面更有教益,因為稀有正是讓指紋具有識別性的東西。但用顯而易見的方式回答它——記錄每次造訪再來數——建立的正是該工具要警告的資料庫。因此測試保留計數器而非日誌:每個值每天一個數字。作業系統、瀏覽器家族、GPU 字串、時區、第一語言、螢幕尺寸等級、指紋識別符的前八個字元、每個單獨檢查的結果,以及一個運行時長桶——每一個都為每日總數貢獻一格,其他什麼都不保留。
邊界比機制更重要。完整指紋從不發送,更談不上儲存;你的位址根本從不發送給計數器。計數器無法被反向查詢到某個人,因為沒有每訪客一行可供查找——聚合發生在計數的一刻,而不是日後在已存記錄上的一步報告。這同樣是總數按日重置的原因:一個永遠累積的計數器,切得足夠細,就會悄悄地通過累積變成一個指紋資料庫,這個設計預先拒絕那種漂移,而不是事後為它道歉。
還有兩個選擇保護從未讀過本文的人。發送全域隱私控制訊號的瀏覽器根本從不被計數——該訊號意味著“不要追蹤我”,對它最尊重的理解是完全排除那些運行,而不是小心地數它們。其他人都能在頁面上得到一個可見的退出選項,在任何計數發生之前被遵守。以及,一次在 45 秒內沒有完成的運行會計數一次,作為“卡住”,記在它自己的桶裡:緩慢與故障對我們作為營運者保持可見,作為單個聚合數字,而不追蹤任何單個訪客穿過一次失敗的運行。
我們選擇不做的東西
這個設計最有教益的部分是被留下的東西清單。沒有連接級探針:沒有 TLS 指紋,沒有對你握手的 JA3 式讀取。誠實的原因是架構上的——本站的 TLS 在它前面的 CDN 處終止,因此在我們源站取得的握手讀取,描述的將是 CDN 的協商與設定,遠多於你的瀏覽器。把它作為“你的 TLS 指紋”發布將是演戲:一個看著合理卻掛著別人名字的值。一個印出一個自信錯誤讀數的隱私工具,會教會它的使用者在其他所有地方都信任自信的錯誤讀數,這比一個空面板還糟。
同樣的紀律塑造了其餘的省略。計數器是粗的,因為細粒度計數器會通過累積、一次一個無辜維度地變成指紋資料庫。上次造訪的記憶——“自你上次運行以來,這些值變了”——只住在你自己的瀏覽器儲存裡、在你的機器上,從不傳輸;它一旦同步,就會成為一份歷史,而歷史就是一份記錄。那些本會真正有用的功能被砍掉的理由,與端點保持小巧的理由相同:當資料代價是這個工具的全部要點時,有用性不是辯護。
最後是殘餘的誠實註腳,因為沒有一篇設計文章應在自己的恭維中結束。這個設計使資料最小化;它無法使讀數本身不那麼具有識別性。你的 GPU、你的字型、你的螢幕和你的時區結合成接近獨一無二的東西,無論有沒有人儲存它們——這正是該工具要向你展示的發現。不保留資料保護你免受我們的侵害。它不保護你免受這項技術的侵害,假裝不是這樣將是我們能交付的唯一真正不誠實的功能。