From b33b633d55f558f7b3b4dedbd3a21ee0391e7e97 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 05:04:50 +0000 Subject: [PATCH] fix(csp): include object-src in wildcard/bare-scheme directive checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit object-src was missing from the list of sensitive fetch directives that are checked for wildcard (*) and bare-scheme (e.g. https:) sources. A policy like `default-src 'self'; object-src *` would silently pass the wildcard check while allowing plugins to be loaded from any origin — a known attack vector for Flash/Java-based exploits. Adds three test cases: wildcard in object-src, bare scheme in object-src, and a negative case confirming object-src 'none' is not flagged. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_015FdvhyE9g4Z7mtnoWxaziR --- src/rules.ts | 2 +- test/analyzer.test.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/rules.ts b/src/rules.ts index 779cf09..d9a03f2 100644 --- a/src/rules.ts +++ b/src/rules.ts @@ -113,7 +113,7 @@ export function checkCSP(headers: RawHeaders): HeaderFinding { // fetch/navigation directive — not just as the first token of default-src/ // script-src. img-src/style-src/font-src/media-src are intentionally omitted // as a wildcard there is low-risk and commonly legitimate. - const wildcardDirectives = ['default-src', 'script-src', 'connect-src', 'form-action', 'frame-src', 'worker-src']; + const wildcardDirectives = ['default-src', 'script-src', 'connect-src', 'form-action', 'frame-src', 'worker-src', 'object-src']; const wildcarded = wildcardDirectives.filter(d => { const sources = extractCspDirective(raw, d); return sources !== undefined && sources.some(isPermissiveSource); diff --git a/test/analyzer.test.ts b/test/analyzer.test.ts index ed22923..9fc6279 100644 --- a/test/analyzer.test.ts +++ b/test/analyzer.test.ts @@ -214,6 +214,23 @@ describe('checkCSP', () => { expect(r.findings.some(f => /Wildcard or bare-scheme/i.test(f))).toBe(false); }); + it('detects wildcard in object-src', () => { + const r = checkCSP({ 'content-security-policy': "default-src 'self'; form-action 'self'; base-uri 'self'; object-src *" }); + expect(r.findings.some(f => /Wildcard.*object-src/i.test(f))).toBe(true); + expect(r.score).toBeLessThan(20); + }); + + it('detects bare scheme in object-src', () => { + const r = checkCSP({ 'content-security-policy': "default-src 'self'; form-action 'self'; base-uri 'self'; object-src https:" }); + expect(r.findings.some(f => /Wildcard or bare-scheme/i.test(f))).toBe(true); + }); + + it("does not flag restrictive object-src 'none'", () => { + const r = checkCSP({ 'content-security-policy': "default-src 'self'; form-action 'self'; base-uri 'self'; object-src 'none'" }); + expect(r.findings.some(f => /object-src/i.test(f))).toBe(false); + expect(r.score).toBe(20); + }); + it('clean CSP returns score 20', () => { const r = checkCSP({ 'content-security-policy': "default-src 'self'; form-action 'self'; base-uri 'self'" }); expect(r.score).toBe(20);