存储配额指纹:磁盘大小与身份
StorageManager.estimate() 如何暴露磁盘大小作为追踪信号,以及如何在浏览器引擎级别控制存储配额响应。
简介
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() 如何工作
navigator.storage.estimate() 方法返回一个 promise,解析为具有两个属性的对象:
quota:源可用的总存储空间,以字节为单位。通常是总磁盘空间的百分比,受浏览器特定上限和策略约束。usage:源当前消耗的存储空间,以字节为单位。
Chrome 将配额计算为大约总可用磁盘空间的 60%,并有额外的每源限制。Firefox 使用基于可用空闲空间的不同公式。Safari 施加更严格的每源限制。
配额计算细节
在 Chrome(和基于 Chromium 的浏览器)上,存储配额计算遵循以下近似逻辑:
- 确定包含配置文件目录的磁盘分区的总卷大小
- 计算该分区的可用空闲空间
- 应用基于百分比的公式(临时存储约为总量的 60%,池在源间共享)
- 应用每源上限以防止任何单个源消耗整个配额
结果是,两台不同磁盘大小的机器即使运行相同浏览器版本和操作系统,也产生可测量不同的配额值。
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)`);
需要检查:
- 配额值对配置文件的目标设备类别真实(而非主机)
- 配额不匹配你的实际主机磁盘大小
- 使用值合理(新会话低,存储数据后增加)
- 值在同一会话的页面重载间保持稳定
- 不同配置文件产生不同配额值
- 指纹测试工具显示与存储相关的无异常
最佳实践
-
使用完整配置文件。 存储配额值必须与设备身份的其余部分一致。声称是移动设备的配置文件不应报告 2 TB 配额。
-
将
user-data-dir与用例匹配。 新的user-data-dir(使用mktemp -d)从最小使用量开始。对于持久会话,重用相同的数据目录以使使用值自然演变。 -
考虑隐身检测。 如果你的工作流程需要避免隐身检测,注意存储配额行为在正常和隐身模式间不同。BotBrowser 在正常模式下的配置文件报告标准配额值。
-
与实际主机验证。 在 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 版本通过 webkitStorageInfo 和 navigator.webkitTemporaryStorage 暴露存储配额。BotBrowser 配置文件也控制这些旧版 API,确保较旧的指纹脚本也收到一致的值。
存储配额能否揭示我在容器中运行?
容器和虚拟机通常有比物理机器更小的磁盘分配。具有 20 GB 卷的 Docker 容器产生的配额与具有 1 TB SSD 的物理机器非常不同。BotBrowser 基于配置文件的方法确保报告的配额匹配目标设备,而非容器的实际存储。
总结
Storage Manager API 的 navigator.storage.estimate() 暴露了作为持久指纹信号的磁盘特征。配额值通过浏览器特定的计算公式间接揭示磁盘大小、设备类别和操作系统。BotBrowser 通过其配置文件系统在引擎级别控制所有存储配额响应,确保报告的值匹配目标设备且不泄漏关于主机实际磁盘配置的信息。相关保护请参阅 Navigator 属性保护、性能时间控制和全面配置文件管理。