diff --git a/src/rules.ts b/src/rules.ts index 779cf09..9352c69 100644 --- a/src/rules.ts +++ b/src/rules.ts @@ -94,12 +94,11 @@ export function checkCSP(headers: RawHeaders): HeaderFinding { const findings: string[] = []; const recommendations: string[] = []; - // 'unsafe-inline' is ignored by browsers that support 'strict-dynamic' when a - // nonce/hash is also present — that combination is the recommended Strict CSP - // pattern (the 'unsafe-inline' is a backwards-compat fallback), so don't penalize it. - const hasStrictDynamic = /'strict-dynamic'/i.test(raw); + // Per CSP2, a nonce or hash source in the policy causes 'unsafe-inline' to be + // ignored by all modern browsers — it becomes a harmless backwards-compat + // fallback for CSP1-only browsers. Don't penalize it in that case. const hasNonceOrHash = /'nonce-[^']+'/i.test(raw) || /'sha(?:256|384|512)-[^']+'/i.test(raw); - if (/'unsafe-inline'/i.test(raw) && !(hasStrictDynamic && hasNonceOrHash)) { + if (/'unsafe-inline'/i.test(raw) && !hasNonceOrHash) { score -= 5; findings.push("'unsafe-inline' weakens XSS protection"); recommendations.push("Remove 'unsafe-inline'; use nonces or hashes instead"); diff --git a/test/analyzer.test.ts b/test/analyzer.test.ts index ed22923..e9f393e 100644 --- a/test/analyzer.test.ts +++ b/test/analyzer.test.ts @@ -170,6 +170,18 @@ describe('checkCSP', () => { expect(r.score).toBe(20); }); + it("does not penalize 'unsafe-inline' when nonce present without 'strict-dynamic' (CSP2 makes it a no-op)", () => { + const r = checkCSP({ 'content-security-policy': "script-src 'nonce-abc123' 'unsafe-inline'; form-action 'self'; base-uri 'none'" }); + expect(r.findings.some(f => f.includes('unsafe-inline'))).toBe(false); + expect(r.score).toBe(20); + }); + + it("does not penalize 'unsafe-inline' when hash present without 'strict-dynamic' (CSP2 makes it a no-op)", () => { + const r = checkCSP({ 'content-security-policy': "script-src 'sha256-abc123def456abc123def456abc123def456abc1' 'unsafe-inline'; form-action 'self'; base-uri 'none'" }); + expect(r.findings.some(f => f.includes('unsafe-inline'))).toBe(false); + expect(r.score).toBe(20); + }); + it("still penalizes 'unsafe-inline' when 'strict-dynamic' present without nonce/hash", () => { const r = checkCSP({ 'content-security-policy': "script-src 'strict-dynamic' 'unsafe-inline'" }); expect(r.findings.some(f => f.includes('unsafe-inline'))).toBe(true);