LinkedIn 的 BrowserGate,以及为什么一个浏览器身份已经不够用了
LinkedIn 悄无声息地探测 6000 多个浏览器扩展、收集 48 项设备属性,每次访问都通过 WebRTC 抓走你的 LAN IP。解法不是某个隐私开关——而是一种形态完全不同的浏览器。
每次你打开 LinkedIn,那个页面都会悄悄向你的浏览器抛出好几千个问题——装了哪些扩展、装了哪些字体、用的是什么 GPU、CPU 有几个核、本地 IP 是多少。然后它把这些答案写进一个请求头里,贴在此后每一个请求上。你从未同意过这件事,你也看不到它在发生。这不是一个 bug;它有个名字,叫「Spectroscopy」。
4 月上旬,一个名为 Fairlinked e.V. 的欧洲 LinkedIn 用户组织发布了一份调查——你怎么算都行,它要么是过去十年里最详尽、要么是最不令人意外的一则浏览器指纹识别故事。他们展示,LinkedIn 自家的前端 JavaScript,每当你打开站点,就会向 chrome-extension:// URL 发起 6000 到 7000 次 fetch() 请求,逐一检测每一个是否可达,另外再采集 48 项设备和浏览器特征,然后把这整包数据经 RSA 加密、贴在此后每一次 API 调用上,回传到 LinkedIn 的遥测端点。
这份报道被 The Next Web 转载,404 Privacy 独立进行了复现,还有人在一篇长篇技术拆解中把代码层面的逆向讲了个遍,一步步还原了真实负载。Fairlinked 的姊妹机构 Teamfluence Signal Systems 早在 2026 年 1 月就已在慕尼黑依据欧盟的 Digital Markets Act(DMA——这条欧洲法规规定了把关平台该如何对待用户)申请了临时禁令。法院驳回了。LinkedIn 那边则告诉记者,这些指控「完全错误」,并称他们的扫描只针对违反其服务条款的扩展。
这篇文章不讨论 LinkedIn 在爱尔兰或德国是否拥有法律上的正当性。它想谈的是,这件事所揭示出来的东西:你此刻正用来阅读这篇文章的浏览器,在设计之初就没打算抵御这类行为——更重要的是,它抵御不了,因为它只有一个身份可以交出来。
当你访问 linkedin.com 时,那个页面到底做了什么
有两条机制在并行运作,哪怕你从未打开过 DevTools 面板,也值得把它们弄明白。
主动扩展检测。 该页面内置了一份硬编码的 6000 多个 Chrome 扩展 ID 列表。对每一个 ID,它都会向该扩展包中的某个已知文件——比方说 chrome-extension://<id>/manifest.json,或者某个特定图标——发起一次 fetch()。如果请求成功,该扩展就是装了的;如果 Chrome 拒绝了请求,就是没装。这个小把戏只对那些至少暴露了一项「web-accessible resource」的扩展有效,而绝大多数真实的扩展都会这么做。文章还提到,其隐蔽变体会用 window.requestIdleCallback() 配合错开延时,以免在网络面板里留下明显痕迹。
被动 DOM 扫描(「Spectroscopy」)。 与此同时,该页面还会在你的 DOM 里搜寻 chrome-extension:// URL 引用。这能捕获那些往页面里注入内容的扩展——也就是硬编码列表可能漏掉的,或者列表还没来得及收录的较新扩展。
这些结果会喂给 LinkedIn 内部称为 APFC 的指纹采集流水线,全称「Anti-fraud Platform Features Collection」,它还有一个别名 DNA,全称「Device Network Analysis」。除了扩展列表之外,这条流水线还会采集:
Canvas 指纹
页面用特定的文本、曲线和颜色绘制一张隐藏图像,再把像素读回来。确切的像素值取决于你的 GPU 驱动、字体光栅化器以及操作系统版本——足以生成一个跨页面加载稳定、且罕见到可以在你回访时认出你的签名。
WebGL 渲染器
描述你图形栈的 65 多项参数:厂商字符串、驱动版本、受支持的扩展、着色器精度。同一个操作系统下的一台笔记本和一台台式机,会产生不同的 WebGL 签名。
音频上下文
通过让静音音频穿过浏览器的振荡器和压缩器节点得出的指纹。不同的音频栈在浮点运算的舍入上会有细微差别。
WebRTC 本地 IP
WebRTC 是浏览器用于实时通话的 API。由于它发现网络路由方式的一项内置副作用,它可以被要求报告你的 LAN IP 地址——也就是你路由器分配给你的 192.168.x 或 10.x 那个数字——哪怕你挂着 VPN。LinkedIn 会问。
硬件与本地化
CPU 核数、设备内存、屏幕分辨率和像素比、时区、语言、电池电量与剩余时间、已安装字体、触控支持。根据 Fairlinked 的报告,一共 48 项属性。
以及扩展列表
前面提到的那份数以千计的探测。大多数指纹研究论文会把「已安装扩展」当作一项锦上添花的信号;LinkedIn 把它当作头号信号。
这些信号被合并为一个加密的数据块,作为 HTTP 请求头贴在该页面此后发出的每一个 API 请求上——这样,LinkedIn 的后端就能在每一跳都知道你是哪一台设备。调查报告指出,LinkedIn 已经对运行了它不喜欢的扩展的用户采取了行动——封号、警告——这是数据确实被用于决策、而不只是被采集的最直接证据。
在法律问题底下,其实还藏着一个真问题:如果 LinkedIn 在以这种规模做这件事,那么还有多少别的网站正在做一个缩水版?诚实的答案是:大部分大型网站都在做。LinkedIn 比较特殊的地方只在于它那份扩展名单的广度,以及它被工程化出来时的那份讲究。消费者互联网的其余部分,也不过是在用不同程度的精巧,运行着同一套基本剧本。
问题不在于指纹识别,而在于身份的复用。
退一步看。Canvas、WebGL、WebRTC、字体枚举——这些都不是新鲜事。过去十年里,几乎每一位安全研究者都写过这样一篇文章,末尾都会说「打开 Firefox 的 resistFingerprinting」或者「装个 CanvasBlocker」。这个建议不能说完全错,但它绕开了这个问题真正的承重墙。
哪怕你拥有完美的指纹抵御能力——每个信号都被归一化、每张 canvas 都是空白、每个 WebRTC 连接都被拒绝——你的浏览器仍然只有一个 cookie jar、一个历史记录文件、一套已登录会话、一个保存密码的保险库、一份已安装扩展列表,以及网络层上的一个 IP 地址。你在网上做的一切,都是从这一个身份里流出来的。一个读不到你 canvas 的指纹采集者,照样能看出来:登录 LinkedIn 的那个人,登录 Facebook 的那个人,浏览某家独立小书店的那个人,查看健康门户的那个人——全都来自同一个浏览器实例、带着同样的 cookie、来自同一个 IP。而且这里存在跨站的网络效应:LinkedIn 的数据一旦和别的任何一个网站的数据拼起来,它的价值就会陡增。
这就是为什么一个孤立的反指纹扩展并不能真正解决问题,也是为什么在普通浏览器之上「加一个 VPN」同样解决不了问题。VPN 能改变你表面上看起来的 IP,但它无法把你的浏览器切分成多个身份。你还是只有一个 cookie jar。你还是只有一套会话。LinkedIn 和 Facebook 看到的还是同一台机器——而现在,对他们来说还更顺手——一台已经被拿去和某家 VPN 的 IP 历史做过交叉比对的机器。
按标签页各自一个 VPN 的思路
这里开始有些不一样了。因为每一个 Bromure 标签页本身就是一台用完即弃的 Linux 虚拟机,VPN 是属于标签页的,不属于整个浏览器,也不属于整台电脑。它属于标签页。
你可以给 LinkedIn 的配置档案挂上瑞典的 Mullvad 出口,给 Facebook 的配置档案挂上瑞士的 ProtonVPN 出口,给「随便点点链接」的配置档案挂上 Cloudflare WARP,而给本地银行的配置档案什么都不挂——所有这些,都同时在同一台 Mac 上、同一个 Bromure 应用里进行。LinkedIn 看到的是瑞典出口。Facebook 看到的是瑞士出口。银行看到的是你家里真实的网络。没有任何一方能用干净的手法察觉到:它们面对的其实是同一个人的笔记本。
这不是理论上的隔离。Bromure 借助 Apple's Virtualization.framework,为每一个标签页启动一个真正的 Linux 内核。每一个虚拟机都有自己的内核网络栈、自己的 /etc/hosts、自己的进程树、自己的一组 Chromium flags、在自己虚拟磁盘上的自己的配置档案目录。两个指纹网站上的两个标签页,经由两个不同的 VPN 出口,在指纹识别所依赖的那一层看,和两个人用两台不同笔记本完全无法区分——因为在那一层上,它们确实就是。
按标签页设置 VPN 的开关在配置档案的 VPN & Ads 面板里,有三个选项:Cloudflare WARP(Cloudflare 的消费级加密服务)、WireGuard(任何提供商、任何自建服务器——把 .conf 粘进来就行),或者 IKEv2(对接企业 VPN)。VPN 在虚拟机内部运行,也就是说,哪怕是客户机里的 Chromium 进程,也看不到你真实的 IP。
Bromure 默认关闭 WebRTC
WebRTC 是 BrowserGate 事件里 LAN IP 泄漏的直接原因。它设计出来是用于浏览器到浏览器的音视频通话的——而作为它发现网络路径方式的一项副作用,它可以被要求枚举你本地的网络接口。任何一个网站——不仅仅是你正在与之通话的那个——都可以在后台悄悄跑一次这个枚举,从而得知「这个用户的路由器分给他的地址是 192.168.1.47,所以他在一个典型的家庭网络里,和这枚指纹上次访问时所在的那个网段是同一个」之类的信息。
对于任何没有同时启用摄像头和麦克风的配置档案,Bromure 默认关闭 WebRTC。具体来说,当某个配置档案不需要摄像头或麦克风访问时,Bromure 的启动器会为 Chromium 加两个 flags:
--force-webrtc-ip-handling-policy=disable_non_proxied_udp--enforce-webrtc-ip-permission-check
并加载一个名为 webrtc-block 的小型浏览器扩展,把 RTCPeerConnection 构造函数替换为一个什么都不做的存根。最后这一点很关键:仅靠那条 policy flag 就能阻止 IP 通过 UDP 泄漏,但一个较真的指纹采集者仍然可以实例化 RTCPeerConnection、去观察它的行为;这个存根让连构造函数本身都会失败。最终效果是:某个网站若想在一个不需要 WebRTC 的 Bromure 配置档案上复制 LinkedIn 那套 LAN IP 把戏,它什么也拿不到。
你不需要为此切换任何开关。对于任何没有启用视频通话类硬件的配置档案,这就是它的默认状态。如果你在某个配置档案的 Media 面板里打开了摄像头或麦克风共享,需要 WebRTC 的 VPN 和视频通话场景就会自动回来。这个开关的逻辑是「只有在你真的需要的时候才开」——这更接近这张网本来应有的工作方式。
隔离解决不了什么
有两件事值得开门见山地说清楚。第一,如果你在每个配置档案里都用真名登录了 LinkedIn,那么 LinkedIn 就知道你是谁。隔离并不会让你对那些你主动登录的服务匿名;它打破的是跨站的身份拼接,并阻止了不同会话之间的被动再识别。这些是实打实的胜利,但它们不是匿名。
第二,Bromure 无法阻止 LinkedIn 在 Bromure 的虚拟机里运行它的扩展扫描。它能做到的——并且确实做到了——是让扫描的结果变得索然无味:默认情况下,一个 Bromure 配置档案里除了 Bromure 自己内部用的扩展之外,没装任何 Chrome 扩展,而那些内部扩展挂的是按配置档案限定的、不同的扩展 ID,不在 LinkedIn 的硬编码列表里。那个页面会一本正经地探测 6000 个 ID;什么都不会返回。48 项属性的指纹还是会被采集——但它标识的是一台用完即弃的虚拟机,它的 WebRTC、cookie 和 IP 都不会与你运行的任何其他虚拟机重叠。
这足够把 BrowserGate 作为一个跨站关联工具的獠牙给拔掉。它不足以让你登录之后在 LinkedIn 面前隐身,我们也不会装作它能。
LinkedIn(以及类似站点)推荐的 Bromure 设置
如果你要新建一个专门用于 LinkedIn 的配置档案,或者 Facebook 的,或者任何已知会积极做指纹识别的社交平台的配置档案,下面这些默认项值得检查一下。以下设置都在按配置档案的设置面板里(列表中每个配置档案旁的齿轮图标)。
General → Retain Browsing Data:开
默认情况下,Bromure 会在你关闭窗口时把一切都销毁,这对随手浏览是对的,但对一个你每天都要登录的站点就是错的。把它打开,cookie 和密码会在一个专用的虚拟磁盘上跨会话保留下来。你不用每次都重新登录。
Privacy & Safety → AI Phishing Detection:开
LinkedIn 上的钓鱼——伪装成招聘者的消息,链接跳到凭据收集页面——是一个很大的品类。AI Phishing Detection 会把当前页面的 URL、可见文本和表单结构发到 Bromure 的分析服务器上打分,并在你往一个伪造的表单里输入内容前警告你。启用它之后数据会离开本地虚拟机,这一点值得知道。
Media → Share Webcam:关
LinkedIn 用不着你的摄像头。关掉摄像头还可以顺便让 WebRTC 保持关闭,这正是 Fairlinked 调查所揭露的本地 IP 泄漏的直接应对手段。
Media → Share Microphone:关
同理。自动 WebRTC 终止开关要生效,摄像头和麦克风必须都关着(WebRTC 是它们两个共同依赖的 API)。
File Transfer → File Download:关
正常使用 LinkedIn 并不需要从 LinkedIn 下载文件。把下载关掉,意味着即便你点了本不该点的东西,一个被攻陷的页面也没法往你的 Mac 上丢任何东西。如果你需要用简历投递职位,可以把 File Upload 留着开。
VPN & Ads → WireGuard(或 WARP):开
这是「按标签页 VPN」思路的实际体现。把你希望 LinkedIn 看到的出口——Mullvad、ProtonVPN、一台自建服务器——的 WireGuard 配置粘进去,打开 Connect on Startup,LinkedIn 看到的就是一个和你打开的其他任何标签页都不同的 IP。如果你手边还没有 WireGuard 提供商,WARP 就是更省事的一键选项。
几条不那么显眼的提示。只要摄像头和麦克风都关着,这个配置档案的 WebRTC 就已经关了——你不需要在任何地方单独把它设上,也没有一个复选框给你勾,因为它是从这两个设置推导出来的。
如果你是在为工作准备一个专用的 LinkedIn 配置档案,并希望 VPN 会话能挺过单个标签页的关闭,可以把 Retain Browsing Data 和 WireGuard 下的 Connect on Startup 搭着用——每次重新打开那个配置档案时,VPN 都会自动起来。
你还可以再往前走一步。Advanced 面板里有一个 Encrypt Browsing Data 选项,它会用 LUKS 给持久化磁盘加密,密钥存在 macOS Keychain 里。对于那些你既归在社交媒体相关、又希望它能跨越重启保留下来的东西,这是一个合理的偏执等级。
解法的形状
BrowserGate 是一个很好的符号,标记着一件已经在那儿一段时间、而且越来越糟的事情:普通浏览器面对这一类监控几乎拿不出什么有分量的回应,因为它们整套架构就是围绕「每个浏览器实例只有一个身份」造起来的。你可以装扩展去缓解具体的泄漏,也应该这么做;但结构性的答案是一种每个标签页都是自己一台电脑的浏览器,它有自己的网络、自己的隐私面,以及可以随时收场的能力。关掉窗口,这个身份——不管 LinkedIn 的扫描器采集过什么、贴上过什么关联 cookie——就没有了。
这就是解法的全貌。它不依赖于 LinkedIn 同意停手,也不依赖于 DMA。它依赖的是:你的浏览器,长得和你买这台笔记本时它附带的那款,是不一样的形状。