The fix, in one snippet
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
Illustrative values. Change the paths, hostnames and options to match your own site before using it.
The sections below explain what is tested, why it matters and the alternatives.
What we test
Scan.now reads the Permissions-Policy header on the response to / and parses it as a structured dictionary, for example camera=(), microphone=(), geolocation=(self). The check passes when a syntactically valid header is present and restricts at least one feature. It is reported as low when the header is absent. It also flags a header written in the old Feature-Policy syntax (camera 'none'), which browsers will not parse as Permissions-Policy, and notes when a legacy Feature-Policy header is present on its own. Features granted to everyone with * are listed for review. Because Chromium implements the header fully while Firefox and Safari support only a subset (mainly through the iframe allow attribute), the protection is strongest for Chrome and Edge users.
Why it matters
Permissions-Policy lets a page declare which powerful features it and its iframes may use. It does not fix a vulnerability by itself; it limits the blast radius of one. If an advert iframe or an injected script asks for the camera, the browser normally shows a permission prompt in your site's name, and a distracted user may accept. With camera=() the request is denied before any prompt appears. The same applies to geolocation, microphone, payment handlers, USB and screen capture. The header also has a privacy role: browsing-topics=() opts the page out of Chrome's Topics API, and interest-cohort=() was the equivalent for the earlier FLoC trial. A site that never uses these features has no reason to leave them enabled. The security headers guide lists the feature names.
How to fix it
Deny everything you do not use. A conservative starting point for a typical content site:
Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()
nginx:
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()" always;
Apache:
Header always set Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=(), browsing-topics=()"
Helmet does not set this header, so in Express add it directly:
app.use((req, res, next) => {
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
next();
});
Cloudflare's Managed Transform "Add security headers" does not add Permissions-Policy; use a Transform Rule that sets a static response header. To allow a feature for your own origin only, use geolocation=(self); to grant it to a specific embed, camera=(self "https://meet.example") plus a matching allow="camera" attribute on the iframe.
Where this fits
Permissions-Policy is check 6 of 13 that the website vulnerability scanner runs under http security headers, ordered the way they are worth fixing. That ordering is the point: Fixing this one while the check above it still fails buys less than it looks like.
Fix this one first
Above it in the same category sits Cross-Origin-Opener-Policy (low), where the page does not set Cross-Origin-Opener-Policy, so windows it opens, or that open it, from other origins keep a reference to it and share its browsing context group. An attacker who has that does not need this, so it is the better use of the same hour.
What fixing this still leaves open
Permissions-Policy closes one route in. Immediately below it: Referrer-Policy, where no Referrer-Policy is set, or it is set to a value that sends full URLs to other sites; Server version disclosure, where the Server header reveals the exact web server software and version, such as Apache/2.4.58 (Ubuntu) or nginx/1.24.0, which lets attackers and bots match it to known vulnerabilities without any probing; X-Content-Type-Options: nosniff, where responses lack X-Content-Type-Options: Nosniff, so browsers may guess a content type and execute a file as script or style when the server said it was something else.
Found in the same scan
The website vulnerability scanner reports this alongside checks from other categories that are at least as serious, including Certificate chain and hostname validation, where the certificate presented for this hostname did not validate: The chain does not reach a trusted root, an intermediate is missing, the name does not match, or the certificate is self-signed or expired, and Exposed .env configuration file, where a .env configuration file is served from the web root. A single run of website vulnerability scanner answers all of them at once.
The first few lines
You are a senior web engineer. I ran a security and SEO scanner against my site and it reported the finding below. Fix it properly rather than suppressing the symptom.
Finding: Permissions-Policy (low severity)
Scanner check id: permissions-policy
The rest of this prompt names the pages and line numbers we found the problem on, the configuration to change, and the constraints a good answer has to respect. It is free, it just needs an account so the work is not scraped wholesale.
Sign in with GoogleSigning in is free and takes one click. We store your email address and nothing else.