navigator.permissions.query() 暴露约 15 种权限的 granted/prompt/denied 状态——无需操作的指纹信号。
多数指纹识别技术都需要用到 Canvas、GPU 或音频缓冲区。而 Permissions API 什么都不需要——它原本是为了让页面能礼貌地检查"我有没有地理位置权限",然后再决定是否弹窗询问;但只要跨浏览器支持的十几种权限名称逐一查询,得到的答案本身就会变成一种结构化的小型指纹——没有弹窗、没有点击,也完全不触碰任何像素。
核心要点
navigator.permissions.query({name})会返回某项权限的PermissionStatus——granted、denied或prompt——全程不会向用户弹出任何对话框。- Chromium 支持大约 15 到 17 种可查询的权限名称;Firefox 和 Safari 支持的少得多,因此"哪些名称能正常解析而不是报错"本身就是一种浏览器/引擎信号。
- 每项权限返回的
granted/denied/prompt状态,混合了该站点的历史记录与操作系统层面的设置,因此这个组合向量是一种半持久的、按来源划分的指纹贡献。 - 权限名称被枚举的顺序,以及哪些名称会因不受支持而抛出
TypeError,因引擎和版本而异——在状态本身之上,又叠加了一层支持与顺序指纹。 - BrowserInsight 的指纹检测会将这一信号与你暴露的其他信号一并展示,方便你看清它与 Canvas、WebGL 和字体信号如何叠加。
navigator.permissions.query() 到底做了什么
Permissions API 由 W3C Permissions 规范 标准化,它让脚本能够检查权限状态,而不必触发地理位置或通知这类功能通常会弹出的授权提示。调用方式如下:
const status = await navigator.permissions.query({ name: 'geolocation' });
console.log(status.state); // "granted" | "denied" | "prompt"
不会弹出任何对话框。浏览器只是如实报告它已经知道的信息:该来源之前是否已获得地理位置授权、是否已被明确拒绝,或者如果真的请求该功能,是否需要弹窗询问用户。这正是这个 API 存在的意义——让网站只在真的需要询问时才显示"启用定位",而不是靠猜测。
遍历所有权限名称进行查询
从指纹识别的角度来看,有意思的地方在于该 API 接受许多不同的权限名称,脚本只需简单地遍历它们即可:
// 示意性示例——以下并非每个名称在每种浏览器中都受支持
const candidateNames = [
'geolocation', 'notifications', 'push', 'midi', 'camera', 'microphone',
'background-sync', 'persistent-storage', 'ambient-light-sensor',
'accelerometer', 'gyroscope', 'magnetometer', 'screen-wake-lock',
'clipboard-read', 'clipboard-write', 'display-capture', 'nfc',
];
async function fingerprintPermissions() {
const results = {};
for (const name of candidateNames) {
try {
const status = await navigator.permissions.query({ name });
results[name] = status.state; // granted | denied | prompt
} catch {
results[name] = 'unsupported'; // 该名称抛出了错误——浏览器不识别它
}
}
return results; // 与其他信号一同送入哈希函数
}
这个循环会产生两条相互独立的轴线。第一条是哪些名称能成功解析、哪些会抛出错误——这是一种支持性指纹,更接近特征检测而非状态本身,与浏览器引擎和版本大致直接对应。第二条是每个受支持名称实际返回的状态——这一部分由用户在该来源上的历史记录以及操作系统层面的权限设置决定,因此即便在完全相同的浏览器上,也会因人而异。
为什么不同浏览器的结果会不同
基于 Chromium 的浏览器(Chrome、Edge、Opera、Brave)能识别的名称集合最广——根据版本不同大约有 15 到 17 种,其中包括好几种其他引擎完全不通过这个 API 暴露的传感器和硬件相关权限,比如 accelerometer、magnetometer 和 ambient-light-sensor。Firefox 支持的集合小得多——只有 geolocation、notifications、persistent-storage 和 push 是可靠支持的,查询 Chromium 专属的名称只会抛出错误而不会正常解析。Safari 的 WebKit 实现支持范围更窄,无论是采用速度还是识别的名称数量,历来都落后于前两者。
这种差距并非厂商疏忽,而是每一项传感器相关权限都对应引擎实际实现的某个设备 API,所以 navigator.permissions.query({ name: 'magnetometer' }) 在 Firefox 上抛出错误,是 Firefox 压根没有实现该权限所依附的 Generic Sensor API 的直接、可靠后果。对指纹识别而言,实际结果是:只需极少代码,支持的名称集合就能大幅缩小浏览器族系与版本的范围——这与功能与 API 支持差异通常会泄露引擎身份的原理类似,只不过这里被检测的"功能"正是权限系统本身。
值得区分的是另一个完全不同、互不相关的权限模型:一个已安装扩展程序被允许做什么(读取每个页面、访问 Cookie、运行脚本),是由扩展自身的 manifest 权限决定的,而不是由 navigator.permissions.query() 决定——关于这个独立且宽泛得多的授权机制如何运作,参见浏览器扩展隐私风险。
状态向量作为熵值来源
除了哪些名称能解析之外,每个名称最终解析到的状态又构成了第二层信号。大多数用户从未明确授予或拒绝过大多数权限,因此常见情况下几乎所有权限都是 prompt——这是一种低熵的默认状态。但只要用户与某项权限有过交互——比如同意过某站点的地理位置请求、在浏览器设置里全局屏蔽了通知、给某个网页应用授予了持久存储权限——对应条目就会翻转为 granted 或 denied,并一直保持这个状态,直到用户再次更改。
这使得该向量成为一种混合信号:主体部分来自支持模式所构成的浏览器/引擎指纹,再叠加一小部分但确实存在的、来自用户真实权限历史的贡献。结合Canvas、WebGL 或媒体设备数量等其他被动信号,它又增加了一条基本独立的轴线——脚本只需一次异步调用即可免费读取,且不会触发任何属于它自己的权限提示。
缓解措施
主流浏览器中并没有专门的"禁用 permissions.query"开关,因为这个 API 存在的目的正是让正规网站避免过度弹窗打扰用户——移除它只会让每一个受权限限制的功能体验变得更差。现实中的缓解措施与那些普遍约束基于渲染的指纹技术的措施相同:
- Firefox 的
privacy.resistFingerprinting作为其整体趋同策略的一部分,会标准化若干与权限相关的行为,减少支持模式相对 Firefox 基准的差异程度。 - 尽量减少你实际授予的权限。 每一项你实际授予或明确拒绝的权限,都会变成一个稳定、可被查询的位。拒绝你不需要的权限,并在浏览器的站点设置中撤销陈旧的授权,能让你的向量更多地保持在低熵的
prompt默认值上。 - 有意选择支持名称集合更窄的浏览器。 这是一个真实的权衡,而非无代价的胜利——Safari 和 Firefox 更窄的支持范围,也意味着通过这个特定 API 能被指纹识别的信息更少,代价是部分网页功能会有所退化。
- 检查自己暴露了什么。 运行 BrowserInsight 的指纹检测,同时查看你的浏览器目前暴露的权限状态、Canvas、WebGL 及其他信号。
常见问题
查询权限需要用户交互或弹出提示吗?
不需要。navigator.permissions.query() 只会报告已有的状态,不会显示任何对话框。只有真正使用受权限限制的功能时(比如调用 getCurrentPosition() 获取地理位置),才会触发提示,而且只在状态为 prompt 而非已经是 granted 或 denied 时才会弹出。
这项技术单独就能识别出我吗?
不太可靠。大多数用户的大多数权限都停留在 prompt 默认状态,因此状态向量单独来看熵值有限。它对追踪者最大的价值在于与浏览器支持模式(与引擎/版本相关)以及 Canvas、字体等其他被动信号结合使用。
为什么有些权限名称会报错,而不是返回状态?
因为浏览器压根没有实现底层功能。在完全不支持 Generic Sensor API 的浏览器上,navigator.permissions.query({ name: 'magnetometer' }) 会抛出错误,因为对于该引擎中根本不存在的功能,也就不存在可供检查的权限系统。
resistFingerprinting 能阻止这项技术吗?
Firefox 的 privacy.resistFingerprinting 作为其整体趋同策略的一部分,会减少权限相关行为中的部分差异,但底层 API 及其状态报告行为依然存在——它是缩小了信号,而非彻底消除它,这与它对 Canvas 和布局 API 的处理方式是一样的。
结语
Permissions API 是一项聪明而实用的小小便利——先检查再询问——但它恰好免费泄露了两条独立信号:浏览器究竟识别哪些权限名称,以及每一个名称当前处于什么状态。单独看都不算显眼,但叠加到页面无需征得同意就能读取的数十种其他被动信号之上,它再一次提醒我们:"指纹识别"远远不止 Canvas 和 WebGL 那么简单。
推荐阅读:


