37 Audits
πŸ”’

Permissions Policy Auditor

Security Auditor

Validates Permissions-Policy header for secure browser feature controls

SecurityHeaders
50 credits
Per check
~30 seconds
Average runtime
Active
Status

How it works

  1. Fetch β€” sends a HEAD request (or GET when HEAD is not supported) to the audited URL as 37AuditsBot. A blocked request ends the audit with 590; any other failure (timeout, DNS, TLS, non-2xx answer) ends it with 500.

  2. Read β€” collects every Permissions-Policy header line and joins them, as browsers do.

  3. Parse β€” splits the header into members and reads each as feature=allowlist. Both forms the specification allows are accepted: an inner list such as camera=(self "https://a.example") and a single item such as camera=*, camera=self or camera="https://a.example". Parameters after ; (for example ;report-to=main) are ignored.

  4. Rules β€” a missing header stops the audit with 400. Otherwise every rule runs: malformed members (401), sensitive features open to every origin (402), unknown feature names (300), expected sensitive features left unset (301) and retired features (302).

  5. Aggregate β€” FAIL wins over WARNING; when every rule passes the audit is SUCCESS (200).

  6. Specification pin β€” every run emits 37A-PermissionsPolicyAuditor-100 (INFORMATION), naming the W3C Permissions Policy Working Draft of 2026-09-22. It does not change the overall status.

Known feature names come from the Standardized, Proposed and Experimental sections of the W3C feature registry, plus features Chromium ships that the registry does not list. Any ch-* name (Client Hints delegation, such as ch-dpr or ch-save-data) is known. Names in the registry's Retired Features section get their own warning instead.

Sensitive features (402): camera, microphone, geolocation, payment, usb, bluetooth, serial, hid, clipboard-read, clipboard-write, display-capture. Expected features (301): camera, microphone, geolocation, payment.

What this auditor validates

Every row maps to one checkRule* method and one CheckCode, in the order the auditor runs them. Rules 401–302 are skipped when the header is missing (400). CheckCode suffixes use status ranges: 100 information, 200 success, 300–399 warning, 400–499 fail, 500–599 error.

CheckCodeStatusWhen it firesRecommendationWhy it matters
37A-PermissionsPolicyAuditor-100INFORMATIONAlways. Records the specification version this auditor implements.β€” (recommendation is null; nothing for the site to change)β€”
37A-PermissionsPolicyAuditor-400FAILThe page sends no Permissions-Policy header, or an empty one.Add "Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()" to the responses of {url}, and allow only the features the page uses.Without this header any script or embedded frame on the page can ask visitors for their camera, microphone or location, so one compromised widget can spy on them under your brand.
37A-PermissionsPolicyAuditor-401FAILA header member is not feature=allowlist syntax (one check per member).Rewrite "{member}" as name=(allowlist), for example camera=(self) or camera=(); browsers drop a header that does not parse.Browsers ignore a Permissions-Policy header they cannot parse, so the protection you meant to set is not applied at all.
37A-PermissionsPolicyAuditor-402FAILA sensitive feature's allowlist contains * (one check per feature).Restrict {feature} to self or the origins that need it, for example {feature}=(self), or disable it with {feature}=().Allowing every origin lets any embedded frame, including ads and third-party widgets, ask visitors for this access under your site's name.
37A-PermissionsPolicyAuditor-300WARNINGA feature name is neither known nor retired (one check per name).Check the spelling of "{feature}", or remove it; browsers ignore features they do not know.Browsers ignore features they do not know, so a misspelled name leaves the feature you meant to restrict open.
37A-PermissionsPolicyAuditor-301WARNINGOne of camera, microphone, geolocation or payment is not set (one check per feature).Add {feature}=() to Permissions-Policy, or {feature}=(self) if the page uses it.An unset feature keeps the browser default, so scripts on the page, including third-party ones, can still ask visitors for that access under your name.
37A-PermissionsPolicyAuditor-302WARNINGThe header sets a retired feature: document-domain or window-placement (one check per feature).window-placement: Rename window-placement to window-management (window-management={allowlist}). document-domain: Remove document-domain={allowlist} from Permissions-Policy; it has no effect.Browsers no longer read a retired feature, so the restriction it was meant to apply is not in effect.
37A-PermissionsPolicyAuditor-200SUCCESSAll rules passed. Message is Permissions-Policy on {url} sets {count} feature(s): {features}.β€”β€”
37A-PermissionsPolicyAuditor-500ERRORThe page could not be fetched (timeout, DNS, TLS, or a non-2xx answer other than a block).Make sure {url} answers with HTTP 2xx, then run the audit again. Last error: {error}.β€”
37A-PermissionsPolicyAuditor-590ERRORThe audited page answered HTTP 403, HTTP 429 or a Cloudflare challenge, so 37AuditsBot was blocked before it could read the page.Allow 37AuditsBot in your firewall or bot protection: allowlist its user agent, or verify its Web Bot Auth signature. See https://www.37audits.com/bot.While 37AuditsBot is blocked this audit cannot look at the site, so problems on it go unreported.

Output Documentation

StatusDescriptionTest logic
SUCCESSThe header is valid and restricts sensitive featuresHeader present, every member parses, no sensitive feature allows *, every name is known, camera, microphone, geolocation and payment are set (200).
WARNINGThe header works but leaves gapsUnknown feature name (300); expected sensitive feature not set (301); retired feature (302).
FAILThe header is missing, broken or too openMissing or empty header (400); malformed member (401); sensitive feature open to every origin (402).
ERRORThe auditor could not read the pageFetch failure or non-2xx answer (500); site blocked 37AuditsBot (590).
INFORMATIONSpecification pinAlways emitted (100); does not change overall status.

Risks and Considerations

Why the header matters

  • Without a policy, the page and every frame it embeds with an allow attribute can request powerful features. An injected script or a compromised third-party widget can then prompt visitors for their camera, microphone or location, and the prompt carries your site's name.

  • feature=() disables a feature everywhere on the page; feature=(self) allows it only for your own origin. Allow the minimum the page needs.

Parsing is all-or-nothing

  • The header is a Structured Fields dictionary. A browser that cannot parse it drops the whole header, so one malformed member removes every restriction in it.

Feature names change

  • Browsers silently ignore unknown names, so a typo leaves the feature at its default. Retired names such as window-placement (renamed window-management in Chrome 111) no longer have any effect.

  • The known-feature list is a copy of the W3C registry. A brand-new feature may be reported as unknown (300) until the list is refreshed.

What this auditor does not check

  • The legacy Feature-Policy header and the allow attribute on iframes are not read.

  • Only the audited URL is fetched; other pages of the site may send a different policy.

This documentation is built and hosted on Mintlify, a developer documentation platform.

Source: docs.37audits.com

Ready to start auditing?

Add this auditor to your monitoring setup and start identifying issues on your websites today.

Β© 2026 37 Audits. All rights reserved. Audit your websites with confidence.

Supported by

Made with ❀️ in Floripa