# permissions

> The state of 24 browser permissions (granted, denied, prompt), read with navigator.permissions.query, which never asks the user anything. Hashed, not in either id.

Source: https://whiskerjs.com/signals/permissions/ (last updated 2026-10-26)

**permissions at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `permissions` |
| In the visitorId | no (reported, kept out of both ids) |
| structuredId group | none |
| Hashing | always a digest (first 128 bits of SHA-256) |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 2](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Browsers remember which sites may use the camera, send notifications and so on. A page can ask the browser for the current state of a permission without asking the user anything. WhiskerJS asks for 24 of them and keeps only a short hash of the answers. It never requests a permission and never shows a prompt.

## What it reads

The state of each of these permissions from `navigator.permissions.query({ name })`: accelerometer, background-fetch, background-sync, camera, clipboard-read, clipboard-write, display-capture, geolocation, gyroscope, idle-detection, keyboard-lock, local-fonts, magnetometer, microphone, midi, nfc, notifications, payment-handler, persistent-storage, pointer-lock, push, screen-wake-lock, storage-access, window-management.

## How the technique works

1. Without `navigator.permissions.query` the component is `unavailable` / `no_api`.
2. All 24 queries run in parallel. `push` is asked with `userVisibleOnly: true`, the only form browsers accept.
3. Each answer is the state `granted`, `denied` or `prompt`; any other state is `unknown`; a query that throws (the browser does not know that permission) is `unsupported`.
4. If every query is `unsupported`, the component is `unavailable` / `no_value`.
5. Otherwise the value is `name=state;...` in the fixed order above, always hashed (SHA-256, first 128 bits).
6. The notifications state is also kept internally for the [`permissions_inconsistent`](https://whiskerjs.com/docs/integrity/#permissions_inconsistent) check.

## Why it helps

Two things vary: which permission names the browser knows (a browser and version fingerprint of its own) and what the user has granted or refused. Both are useful to your server as context. The second is why it stays out of the ids.

## Stability

Medium: the set of known names changes with browser updates, and the states change whenever the user grants or revokes something. Neither moves the visitorId or the structuredId.

## Browser support and what blocks it

- Chromium browsers ([Chrome](https://whiskerjs.com/browsers/chrome-desktop/), [Edge](https://whiskerjs.com/browsers/edge/), [Brave](https://whiskerjs.com/browsers/brave/), [Samsung Internet](https://whiskerjs.com/browsers/samsung-internet/)) know most of the names.
- [Firefox](https://whiskerjs.com/browsers/firefox/) and [Safari](https://whiskerjs.com/browsers/safari-macos/) implement `navigator.permissions.query` for fewer names; the rest come back `unsupported`.
- On slow or emulated setups the parallel queries can exceed the 1000 ms inner timeout; then the component is `timed_out`.

No browser class excludes it.

## Privacy

Permission states can say a little about how someone uses a site (for example that they allowed notifications). WhiskerJS reads them only, never asks, and reports a digest rather than the list. If you do not need it, leave it out with `collect({ exclude: ['permissions'] })`; since it is outside the ids, that does not change any id.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` |   | digest of the 24 states |
| `unavailable` | `no_api` | no `navigator.permissions.query` |
| `unavailable` | `no_value` | every query was unsupported |
| `unavailable` | `no_digest` | no `crypto.subtle` |
| `excluded` | `caller_excluded` | you excluded it |
| `timed_out` | `timeout`, `aborted` | not all queries answered in time, or budget or abort |

## Example value

In the container test, headless Chromium gave `bcecb787af4b911800b26cdac1d7b6ea` and Firefox `3fb80d81dd1f71b5a083dc4dd324ada7`; in headless WebKit the queries did not finish within 1000 ms and the component was `timed_out` / `timeout`. The same Chromium run also set `permissions_inconsistent`.

## Questions and answers

### Will my visitors see a permission prompt?

No. navigator.permissions.query only reports the current state. WhiskerJS never calls an API that requests a permission, such as getUserMedia, geolocation or Notification.requestPermission.

### Why are permissions not in the id?

The state changes when the user grants or revokes a permission, which is a normal thing to do. In the id, that would change it.

### What is permissions_inconsistent?

An integrity flag that fires when Notification.permission is denied while the permissions query for notifications says prompt, a trait known from headless Chrome. Normal browsers keep the two in step.

## Related pages

- [permissions_inconsistent flag](https://whiskerjs.com/docs/integrity/#permissions_inconsistent): Uses the notifications state read here.
- [storage](https://whiskerjs.com/signals/storage/): Another read-only view of browser settings.
- [Privacy](https://whiskerjs.com/docs/privacy/): No prompts, no network, no storage.
- [keyboard_layout](https://whiskerjs.com/signals/keyboard-layout/): Another component outside the ids.
