From aa2eb537767d5b7ded9e13973a2a4199faf3c934 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sun, 31 Aug 2025 20:22:05 +0200 Subject: [PATCH] fix(csp): always include internal CDN in script-src/connect-src and update tests accordingly See ChatGPT conversation: https://chatgpt.com/share/68b492b8-847c-800f-82a9-fb890d4add7f --- filter_plugins/csp_filters.py | 7 ++-- tests/unit/filter_plugins/test_csp_filters.py | 36 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/filter_plugins/csp_filters.py b/filter_plugins/csp_filters.py index eae55f26..23b71dc0 100644 --- a/filter_plugins/csp_filters.py +++ b/filter_plugins/csp_filters.py @@ -139,10 +139,9 @@ class FilterModule(object): if matomo_domain: tokens.append(f"{web_protocol}://{matomo_domain}") - # Allow the loading of js from the cdn - if self.is_feature_enabled(applications, 'logout', application_id) or self.is_feature_enabled(applications, 'desktop', application_id): - domain = domains.get('web-svc-cdn')[0] - tokens.append(f"{web_protocol}://{domain}") + # Allow fetching from internal CDN as default for all applications + domain = domains.get('web-svc-cdn')[0] + tokens.append(f"{web_protocol}://{domain}") # ReCaptcha integration: allow loading scripts from Google if feature enabled if self.is_feature_enabled(applications, 'recaptcha', application_id): diff --git a/tests/unit/filter_plugins/test_csp_filters.py b/tests/unit/filter_plugins/test_csp_filters.py index 94f93305..1d0e86a6 100644 --- a/tests/unit/filter_plugins/test_csp_filters.py +++ b/tests/unit/filter_plugins/test_csp_filters.py @@ -45,7 +45,7 @@ class TestCspFilters(unittest.TestCase): "body { background: #fff; }", ] } - } + } }, }, 'app2': {} @@ -85,18 +85,16 @@ class TestCspFilters(unittest.TestCase): header = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https') # Ensure core directives are present self.assertIn("default-src 'self';", header) - # script-src directive should include unsafe-eval, Matomo domain and CDN (hash may follow) + # script-src-elem should include 'self', Matomo, internes CDN und explizite Whitelist-CDN + self.assertIn("script-src-elem 'self'", header) + self.assertIn("https://matomo.example.org", header) + self.assertIn("https://cdn.example.org", header) # internes CDN + self.assertIn("https://cdn.example.com", header) # Whitelist + # script-src directive should include unsafe-eval + self.assertIn("script-src 'self' 'unsafe-eval'", header) + # connect-src directive self.assertIn( - "script-src-elem 'self' https://matomo.example.org https://cdn.example.com", - header - ) - self.assertIn( - "script-src 'self' 'unsafe-eval'", - header - ) - # connect-src directive unchanged (no inline hash) - self.assertIn( - "connect-src 'self' https://matomo.example.org https://api.example.com;", + "connect-src 'self' https://matomo.example.org https://cdn.example.org https://api.example.com;", header ) # ends with img-src @@ -106,8 +104,9 @@ class TestCspFilters(unittest.TestCase): header = self.filter.build_csp_header(self.apps, 'app2', self.domains) # default-src only contains 'self' self.assertIn("default-src 'self';", header) - # no external URLs - self.assertNotIn('http', header) + self.assertIn('https://cdn.example.org', header) + self.assertNotIn('matomo.example.org', header) + self.assertNotIn('www.google.com', header) # ends with img-src self.assertTrue(header.strip().endswith('img-src * data: blob:;')) @@ -154,7 +153,6 @@ class TestCspFilters(unittest.TestCase): style_hash = self.filter.get_csp_hash("body { background: #fff; }") self.assertNotIn(style_hash, header) - def test_build_csp_header_recaptcha_toggle(self): """ When the 'recaptcha' feature is enabled, 'https://www.google.com' @@ -185,7 +183,7 @@ class TestCspFilters(unittest.TestCase): self.domains['web-app-desktop'] = ['domain-example.com'] header = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https') - # Expect '*.domain-example.com' in the frame-ancestors directive + # Expect 'domain-example.com' in the frame-ancestors directive self.assertRegex( header, r"frame-ancestors\s+'self'\s+domain-example\.com;" @@ -194,9 +192,9 @@ class TestCspFilters(unittest.TestCase): # Now disable the feature and rebuild self.apps['app1']['features']['desktop'] = False header_no = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https') - # Should no longer contain the wildcarded sld.tld - self.assertNotIn("*.domain-example.com", header_no) + # Should no longer contain the SLD+TLD + self.assertNotIn("domain-example.com", header_no) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main()