隐私测试如何做到不留数据
隐私测试是一种向你展示网站能从你的浏览器了解到什么的工具——画布与 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、你的字体、你的屏幕和你的时区结合成接近独一无二的东西,无论有没有人存储它们——这正是该工具要向你展示的发现。不保留数据保护你免受我们的侵害。它不保护你免受这项技术的侵害,假装不是这样将是我们能交付的唯一真正不诚实的功能。