From 7845079307120c05cb67cb3f131eff24fdaf37dd Mon Sep 17 00:00:00 2001 From: colin Date: Thu, 3 Jul 2025 18:15:49 -0400 Subject: [PATCH] Fix production site issues with a more permissive CSP policy --- docker/ploughshares/app.py | 50 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/docker/ploughshares/app.py b/docker/ploughshares/app.py index 9c2871a..8ee04f4 100644 --- a/docker/ploughshares/app.py +++ b/docker/ploughshares/app.py @@ -54,19 +54,20 @@ if CSP_CSS_HASH: if CSP_CUSTOM_CSS_HASH: logger.info(f"Using pre-calculated CSP hash for custom CSS: sha256-{CSP_CUSTOM_CSS_HASH}") -# Define the base CSP for UI routes -ui_csp = { - 'default-src': "'none'", # Deny by default - 'script-src': ["'self'"] + ([f"'sha256-{CSP_JS_HASH}'"] if CSP_JS_HASH else []), - 'style-src': ["'self'"] + +# Define a more permissive CSP that works for both UI and API routes +# This is less secure but ensures compatibility with all clients +csp = { + 'default-src': ["'self'", "'unsafe-inline'", "'unsafe-eval'", "data:", "blob:"], + 'script-src': ["'self'", "'unsafe-inline'", "'unsafe-eval'"] + ([f"'sha256-{CSP_JS_HASH}'"] if CSP_JS_HASH else []), + 'style-src': ["'self'", "'unsafe-inline'"] + ([f"'sha256-{CSP_CSS_HASH}'"] if CSP_CSS_HASH else []) + ([f"'sha256-{CSP_CUSTOM_CSS_HASH}'"] if CSP_CUSTOM_CSS_HASH else []), - 'img-src': ["'self'", "data:"], - 'font-src': ["'self'"], - 'connect-src': "'self'", + 'img-src': ["'self'", "data:", "blob:"], + 'font-src': ["'self'", "data:"], + 'connect-src': ["'self'", "*"], 'manifest-src': "'self'", - 'object-src': "'none'", # Explicitly disallow objects - 'frame-ancestors': "'none'", # Prevent framing + 'object-src': "'none'", # Still explicitly disallow objects + 'frame-ancestors': "'none'", # Still prevent framing 'base-uri': "'self'", 'form-action': "'self'" } @@ -75,18 +76,16 @@ ui_csp = { if APP_DOMAIN: logger.info(f"Configuring CSP for domain: {APP_DOMAIN}") # Add domain to connect-src if needed - if APP_DOMAIN not in ui_csp['connect-src']: - if isinstance(ui_csp['connect-src'], list): - ui_csp['connect-src'].append(APP_DOMAIN) - else: - ui_csp['connect-src'] = [ui_csp['connect-src'], APP_DOMAIN] - + if APP_DOMAIN not in csp['connect-src']: + if isinstance(csp['connect-src'], list): + csp['connect-src'].append(APP_DOMAIN) + # Update form-action to include the domain - if isinstance(ui_csp['form-action'], list): - if APP_DOMAIN not in ui_csp['form-action']: - ui_csp['form-action'].append(APP_DOMAIN) + if isinstance(csp['form-action'], list): + if APP_DOMAIN not in csp['form-action']: + csp['form-action'].append(APP_DOMAIN) else: - ui_csp['form-action'] = [ui_csp['form-action'], APP_DOMAIN] + csp['form-action'] = [csp['form-action'], APP_DOMAIN] # Configure Permissions-Policy (formerly Feature-Policy) # Deny access to all browser features that we don't need @@ -132,11 +131,10 @@ additional_headers = { 'Cross-Origin-Opener-Policy': 'same-origin' } -# Initialize Talisman with the static CSP configuration -# We'll handle API routes separately in the after_request handler +# Initialize Talisman with the permissive CSP configuration talisman = Talisman( app, - content_security_policy=ui_csp, + content_security_policy=csp, content_security_policy_nonce_in=['script-src'], feature_policy=permissions_policy, force_https=force_https, @@ -151,7 +149,7 @@ talisman = Talisman( session_cookie_http_only=True ) -# Add CORS headers for API routes and handle CSP +# Add CORS headers for API routes @app.after_request def add_api_headers(response): if request.path.startswith('/api/'): @@ -160,10 +158,6 @@ def add_api_headers(response): response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' response.headers['Cross-Origin-Resource-Policy'] = 'cross-origin' - - # Remove CSP for API routes to ensure compatibility with clients - if 'Content-Security-Policy' in response.headers: - del response.headers['Content-Security-Policy'] else: # For UI routes, add additional security headers for header, value in additional_headers.items():