From e73fad697b5b298655323c9315e79faa9b230db3 Mon Sep 17 00:00:00 2001 From: Andrew Hosgood Date: Fri, 24 Apr 2026 10:26:00 +0100 Subject: [PATCH] Add support for Access-Control-Allow-Origin header in Talisman and update CSP rules --- CHANGELOG.md | 1 + tests/test_flask_talisman.py | 38 +++++++++++++++++++++++++-------- tna_utilities/flask/talisman.py | 23 ++++++++++++++++---- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 807887a..2f757f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added option in Flask Talisman to add Adobe Typekit CSP rules with `allow_typekit_content_security_policy=True` +- Allow `Access-Control-Allow-Origin` header to be set in `Talisman` with `allow_cors_origin` ### Changed diff --git a/tests/test_flask_talisman.py b/tests/test_flask_talisman.py index e3ce430..60b3727 100644 --- a/tests/test_flask_talisman.py +++ b/tests/test_flask_talisman.py @@ -31,6 +31,9 @@ def test_naked_app(self): self.assertNotIn("Cross-Origin-Opener-Policy", rv.headers) self.assertNotIn("Cross-Origin-Resource-Policy", rv.headers) + self.assertNotIn("Referrer-Policy", rv.headers) + self.assertNotIn("Access-Control-Allow-Origin", rv.headers) + self.assertIn("Set-Cookie", rv.headers) self.assertIn("session=", rv.headers["Set-Cookie"]) self.assertNotIn("Secure", rv.headers["Set-Cookie"]) @@ -59,6 +62,11 @@ def test_default_talisman_app(self): self.assertIn("Cross-Origin-Resource-Policy", rv.headers) self.assertEqual("same-origin", rv.headers["Cross-Origin-Resource-Policy"]) + self.assertIn("Referrer-Policy", rv.headers) + self.assertEqual( + "strict-origin-when-cross-origin", rv.headers["Referrer-Policy"] + ) + self.assertIn("Set-Cookie", rv.headers) self.assertIn("session=", rv.headers["Set-Cookie"]) self.assertNotIn(" Secure", rv.headers["Set-Cookie"]) # force_https is False @@ -89,7 +97,7 @@ def test_talisman_google_csp(self): self.assertIn("Content-Security-Policy", rv.headers) self.assertIn("default-src 'self';", rv.headers["Content-Security-Policy"]) self.assertIn( - "connect-src 'self' *.google-analytics.com www.googletagmanager.com;", + "connect-src 'self' *.google-analytics.com *.googletagmanager.com *.analytics.google.com;", rv.headers["Content-Security-Policy"], ) self.assertIn( @@ -97,19 +105,19 @@ def test_talisman_google_csp(self): rv.headers["Content-Security-Policy"], ) self.assertIn( - "font-src 'self' *.gstatic.com;", + "font-src 'self' *.gstatic.com data:;", rv.headers["Content-Security-Policy"], ) self.assertIn( - "img-src 'self' img.youtube.com i.ytimg.com www.googletagmanager.com;", + "img-src 'self' img.youtube.com i.ytimg.com *.googletagmanager.com ssl.gstatic.com www.gstatic.com *.google-analytics.com;", rv.headers["Content-Security-Policy"], ) self.assertIn( - "script-src 'self' ajax.googleapis.com *.googleanalytics.com *.google-analytics.com www.youtube.com *.gstatic.com www.googletagmanager.com;", + "script-src 'self' ajax.googleapis.com *.googleanalytics.com *.google-analytics.com www.youtube.com *.gstatic.com *.googletagmanager.com tagmanager.google.com;", rv.headers["Content-Security-Policy"], ) self.assertIn( - "style-src 'self' ajax.googleapis.com fonts.googleapis.com *.gstatic.com;", + "style-src 'self' ajax.googleapis.com fonts.googleapis.com *.gstatic.com googletagmanager.com tagmanager.google.com;", rv.headers["Content-Security-Policy"], ) @@ -144,15 +152,15 @@ def test_talisman_google_and_typekit_csp(self): self.assertIn("Content-Security-Policy", rv.headers) self.assertIn( - "font-src 'self' *.gstatic.com use.typekit.net;", + "font-src 'self' *.gstatic.com data: use.typekit.net;", rv.headers["Content-Security-Policy"], ) self.assertIn( - "style-src 'self' ajax.googleapis.com fonts.googleapis.com *.gstatic.com *.typekit.net;", + "style-src 'self' ajax.googleapis.com fonts.googleapis.com *.gstatic.com googletagmanager.com tagmanager.google.com *.typekit.net;", rv.headers["Content-Security-Policy"], ) self.assertIn( - "img-src 'self' img.youtube.com i.ytimg.com www.googletagmanager.com;", + "img-src 'self' img.youtube.com i.ytimg.com *.googletagmanager.com ssl.gstatic.com www.gstatic.com *.google-analytics.com;", rv.headers["Content-Security-Policy"], ) @@ -201,7 +209,7 @@ def test_talisman_custom_csp_with_google(self): rv.headers["Content-Security-Policy"], ) self.assertIn( - "img-src 'self' img.example.com img.youtube.com i.ytimg.com www.googletagmanager.com;", + "img-src 'self' img.example.com img.youtube.com i.ytimg.com *.googletagmanager.com ssl.gstatic.com www.gstatic.com *.google-analytics.com;", rv.headers["Content-Security-Policy"], ) @@ -231,3 +239,15 @@ def test_talisman_force_https_permanent(self): "https://localhost/foobar?test=1", rv.headers["Location"], ) + + def test_talisman_allow_cors_origin(self): + Talisman(self.app, force_https=False, allow_cors_origin="https://example.com") + + rv = self.test_client.get("/") + + self.assertEqual(rv.status_code, 200) + + self.assertIn("Access-Control-Allow-Origin", rv.headers) + self.assertEqual( + "https://example.com", rv.headers["Access-Control-Allow-Origin"] + ) diff --git a/tna_utilities/flask/talisman.py b/tna_utilities/flask/talisman.py index 61f5f11..408e703 100644 --- a/tna_utilities/flask/talisman.py +++ b/tna_utilities/flask/talisman.py @@ -5,7 +5,10 @@ from ..security import CspGenerator, common_security_headers GOOGLE_CSP_DIRECTIVES = { - "font-src": ["*.gstatic.com"], # Fonts from fonts.google.com + "font-src": [ + "*.gstatic.com", # Fonts from fonts.google.com + "data:", # Fonts for GTM preview mode + ], "frame-src": [ "www.google.com", #