Summary
checkPermissionsPolicy in src/rules.ts unconditionally populates the recommendations array with a generic remediation string, even when isGood === true — i.e. when the header already restricts camera, microphone, and geolocation. A developer who has correctly set Permissions-Policy: camera=(), microphone=(), geolocation=() receives a status: 'good' result with an empty findings[], but also a non-empty recommendations[] telling them to set the very policy they already have. Every other rule in the codebase returns recommendations: [] when its status is 'good'; this is the only exception.
Investigation Path
- Read all six source files:
src/rules.ts, src/analyzer.ts, src/fetch.ts, src/index.ts, src/cli.ts, src/types.ts.
- Audited every
return statement in each check* function for structural consistency — specifically that findings and recommendations are both empty arrays when status is 'good'.
- All five other rules are consistent:
checkHSTS — findings and recommendations are only pushed when a condition fails.
checkCSP — starts with const recommendations: string[] = []; pushes conditionally.
checkXFrameOptions — findings: [], recommendations: [] on the good path.
checkXContentTypeOptions — recommendations: score < 10 ? [...] : [].
checkReferrerPolicy — recommendations: isStrong ? [] : [...].
- Found the single exception at
src/rules.ts line 141: checkPermissionsPolicy uses findings: isGood ? [] : [...] (correct) but recommendations: ["Set Permissions-Policy to..."] (unconditional — no ternary guard).
- Traced the effect through
src/cli.ts line 61: for (const rec of h.recommendations) console.log(...) — confirms the "Fix:" line prints regardless of status.
- Ran all three deduplication searches against open and recently-closed issues (searched: "Permissions-Policy recommendations", "good status fix recommendation", "spurious Fix recommendation false positive output") — zero matches.
Evidence
- File:
src/rules.ts lines 121–143, commit 81f8735d167b597ed7a7a88cc7f89ba0b440b07d
export function checkPermissionsPolicy(headers: RawHeaders): HeaderFinding {
const raw = getHeader(headers, 'permissions-policy') ?? getHeader(headers, 'feature-policy');
if (!raw) return { /* ... score: 0, status: 'missing' ... */ };
const lc = raw.toLowerCase();
const hasCam = lc.includes("camera=()");
const hasMic = lc.includes("microphone=()");
const hasGeo = lc.includes("geolocation=()");
const score = (hasCam && hasMic && hasGeo) ? 10 : 5;
const isGood = score === 10;
return {
header: "Permissions-Policy",
score,
maxScore: 10,
status: isGood ? "good" : "warning",
raw,
findings: isGood ? [] : ["Permissions-Policy does not restrict at least camera, microphone, and geolocation"],
recommendations: ["Set Permissions-Policy to camera=(), microphone=(), geolocation=(), and any other features needed by your app"]
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// No ternary guard — always populated, even when isGood === true
};
}
- Current behavior: A correctly configured header returns:
checkPermissionsPolicy({
'permissions-policy': 'camera=(), microphone=(), geolocation=()',
})
// =>
{
header: 'Permissions-Policy',
score: 10,
maxScore: 10,
status: 'good', // ← correct
findings: [], // ← correct
recommendations: [ // ← INCORRECT: should be []
'Set Permissions-Policy to camera=(), microphone=(), geolocation=(), and any other features needed by your app'
]
}
-
Expected behavior: recommendations: [] when isGood === true, matching the contract of every other check function.
-
CLI impact (src/cli.ts line 61):
✓ Permissions-Policy (10/10)
camera=(), microphone=(), geolocation=()
Fix: Set Permissions-Policy to camera=(), microphone=(), geolocation=(), and any other features needed by your app
A developer sees a green ✓ immediately followed by an apparently required "Fix:" — contradictory output.
- Reference: The structural contract is implicit in every other rule and explicit in the
HeaderFinding interface (src/types.ts), where findings and recommendations are described as per-header guidance; both should be empty when the header is correctly configured.
Impact
Developers who have correctly configured Permissions-Policy receive contradictory, noise-introducing output. Two concrete consequences:
-
CLI confusion. The color-coded ✓ (good) and the Fix: recommendation appear on consecutive lines for the same header. A developer reading the output quickly may either (a) wonder if they need to change something that is already correct, potentially regressing their configuration, or (b) dismiss all Fix: lines as low-signal, undermining trust in genuine warnings from other headers.
-
False positives for API consumers. Any library user filtering by h.recommendations.length > 0 to build automated remediation pipelines, CI annotations, or dashboard alerts will receive spurious Permissions-Policy alerts for every site that has already implemented the recommended policy — exactly the sites that should be silent.
This is the most common failure mode for "passed our header audit" workflows: the tool flags something as needing fixing that has already been fixed.
Suggested Remediation
Add the same ternary guard that checkReferrerPolicy already uses (src/rules.ts line 117):
// src/rules.ts — line 141
// Before:
recommendations: ["Set Permissions-Policy to camera=(), microphone=(), geolocation=(), and any other features needed by your app"]
// After:
recommendations: isGood ? [] : ["Set Permissions-Policy to camera=(), microphone=(), geolocation=(), and any other features needed by your app"]
This is a one-line, zero-risk change with no logic impact on the warning or missing paths.
Acceptance Criteria
Claude Code review routine · commit 81f8735d167b597ed7a7a88cc7f89ba0b440b07d · 2026-05-26T00:00:00Z
Summary
checkPermissionsPolicyinsrc/rules.tsunconditionally populates therecommendationsarray with a generic remediation string, even whenisGood === true— i.e. when the header already restrictscamera,microphone, andgeolocation. A developer who has correctly setPermissions-Policy: camera=(), microphone=(), geolocation=()receives astatus: 'good'result with an emptyfindings[], but also a non-emptyrecommendations[]telling them to set the very policy they already have. Every other rule in the codebase returnsrecommendations: []when itsstatusis'good'; this is the only exception.Investigation Path
src/rules.ts,src/analyzer.ts,src/fetch.ts,src/index.ts,src/cli.ts,src/types.ts.returnstatement in eachcheck*function for structural consistency — specifically thatfindingsandrecommendationsare both empty arrays whenstatusis'good'.checkHSTS—findingsandrecommendationsare only pushed when a condition fails.checkCSP— starts withconst recommendations: string[] = []; pushes conditionally.checkXFrameOptions—findings: [], recommendations: []on the good path.checkXContentTypeOptions—recommendations: score < 10 ? [...] : [].checkReferrerPolicy—recommendations: isStrong ? [] : [...].src/rules.tsline 141:checkPermissionsPolicyusesfindings: isGood ? [] : [...](correct) butrecommendations: ["Set Permissions-Policy to..."](unconditional — no ternary guard).src/cli.tsline 61:for (const rec of h.recommendations) console.log(...)— confirms the "Fix:" line prints regardless of status.Evidence
src/rules.tslines 121–143, commit81f8735d167b597ed7a7a88cc7f89ba0b440b07dExpected behavior:
recommendations: []whenisGood === true, matching the contract of every other check function.CLI impact (
src/cli.tsline 61):A developer sees a green
✓immediately followed by an apparently required "Fix:" — contradictory output.HeaderFindinginterface (src/types.ts), wherefindingsandrecommendationsare described as per-header guidance; both should be empty when the header is correctly configured.Impact
Developers who have correctly configured
Permissions-Policyreceive contradictory, noise-introducing output. Two concrete consequences:CLI confusion. The color-coded
✓(good) and theFix:recommendation appear on consecutive lines for the same header. A developer reading the output quickly may either (a) wonder if they need to change something that is already correct, potentially regressing their configuration, or (b) dismiss allFix:lines as low-signal, undermining trust in genuine warnings from other headers.False positives for API consumers. Any library user filtering by
h.recommendations.length > 0to build automated remediation pipelines, CI annotations, or dashboard alerts will receive spurious Permissions-Policy alerts for every site that has already implemented the recommended policy — exactly the sites that should be silent.This is the most common failure mode for "passed our header audit" workflows: the tool flags something as needing fixing that has already been fixed.
Suggested Remediation
Add the same ternary guard that
checkReferrerPolicyalready uses (src/rules.tsline 117):This is a one-line, zero-risk change with no logic impact on the warning or missing paths.
Acceptance Criteria
checkPermissionsPolicy({ 'permissions-policy': 'camera=(), microphone=(), geolocation=()' })returnsrecommendations: []checkPermissionsPolicy({ 'permissions-policy': 'camera=()' })continues to return a non-emptyrecommendations(warning path is unaffected)checkPermissionsPolicy({})continues to returnstatus: 'missing'with a non-emptyrecommendations(missing path is unaffected)expect(checkPermissionsPolicy({ 'permissions-policy': 'camera=(), microphone=(), geolocation=()' }).recommendations).toEqual([])Fix:lineClaude Code review routine · commit
81f8735d167b597ed7a7a88cc7f89ba0b440b07d· 2026-05-26T00:00:00Z