camofox-browser看着像个玩具项目名camo是伪装fox是火狐合起来就是一只会伪装的狐狸。但这只狐狸做的是正经事——在Gecko引擎的基础上把Firefox调校成一套日常可用的反指纹浏览器。做这个项目之前我花了大半年时间试用各种隐私浏览器结论都不够满意最后干脆把Firefox的隐私参数、插件策略、指纹伪装逻辑全部固化到一个可复现的构建脚本里给这个项目起了个代号就叫camofox-browser。这篇文章就是这套方案的完整复盘内容包括为什么弃用现成方案、user.js里每个关键项的用途、构建脚本的设计思路以及实测指纹数据的变化。1. 项目起点我为什么不用Tor Browser而要自己改造Firefox1.1 现成隐私浏览器的真实体验先说结论市面上并不缺隐私浏览器缺的是“隐私和日常可用性平衡得好”的浏览器。Tor Browser的安全等级确实高所有流量走多跳中继指纹也被抹得很平但代价是速度慢、验证码多打开一个稍微复杂的单页应用都可能卡半天。我用它当主力浏览器两周最后实在受不了只能退回普通浏览器。LibreWolf是另一个经常被推荐的方案。它在Firefox基座上做了大量默认关闭项安全团队会定期维护一份很激进的黑名单很多正常网站会被误伤。比如我有次用LibreWolf登录一个视频会议后台页面里的屏幕共享模块直接失效还有一次访问企业OA系统文件上传组件渲染不出来。这些问题的根源不是Firefox本身不行而是LibreWolf把Canvas、WebGL、字体等模块默认状态下动不动就禁掉缺少一种“让所有用户看起来一样”而不是“让浏览器看起来什么都没有”的统一策略。Brave我也用过一段时间。它的性能非常好内置广告拦截但它的商业模式和隐私理念有天然拉扯——用户数据不去Google却要去它自己的广告网络。再加上Chrome系浏览器对指纹暴露面极大Canvas指纹、AudioContext指纹、WebGL指纹在这些内核上几乎是半开放状态靠扩展去堵漏效果有限。1.2 Gecko引擎对我的意义折腾了一圈之后我把目光放回Firefox。理由很简单Firefox的配置系统完全开放about:config里有上千个开关从网络行为、缓存策略到指纹暴露面都能精细控制。更关键的是Mozilla维护了一套叫RFPResist Fingerprinting的机制它不是简单地把某个API禁用掉而是把Canvas、字体、时区、语言、UA等指纹信息的取值统一成一套固定的模板所有开启RFP的浏览器在网站眼里就是同一台设备。这个思路和“涂抹伪装”正好对上我不需要让自己变成一台没有特征的机器这样反而在统计上非常显眼。我只需要让camofox-browser的所有用户共享同一套指纹参数让广告系统和大数据平台无法区分谁是谁。所以我在项目描述里给这个浏览器定了一条原则不追求指纹为空追求指纹不可分。2. 指纹伪装的关键战场user.js参数逐项拆解2.1 RFP模式才是核心而不是单独禁用某个API很多新手做隐私浏览器第一反应是把webgl.enabled设为false、把canvas.extractData设为false以为把这些API全部禁用就安全了。实际上这个思路是错的。原因有两点第一禁用API本身就是巨大的指纹。现在很多反检测脚本会遍历所有Web API的存在性你的浏览器“恰好缺少”某个方法这个特征比方法本身返回什么值还要敏感。第二禁用API会破坏大量正常功能地图白屏、图表库无法渲染、网页版PS打不开最终用户只能为了某个网站临时改配置反而制造更多不一致。RFP的思路完全不同。它允许这些API正常存在但返回值被统一固定或者加入微小噪声让抓取方拿到的不是真实环境信息而是一个标准模板。这意味着所有camofox用户不管机器上是Windows、macOS还是Linux在网站上看到的屏幕尺寸、颜色深度、字体列表、时区都是一样的。我自己测试过开启RFP之后同一天内在两台配置差距很大的电脑上跑指纹检测返回的哈希值完全相同。2.2 一份可以照抄的user.js核心配置下面这段是我目前camofox-browser配置文件的骨架每一条都经过实测。你可以直接复制到profile目录下覆盖user.js或者追加到prefs.js里。// 启用RFP指纹伪装 privacy.resistFingerprinting true; // 自动拒绝网站的Canvas无输入提示请求 privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts true; // 即使有用户交互也要求明确授权后才允许读取Canvas privacy.resistFingerprinting.autoDeclineCanvasPrompts true; // 关闭地理位置 geo.enabled false; // 关闭网络共享位置的兜底服务 geo.provider.network.url ; // 关闭媒体设备枚举麦克风、相机默认不可见 media.navigator.enabled false; // 设备开启标志不响应网站询问 media.ondevicechange.enabled false; // 禁用Web通知 dom.webnotifications.enabled false; // 关闭遥测 toolkit.telemetry.enabled false; browser.ping-centre.telemetry false; // 关闭Firefox账号设备列表同步入口 identity.fxaccounts.enabled false; // 关闭Pocket extensions.pocket.enabled false; // 关闭搜索建议请求记录 browser.search.suggest.enabled false; // 强制所有Cookie按域名隔离并设置会话期结束自动清理 network.cookie.lifetimePolicy 2; // 关闭DNS缓存预读取带来的不确定性 network.dns.disablePrefetch true; // 关闭预测式预连接 network.predictor.enabled false; // 关闭HTTP Referer发送 network.http.sendRefererHeader 0; // 关闭WebRTC对本地地址的暴露 media.peerconnection.enabled false;需要说明的是network.http.sendRefererHeader 0这个项会让很多网站统计后台丢失来源如果有一部分场景必须保留Referer可以把值改为2表示只发送目标地址是同一站点时的来源信息这个要按自己需求调整。2.3 部分参数的逻辑解释很多人不理解为什么privacy.resistFingerprinting设置为true之后User-Agent也会被改变。实际上Firefox在RFP模式下会把UA统一成固定的模板同时会把操作系统平台相关的信息隐藏掉。你打开任何一个UA检测网站看到的都是同一串字符串不管下面跑的是Windows 11还是老款macOS。这样做的目的是抹平操作系统层面的指纹。还有一个很多人忽略的参数font.systemFonts。在普通Firefox里网站在较新版本中已经不能直接枚举系统中的字体列表但部分网站会通过Canvas一段段文本的渲染宽度来探测本地是否安装了特定字体。这个行为被称为“字体探测攻击”RFP模式会在绘制文本时加入像素级噪声让攻击者无法通过精密的尺寸差异来判断字体是否存在。我在这里特别建议不要做的一件事不要叠加安装CanvasBlocker这类以随机噪声为原理的插件。原因是随机噪声会让每次访问生成的指纹都不一样在短时间内可以干扰追踪但从统计上看一个长期保持噪声模式的浏览器本身就是孤立点。RFP的做法是“统一化”让所有用户进入同一个组这个思路更适合“融入人群”的目标。3. 构建脚本与目录设计让定制可复现、可迭代3.1 为什么选择配置分发而不是重新编译源码在动手之前我其实先考虑过从Firefox源码编译一个独立浏览器。Gecko的编译需要下载几十GB的依赖在一台8核16GB内存的机器上首次编译大约要两三个小时之后每次调整代码、重新打包又得花很长时间。而且Firefox每隔几周就会更新主版本跟着上游走会被拖得很累。最后我采用的方案是“Firefox ESR profiles文件 policies.json 预置扩展”通过一个shell脚本把这些内容固化下来任何一台机器只要跑一次脚本就能得到一个完整的camofox-browser环境。ESR是Firefox的企业稳定版大约每42周才升级一次大版本配置兼容性的测试窗口很长不会动不动因为新特性导致配置失效。这个方案同样适合社区分发别人不需要参与编译直接把profile复制过去就能用。3.2 目录结构与构建流程我的项目目录长这样camofox-browser/ ├── scripts/ │ ├── bootstrap.sh │ ├── verify-profile.py │ └── sign-extensions.sh ├── customization/ │ ├── policies.json │ └── distribution.ini ├── profile-template/ │ ├── user.js │ ├── containers.json │ └── search.json.mozlz4 ├── extensions-src/ │ └── ublockorigin-webext/ └── dist/bootstrap.sh做了这样几件事下载Firefox ESR对应的系统安装包解压到指定目录。把policies.json复制到浏览器安装路径的distribution文件夹里。这个文件是Firefox企业版策略普通人会把它类比为Windows组策略——不需要改源码就能控制浏览器的更新地址、禁用组件、预设主页和证书文件。用firefox --CreateProfile camofox创建一个独立profile然后整体把profile-template里的文件覆盖进去。将extensions-src里预先下载好的uBlock Origin的.xpi安装到profiles的extensions目录同时写入prefs.js里的extensions.autoDisableScopes避免浏览器因为签名校验失败而自动禁用扩展。3.3 policies.json里还可以做什么刚接触这个机制的人可能会忽略policies.json不仅能冻结更新还能通过Preferences块直接写入首选项。这比用户手动打开about:config修改要稳定得多因为企业策略的优先级高于普通配置文件普通网页甚至本地js都无法篡改这些值。我有一段时间发现Firefox会在后台偷偷创建一些内部任务记录比如定向推荐组件的下载缓存。后来我在policies里加了一条{ policies: { DisableAppUpdate: true, NoDefaultBookmarks: true, Cookies: { Behavior: reject-tracker }, Preferences: { browser.topsites.ignoreSponsored: true } } }这样可以一次性把预置书签、推广站点、自动更新提示全部屏蔽掉。对于团队分发场景还能加入Certificates块控制内部根证书加入Homepage块锁定起始页这些都是在企业环境里几个小时内就能落地的事。4. 插件层面的攻防哪些扩展能留哪些必须删4.1 扩展本身就是一枚重型指纹在桌面浏览器上扩展列表可以通过大量途径泄漏。依赖的历史原型navigator.plugins早期可以直接枚举插件现在虽然被部分限制但WebExtensions的图标下载请求、Content Script在所有页面注入DOM的特定节点、扩展内置API开放时的特殊报错文案都会在不经意间暴露浏览器装了哪些扩展。这意味着一个很反直觉的结论装得越多指纹越独特。很多用户在Firefox上装了十几款隐私扩展结果每一款扩展都给指纹检测脚本提供了一个额外判定条件的素材。广告追踪系统就算不知道扩展的具体版本也能通过页面里的异常行为概率判断这个浏览器“装了不常见的东西”这本身就是高价值标签。4.2 camofox-browser的取舍思路我在这个项目里留下的唯一常规扩展是uBlock Origin而且我把它调整成了中高级模式不再把拦截逻辑全部依赖预设规则列表。平时把拦截器切换成“Medium mode”也就是只拦截第三方脚本和iframe遇到需要精细化控制的站点再手动改成“No remote fonts”“No large media elements”这类更严格的规则。这样一来uBlock Origin承担了反追踪、反挖矿、反恶意跳转的大部分工作同时它本身是用户量极大的项目它的指纹特征被淹没在千万级用户群中不会成为孤立特征。其他扩展我全部砍掉。Firefox的隐私容器功能足以替代Multi-Account Containers插件内置的密码管理器在开启主密码后的安全性也比很多第三方密码插件要好。临时容器、会话恢复等原本需要插件才能实现的功能现在要么由内置API实现要么可以通过user.js配置完成。4.3 扩展签名的问题有一个很现实的坑Firefox默认只允许安装来自官方商店且通过Mozilla签名的扩展。如果是自定义项目自己签一个开发版扩展当本地签名证书过期或者浏览器更新后校验版本变化扩展会被静默禁用。我早期在camofox-browser里随手拖进了一个未签名扩展重启浏览器后它直接消失查日志才知道是校验失败。后来我在项目的sign-extensions.sh里做了两件事先判断xpinstall.signatures.required策略是否被企业策略锁死然后通过web-ext sign --api-key... --api-secret...走Mozilla的API签名流程将签名后的.xpi放进profile。如果你只是自用、不打算分发自己写一个扩展时用about:debugging临时加载是最快的方案。但这个方案只在当前会话有效重启后依然得重新挂载所以对“构建可复现浏览器”的目标来说签名环节早晚得做。5. 验证环节指纹检测结果与日常使用权衡5.1 指纹维度的前后对比我把默认Firefox和camofox-browser在同一个指纹检测网站上跑了一遍字段对比如下变化比较直观检测维度默认Firefoxcamofox-browserUser-Agent真实系统型号版本统一模板不区分OS屏幕分辨率真实分辨率固定为统一模板时区真实时区锁定为固定值语言列表真实语言列表精简配置Canvas指纹真实设备渲染结果加入统一噪声WebGL渲染器GPU品牌驱动信息返回通用软渲染器字体列表完整系统字体只暴露基础字体集媒体设备麦克风/摄像头默认可见默认不可见这组数据说明伪装不是“把信息清成空白”而是“把信息替换成一套公共模板”。你也可以在项目里让UA、时区等字段随机化但我不建议这么做。随机会带来跨站点登录状态的不一致今天看还是同一台设备明天就变成另一台很多网站的账号安全系统会直接判定账户异常。5.2 日常网站适配记录我用camofox-browser做主力浏览器已经将近两个月这里记录几个容易被忽视的场景输入框的自动填充基本正常但部分使用严格设备指纹加密的金融类App网页版会出现较高级别的验证码这个在可接受范围内。视频网站播放器能正常出画面但要留意DRM模块是否已安装Firefox的Widevine CDM默认会按需下载如果企业策略里把MediaDRM禁了视频就会退回普通清晰度。还有一次我用它登录一个在线文档协作平台原本正常后来平台升级后要求启用麦克风权限由于我在user.js里把media.navigator.enabled设为false页面上连权限申请弹窗都出不来只能临时把配置改回true。这类“权限全面禁用”的问题很典型解决办法不是一刀切而是用一个独立的“工作profile”来装这些需要授权的场景和默认的反指纹profile分开使用。5.3 性能开销与内存占用开启RFP对浏览器性能的影响在普通办公和网页浏览场景下几乎感知不到。Canvas绘制会有一点额外噪声计算但影响可以忽略。内存占用方面由于禁用了遥测、预链接和一些不必要的后台任务反而比原生Firefox略低一点净效果是日常浏览更轻快。真正需要担心的不是CPU和内存而是缓存的粒度。RFP模式下Firefox对缓存和Storage的分区策略更严格同一个域名下的会话状态碎片更多所以“退出时自动清理Cookie”开启之后部分网站重新登录的频率会明显提高。这是隐私和体验之间最直接的取舍我没有用插件去平滑它而是靠Firefox账号的跳过机制来管理。6. 我踩过的坑和后续扩展思路6.1 反复出现的四个坑第一个坑是我把webgl.disabled设为true结果大多数地图应用全白屏。后来从日志里看到页面加载时WebGL上下文创建失败地图引擎直接放弃渲染。后来我把该项删掉依赖RFP本身对WebGL返回值的模糊化地图功能恢复了也没有看到WebGL指纹真实暴露。第二个坑是时区锁定。最开始我图省事把时区统一设为UTC结果邮箱里的会议邀请全部乱套日历系统还发出了“您在会议开始前的提醒可能不准确”的警告。之后我把profile的时区锁定在主要使用场景对应的实际时区降低了异常程度也避免了跨时区出差时的时间判断错误。第三个坑跟开发者工具有关。RFP会把屏幕、DPR等参数统一所以用开发者工具调试响应式布局时模拟设备看到的数值总是对不上。后来我专门留了一个给开发调试用的“浅伪装profile”不开启RFP其他隐私项照旧这样既不影响日常使用又能在需要写前端的时候不炸手。第四个坑是浏览器更新后个别user.js项被上游改名。Firefox每次主版本升级可能把某个偏好的作用合并到另一个开关里例如privacy.trackingprotection.fingerprinting.enabled在不同版本里的默认值有过变化。维护这类配置文件必须在每个ESR更新后重新跑一遍verify-profile.py对比用户修改过的项和当前版本默认值把被上游标注为弃用的项挑出来。6.2 白名单机制的教训一开始我以为“反指纹浏览器”就应该一视同仁所有网站都不能破例。后来发现这个想法太理想化。网银、政务平台、办公系统这些地方往往同时依赖设备指纹和Cookies来维持风控会话。全开RFP导致的结果就是不断被验证码打断体验极差。我最后的做法是按使用频率把网站分成三类第一类是纯信息浏览类比如技术文档、新闻站保持完整RFP强度第二类是登录型服务比如邮箱、在线文档保留RFP但对个别域名开启Cookie例外第三类是金融和政务这类对风控敏感的场景用一个完全独立的profile处理不共享任何配置。这个白名单机制没有写在user.js里而是通过Firefox自带的Multi-Account Containers配合独立profile实现的。容器之间分配不同的Cookie、缓存和站点数据实现逻辑隔离。6.3 后续想做的事接下来我会重点尝试两个方向。第一是移动端Android版Firefox的定制能力一直没有桌面版那么全我在尝试把policies.json和user.js的思路移植过去如果可行camofox-browser就能覆盖手机日常浏览场景。第二是分发形态现在整个项目还是一个脚本集使用者需要自己跑bootstrap.sh门槛偏高。等脚本稳定后我会把最后的输出打包成一个可以直接运行的目录让用户解压即用。做这套项目最大的体会是隐私配置不是越极端越好真正的目标是在默认状态下把暴露面压到最小同时保证所有伪装参数在一个群体内共享。只要你做到“你的指纹和大家一样”就已经赢过大多数用户了。最后再分享一个例行维护的小动作每次Firefox升级后我都先把user.js备份一份然后跑一次官方的配置迁移工具确认没有参数被静默删除再重新覆盖回去。这个步骤虽然只有两分钟但能避免很多莫名其妙的隐私泄漏问题。