指纹

存储配额指纹:磁盘大小与身份

StorageManager.estimate() 如何暴露磁盘大小作为追踪信号,以及如何在浏览器引擎级别控制存储配额响应。

文档中心

想直接看维护中的产品文档?

这篇文章对应的主题已经有文档中心页面。需要规范流程、当前参数和长期参考时,优先看 docs。

简介

Storage Manager API 的引入是为了帮助 Web 应用了解有多少存储空间可用于其数据。通过 navigator.storage.estimate(),网站可以查询大约的存储配额(最大可用空间)和当前使用量(已消耗多少)。这些信息帮助应用做出关于缓存策略、离线数据存储以及是否提示用户释放空间的决定。

该 API 的设计初衷是好的。渐进式 Web 应用、离线优先应用和媒体密集型网站都受益于了解其存储预算。然而,navigator.storage.estimate() 返回的配额值是从设备的实际磁盘特征派生的。例如,Chrome 通常基于总可用磁盘空间的百分比分配配额。这意味着返回的值间接揭示了用户物理存储硬件的信息,创建了一个跨会话持续存在并经得住常见隐私措施的指纹信号。

隐私影响

存储配额指纹识别令人担忧,因为磁盘配置是稳定的、硬件特定的特征。与 Cookie 或会话数据不同,设备上的可用磁盘空间变化缓慢(仅在添加、删除文件或更换驱动器时)。这意味着存储配额值是一个持久标识符。

隐私影响显著:

  • 磁盘大小识别:256 GB SSD 的设备产生与 512 GB 或 1 TB 驱动器不同的配额值。由于磁盘大小遵循标准制造等级(128、256、512、1024 GB),配额值有效地揭示了存储等级。
  • 使用模式推断:配额和使用量之间的比率揭示了磁盘有多满。随时间变化,这个比率的变化可以揭示用户活动模式。
  • 操作系统识别:不同操作系统以不同方式计算存储配额。Chrome 在 Windows、macOS 和 Linux 上各应用不同的公式和上限。
  • 设备类别识别:2 TB 驱动器的设备可能是桌面或高端笔记本电脑,而 64 GB 暗示预算笔记本电脑或 Chromebook。

阿德莱德大学的研究表明,存储配额值与其他存储相关信号(IndexedDB 可用性、Cache API 行为、持久存储权限状态)结合时,可以将指纹唯一性提高 5-10%。该 API 不需要权限且不产生通知。

这个问题因配额值并非设备上的静态值而更加复杂。它随着磁盘空间的使用和释放而变化,创建了一个可以随时间追踪的缓慢变化的标识符。一个月内配额减少了 20 GB 的用户正在留下一条存储状态轨迹,可以与其他信号关联。

技术背景

navigator.storage.estimate() 方法返回一个 promise,解析为具有两个属性的对象:

  • quota:源可用的总存储空间,以字节为单位。通常是总磁盘空间的百分比,受浏览器特定上限和策略约束。
  • usage:源当前消耗的存储空间,以字节为单位。

Chrome 将配额计算为大约总可用磁盘空间的 60%,并有额外的每源限制。Firefox 使用基于可用空闲空间的不同公式。Safari 施加更严格的每源限制。

配额计算细节

在 Chrome(和基于 Chromium 的浏览器)上,存储配额计算遵循以下近似逻辑:

  1. 确定包含配置文件目录的磁盘分区的总卷大小
  2. 计算该分区的可用空闲空间
  3. 应用基于百分比的公式(临时存储约为总量的 60%,池在源间共享)
  4. 应用每源上限以防止任何单个源消耗整个配额

结果是,两台不同磁盘大小的机器即使运行相同浏览器版本和操作系统,也产生可测量不同的配额值。

IndexedDB 和 Cache API 信号

除了 navigator.storage.estimate() 之外,其他存储 API 也可能泄漏磁盘信息:

  • IndexedDB:最大数据库大小受存储配额影响。尝试存储越来越大的 blob 可以通过成功/失败边界揭示大约的配额。
  • Cache API:类似于 IndexedDB,Cache API 共享相同的存储配额,尝试缓存大响应可以揭示配额限制。
  • 持久存储navigator.storage.persist() 方法请求持久存储(不受逐出影响)。其是否成功以及如何影响配额取决于平台。

值的精度

quota 值以字节为单位返回,提供非常高的精度。512 GB 驱动器可能产生约 307,200,000,000 字节的配额(大约 512 GB 的 60%),而 256 GB 驱动器产生大约 153,600,000,000。精确值取决于实际空闲空间、其他配置文件和操作系统开销,但数量级可靠地区分磁盘大小。

这种精度意味着即使磁盘配置上的微小差异,如不同的分区布局或不同的已用空间量,也会产生可区分的配额值。

跨浏览器差异

不同浏览器以不同方式计算配额:

  • Chrome:总磁盘空间的百分比配每源限制
  • Firefox:基于可用空闲空间,有不同的等级计算
  • Safari:固定的每源限制(通常初始 1 GB,可经用户许可扩展)
  • Edge:与 Chrome 相同(基于 Chromium)

这些差异意味着同一物理设备在不同浏览器中产生不同的配额值,为指纹增加了另一个维度。

常见保护方法及其局限性

VPN 和代理服务器

VPN 对存储配额值没有影响。配额完全由本地磁盘配置和浏览器计算决定。

隐身和隐私浏览

隐私浏览模式改变存储配额行为,但不是以帮助隐私的方式。在隐身模式下,一些浏览器报告显著降低的配额,配额可能与正常模式不同。这种差异本身成为检测隐身模式的信号。

浏览器扩展

扩展可以拦截 navigator.storage.estimate() promise 并返回修改的值:

  • 固定值:返回硬编码的配额(例如始终 100 GB)是可检测的,如果该值不在会话间变化或与其他磁盘相关信号不相关。
  • 四舍五入:将配额四舍五入到常见值降低了精度,但可能与所模拟的浏览器和操作系统组合的预期值不匹配。
  • Promise 拦截:覆盖 navigator.storage.estimate 或其原型可以通过属性描述符检查和原型链检查检测到。

减少磁盘使用

使用较小的分区或磁盘确实会改变配额,但用户不能轻易为了隐私目的而控制磁盘大小。这是一个从根本上难以修改的硬件级特征。

BotBrowser 的引擎级方法

BotBrowser 在浏览器引擎级别控制存储配额响应。当加载指纹配置文件时,所有存储相关查询返回配置文件定义的值,而不是从主机的实际磁盘计算。

基于配置文件的配额值

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

配置文件包含从真实设备捕获的存储配额值。这些值对配置文件的目标设备类别来说是真实的:代表 512 GB SSD 笔记本电脑的配置文件报告与该配置一致的配额值,无论主机的实际磁盘大小。

内部一致的存储信号

BotBrowser 确保所有存储相关 API 产生一致的结果:

  • navigator.storage.estimate() 返回配置文件定义的配额和使用值
  • IndexedDB 存储行为与报告的配额一致
  • Cache API 行为与存储预算一致
  • 配额/使用比率对目标设备类别真实
  • 值在会话中保持稳定

跨平台准确性

因为不同浏览器以不同方式计算配额,BotBrowser 的配置文件包含浏览器特定的配额值。Chrome 配置文件返回适合 Chrome 的配额计算,而浏览器间的底层公式差异在配置文件数据中得到了考虑。

防止主机磁盘泄漏

主要目标是防止主机的磁盘配置通过存储 API 泄漏。无论 BotBrowser 在 128 GB 服务器还是 4 TB 工作站上运行,报告给网页的存储配额值反映配置文件的目标设备,而非实际主机硬件。

使用值控制

usage 值(当前存储消耗)也受控。新加载的配置文件报告最小使用量,值在会话期间随着源存储数据而自然演变,但始终在配置文件配额框架的上下文中。

配置和使用

基本 CLI 用法

加载配置文件时自动提供存储配额保护:

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

不需要额外标志。

Playwright 集成

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: ['--bot-profile=/path/to/profile.enc'],
    headless: true,
  });

  const context = await browser.newContext({ viewport: null });
  const page = await context.newPage();

  const storageInfo = await page.evaluate(async () => {
    const estimate = await navigator.storage.estimate();
    return {
      quota: estimate.quota,
      usage: estimate.usage,
      quotaGB: (estimate.quota / (1024 ** 3)).toFixed(2),
      usageMB: (estimate.usage / (1024 ** 2)).toFixed(2),
    };
  });

  console.log('Storage info:', storageInfo);
  await browser.close();
})();

Puppeteer 集成

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: ['--bot-profile=/path/to/profile.enc'],
    headless: true,
    defaultViewport: null,
  });

  const page = await browser.newPage();
  await page.goto('about:blank');

  const quota = await page.evaluate(async () => {
    const est = await navigator.storage.estimate();
    return { quota: est.quota, usage: est.usage };
  });

  console.log('Quota:', quota.quota, 'bytes');
  console.log('Usage:', quota.usage, 'bytes');
  await browser.close();
})();

与其他保护结合

为获得全面的设备身份控制:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-noise-seed=42 \
       --proxy-server="socks5://user:pass@proxy:1080" \
       --bot-config-timezone="America/Chicago" \
       --user-data-dir="$(mktemp -d)"

验证

使用配置文件启动 BotBrowser 后,验证存储配额值:

const estimate = await navigator.storage.estimate();
const quotaGB = (estimate.quota / (1024 ** 3)).toFixed(2);
const usageMB = (estimate.usage / (1024 ** 2)).toFixed(2);

console.log(`Quota: ${quotaGB} GB (${estimate.quota} bytes)`);
console.log(`Usage: ${usageMB} MB (${estimate.usage} bytes)`);

需要检查:

  1. 配额值对配置文件的目标设备类别真实(而非主机)
  2. 配额不匹配你的实际主机磁盘大小
  3. 使用值合理(新会话低,存储数据后增加)
  4. 值在同一会话的页面重载间保持稳定
  5. 不同配置文件产生不同配额值
  6. 指纹测试工具显示与存储相关的无异常

最佳实践

  1. 使用完整配置文件。 存储配额值必须与设备身份的其余部分一致。声称是移动设备的配置文件不应报告 2 TB 配额。

  2. user-data-dir 与用例匹配。 新的 user-data-dir(使用 mktemp -d)从最小使用量开始。对于持久会话,重用相同的数据目录以使使用值自然演变。

  3. 考虑隐身检测。 如果你的工作流程需要避免隐身检测,注意存储配额行为在正常和隐身模式间不同。BotBrowser 在正常模式下的配置文件报告标准配额值。

  4. 与实际主机验证。 在 BotBrowser 之外在你的主机上运行 navigator.storage.estimate() 以了解你的真实配额值,然后确认 BotBrowser 的配置文件报告不同的值。

常见问题

存储配额是否揭示精确磁盘大小?

不完全是。配额是可用磁盘空间的百分比(Chrome 上约 60%),随磁盘空间使用和释放而变化。但配额值可靠地区分磁盘大小等级(128 GB vs. 256 GB vs. 512 GB vs. 1 TB),使其成为有用的指纹信号。

网站能否在不使用 navigator.storage.estimate() 的情况下探测存储限制?

可以。通过尝试向 IndexedDB 或 Cache API 写入越来越大的 blob,网站可以通过试错发现存储限制。BotBrowser 控制底层配额系统,因此估计 API 和实际存储行为都反映配置文件的配置。

存储配额在隐身模式下会变化吗?

是的。隐身模式通常使用具有不同配额的临时文件系统。隐身模式下的配额值通常显著低于正常模式,可用于检测隐身浏览。

配额作为指纹有多精确?

quota 值以字节为单位返回,提供非常高的精度。但由于值随磁盘空间变化而波动,它最有用的是作为粗略标识符(磁盘大小等级)而非精确标识符。与其他信号结合时,它对整体指纹有重要贡献。

BotBrowser 控制 Persistent Storage API 吗?

navigator.storage.persist() 方法及其相关行为由配置文件控制。配置文件决定持久存储是否可用以及持久性状态如何影响配额计算。

废弃的 webkitStorageInfo API 怎么办?

较旧的 Chromium 版本通过 webkitStorageInfonavigator.webkitTemporaryStorage 暴露存储配额。BotBrowser 配置文件也控制这些旧版 API,确保较旧的指纹脚本也收到一致的值。

存储配额能否揭示我在容器中运行?

容器和虚拟机通常有比物理机器更小的磁盘分配。具有 20 GB 卷的 Docker 容器产生的配额与具有 1 TB SSD 的物理机器非常不同。BotBrowser 基于配置文件的方法确保报告的配额匹配目标设备,而非容器的实际存储。

总结

Storage Manager API 的 navigator.storage.estimate() 暴露了作为持久指纹信号的磁盘特征。配额值通过浏览器特定的计算公式间接揭示磁盘大小、设备类别和操作系统。BotBrowser 通过其配置文件系统在引擎级别控制所有存储配额响应,确保报告的值匹配目标设备且不泄漏关于主机实际磁盘配置的信息。相关保护请参阅 Navigator 属性保护性能时间控制全面配置文件管理

#Storage#Quota#浏览器指纹识别#Privacy#Indexeddb#Disk

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。