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 那麼簡單。
推薦閱讀:


