diff --git a/Gemfile b/Gemfile
index 4a642b3b4bf..0bd50ce9ffe 100644
--- a/Gemfile
+++ b/Gemfile
@@ -138,9 +138,6 @@ gem "rack-protection", "~> 3.2.0"
# https://github.com/kickstarter/rack-attack
gem "rack-attack", "~> 6.7.0"
-# CSP headers
-gem "secure_headers", "~> 7.1.0"
-
# Browser detection for incompatibility checks
gem "browser", "~> 6.2.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index ce65a0fc6fc..c14ea7025d6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1167,7 +1167,6 @@ GEM
nokogiri (>= 1.16.8)
scimitar (2.11.0)
rails (>= 7.0)
- secure_headers (7.1.0)
securerandom (0.4.1)
selenium-devtools (0.138.0)
selenium-webdriver (~> 4.2)
@@ -1515,7 +1514,6 @@ DEPENDENCIES
rubytree (~> 2.1.0)
sanitize (~> 7.0.0)
scimitar (~> 2.11)
- secure_headers (~> 7.1.0)
selenium-devtools
selenium-webdriver (~> 4.20)
semantic (~> 1.6.1)
@@ -1948,7 +1946,6 @@ CHECKSUMS
safety_net_attestation (0.4.0) sha256=96be2d74e7ed26453a51894913449bea0e072f44490021545ac2d1c38b0718ce
sanitize (7.0.0) sha256=269d1b9d7326e69307723af5643ec032ff86ad616e72a3b36d301ac75a273984
scimitar (2.11.0) sha256=77cf779a843be7d572046acdcf0a1829bd3b1c33db993fa83faf7f1863d8c625
- secure_headers (7.1.0) sha256=6b1f9d5f9507af2948f4636452c41c09371927836396c2185438ffdf0a731124
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
selenium-devtools (0.138.0) sha256=596c08e114342dd89513bc3d18bbb2ae39e532864dbc25c09a48fb9922fc5b7b
selenium-webdriver (4.34.0) sha256=ec7bb718cbe66fe2b247d8ca5e6ba26caed0976d76579d7cb2fadd8dae8b271e
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c5e84856c4c..8ddad78f7ee 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -56,6 +56,7 @@ class ApplicationController < ActionController::Base
include OpenProjectErrorHelper
include Security::DefaultUrlOptions
include OpModalFlashable
+ include DynamicContentSecurityPolicy
layout "base"
diff --git a/lib/open_project/patches/secure_headers_turbo_aware_nonce.rb b/app/controllers/concerns/dynamic_content_security_policy.rb
similarity index 63%
rename from lib/open_project/patches/secure_headers_turbo_aware_nonce.rb
rename to app/controllers/concerns/dynamic_content_security_policy.rb
index 64571d7bcd8..4d9e015a0c9 100644
--- a/lib/open_project/patches/secure_headers_turbo_aware_nonce.rb
+++ b/app/controllers/concerns/dynamic_content_security_policy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -25,18 +27,23 @@
#
# See COPYRIGHT and LICENSE files for more details.
#++
-#
-module OpenProject::Patches::SecureHeadersTurboAwareNonce
- def content_security_policy_script_nonce(request)
- if request.env["HTTP_TURBO_REFERRER"].present?
- request.env[SecureHeaders::NONCE_KEY] ||= request.env["HTTP_X_TURBO_NONCE"]
+module DynamicContentSecurityPolicy
+ ##
+ # Dynamically append sources to CSP directives
+ # This replaces the secure_headers named append functionality
+ def append_content_security_policy_directives(directives)
+ current_policy = current_content_security_policy
+ directives.each do |directive, source_values|
+ current_value = current_policy.send(directive) || current_policy.default_src
+ new_values =
+ if current_value == %w('none') # rubocop:disable Lint/PercentStringArray
+ source_values.compact.uniq
+ else
+ (current_value + source_values).compact.uniq
+ end
+
+ request.content_security_policy.send(directive, *new_values)
end
-
- super
end
end
-
-OpenProject::Patches.patch_gem_version "secure_headers", "7.1.0" do
- SecureHeaders.singleton_class.prepend OpenProject::Patches::SecureHeadersTurboAwareNonce
-end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 38f86560e68..88dc6c9fb0e 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -219,6 +219,12 @@ module ApplicationHelper
end
end
+ # Backward compatibility helper for secure_headers gem migration
+ # Rails built-in CSP equivalent of nonced_javascript_tag
+ def nonced_javascript_tag(**, &)
+ javascript_tag(nonce: true, **, &)
+ end
+
def to_path_param(path)
path.to_s
end
diff --git a/app/helpers/frontend_asset_helper.rb b/app/helpers/frontend_asset_helper.rb
index 38772224edd..b873ed0ea3a 100644
--- a/app/helpers/frontend_asset_helper.rb
+++ b/app/helpers/frontend_asset_helper.rb
@@ -66,7 +66,7 @@ module FrontendAssetHelper
end
def nonced_javascript_include_tag(path, **)
- javascript_include_tag(path, nonce: content_security_policy_script_nonce, **)
+ javascript_include_tag(path, nonce: content_security_policy_nonce, **)
end
private
diff --git a/app/helpers/secure_headers_helper.rb b/app/helpers/secure_headers_helper.rb
index cebd0d6b9f2..826ce6626b8 100644
--- a/app/helpers/secure_headers_helper.rb
+++ b/app/helpers/secure_headers_helper.rb
@@ -29,10 +29,8 @@
module SecureHeadersHelper
##
# Output a rails +csp_meta_tag+ compatible tag
- # while we're still using the +secure_headers+ gem.
+ # using Rails built-in CSP functionality.
def secure_header_csp_meta_tag
- tag :meta,
- name: "csp-nonce",
- content: content_security_policy_script_nonce
+ csp_meta_tag
end
end
diff --git a/app/views/layouts/_common_head.html.erb b/app/views/layouts/_common_head.html.erb
index 57d37e7add5..ce5f79f98d1 100644
--- a/app/views/layouts/_common_head.html.erb
+++ b/app/views/layouts/_common_head.html.erb
@@ -31,7 +31,7 @@
<%= render "common/favicons" %>
<%# Allow gon output when necessary %>
-<%= include_gon(nonce: content_security_policy_script_nonce) %>
+<%= include_gon(nonce: content_security_policy_nonce) %>
<%# Include CLI assets (development) or prod build assets %>
<%= include_frontend_assets %>
diff --git a/config/application.rb b/config/application.rb
index 8dfadd67d4d..67611fcb0ec 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -125,9 +125,6 @@ module OpenProject
# http://stackoverflow.com/questions/4590229
config.middleware.use Rack::TempfileReaper
- # Move secure_headers middleware to after the ShowExceptions
- config.middleware.move_after ActionDispatch::ShowExceptions, SecureHeaders::Middleware
-
# Add lookbook preview paths when enabled
if OpenProject::Configuration.lookbook_enabled?
config.paths.add Primer::ViewComponents::Engine.root.join("app/components").to_s, eager_load: true
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index b3076b38fe1..e3b844bc760 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -1,25 +1,100 @@
+# frozen_string_literal: true
+
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy.
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header
-# Rails.application.configure do
-# config.content_security_policy do |policy|
-# policy.default_src :self, :https
-# policy.font_src :self, :https, :data
-# policy.img_src :self, :https, :data
-# policy.object_src :none
-# policy.script_src :self, :https
-# policy.style_src :self, :https
-# # Specify URI for violation reports
-# # policy.report_uri "/csp-violation-report-endpoint"
-# end
-#
-# # Generate session nonces for permitted importmap, inline scripts, and inline styles.
-# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
-# config.content_security_policy_nonce_directives = %w(script-src style-src)
-#
-# # Report violations without enforcing the policy.
-# # config.content_security_policy_report_only = true
-# end
+Rails.application.config.after_initialize do
+ Rails.application.configure do
+ config.content_security_policy do |policy|
+ # Valid for assets
+ assets_src = ["'self'"]
+ asset_host = OpenProject::Configuration.rails_asset_host
+ assets_src << asset_host if asset_host.present?
+
+ # Valid for iframes
+ frame_src = %w['self' https://player.vimeo.com https://www.youtube.com] # rubocop:disable Lint/PercentStringArray
+ frame_src << OpenProject::Configuration[:security_badge_url]
+
+ # Default src
+ default_src = %w('self') # rubocop:disable Lint/PercentStringArray
+
+ # Attachment uploaders
+ default_src += OpenProject::Configuration.remote_storage_hosts
+
+ # Chargebee self-service
+ frame_src += [
+ "https://js.chargebee.com/",
+ "#{OpenProject::Configuration.enterprise_chargebee_site}.chargebee.com"
+ ]
+
+ default_src << "#{OpenProject::Configuration.enterprise_chargebee_site}.chargebee.com"
+
+ # Allow requests to CLI in dev mode
+ connect_src = default_src + [OpenProject::Configuration.enterprise_trial_creation_host]
+
+ # Rules for media (e.g. video sources)
+ media_src = default_src
+ media_src << asset_host if asset_host.present?
+
+ if OpenProject::Configuration.appsignal_frontend_key
+ connect_src += ["https://appsignal-endpoint.net"]
+ end
+
+ # Add proxy configuration for Angular CLI to csp
+ if FrontendAssetHelper.assets_proxied?
+ proxied = ["ws://#{Setting.host_name}", "http://#{Setting.host_name}",
+ FrontendAssetHelper.cli_proxy.sub("http", "ws"), FrontendAssetHelper.cli_proxy]
+ connect_src += proxied
+ assets_src += proxied
+ media_src += proxied
+ end
+
+ # Allow to extend the script-src in specific situations
+ script_src = assets_src + %w(js.chargebee.com)
+
+ # Allow unsafe-eval for rack-mini-profiler
+ if Rails.env.development? && ENV.fetch("OPENPROJECT_RACK_PROFILER_ENABLED", false)
+ script_src += %w('unsafe-eval') # rubocop:disable Lint/PercentStringArray
+ end
+
+ # Allow ANDI bookmarklet to run in development mode
+ # https://www.ssa.gov/accessibility/andi/help/install.html
+ if Rails.env.development?
+ script_src += ["https://www.ssa.gov"]
+ assets_src += ["https://www.ssa.gov"]
+ end
+
+ # Configure CSP directives
+ policy.default_src(*default_src)
+ policy.base_uri("'self'")
+ policy.font_src(*assets_src, "data:", "'self'")
+ policy.form_action(*default_src)
+ policy.frame_src(*frame_src, "'self'")
+ policy.frame_ancestors("'self'")
+ policy.img_src("*", "data:", "blob:")
+ policy.script_src(*script_src)
+ policy.script_src_attr("'none'")
+ policy.style_src(*assets_src, "'unsafe-inline'")
+ policy.object_src(OpenProject::Configuration[:security_badge_url])
+ policy.connect_src(*connect_src)
+ policy.media_src(*media_src)
+ end
+
+ # Generate session nonces for permitted importmap, inline scripts, and inline styles.
+ # This handles Turbo integration natively
+ config.content_security_policy_nonce_generator = lambda do |request|
+ # Use Turbo nonce if available (for Turbo navigation)
+ if request.env["HTTP_TURBO_REFERRER"].present? && request.env["HTTP_X_TURBO_NONCE"].present?
+ request.env["HTTP_X_TURBO_NONCE"]
+ else
+ # Generate a new nonce based on session
+ SecureRandom.base64(16)
+ end
+ end
+
+ config.content_security_policy_nonce_directives = %w(script-src)
+ end
+end
diff --git a/config/initializers/lookbook.rb b/config/initializers/lookbook.rb
index eb74d34c9aa..d6d167b170b 100644
--- a/config/initializers/lookbook.rb
+++ b/config/initializers/lookbook.rb
@@ -22,49 +22,4 @@ Rails.application.configure do
# Show notes first, all other panels next
config.lookbook.preview_inspector.drawer_panels = [:notes, "*"]
config.lookbook.ui_theme = "blue"
-
- SecureHeaders::Configuration.named_append(:lookbook) do
- proxied =
- if FrontendAssetHelper.assets_proxied?
- ["ws://#{Setting.host_name}", "http://#{Setting.host_name}",
- FrontendAssetHelper.cli_proxy.sub("http", "ws"), FrontendAssetHelper.cli_proxy]
- else
- []
- end
-
- {
- script_src: proxied + %w('unsafe-eval' 'unsafe-inline' 'self'), # rubocop:disable Lint/PercentStringArray
- script_src_elem: proxied + %w('unsafe-eval' 'unsafe-inline' 'self'), # rubocop:disable Lint/PercentStringArray
- style_src: proxied + %w('self' 'unsafe-inline'), # rubocop:disable Lint/PercentStringArray
- style_src_attr: proxied + %w('self' 'unsafe-inline') # rubocop:disable Lint/PercentStringArray
- }
- end
-
- # rubocop:disable Lint/ConstantDefinitionInBlock
- module LookbookCspExtender
- extend ActiveSupport::Concern
-
- included do
- before_action do
- use_content_security_policy_named_append :lookbook
- end
- end
- end
- # rubocop:enable Lint/ConstantDefinitionInBlock
-
- Rails.application.reloader.to_prepare do
- Lookbook.add_input_type(:octicon, "lookbook/previews/inputs/octicon")
-
- [
- Lookbook::ApplicationController,
- Lookbook::PreviewController,
- Lookbook::PreviewsController,
- Lookbook::PageController,
- Lookbook::PagesController,
- Lookbook::InspectorController,
- Lookbook::EmbedsController
- ].each do |controller|
- controller.include LookbookCspExtender
- end
- end
end
diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb
deleted file mode 100644
index 6e0d364215f..00000000000
--- a/config/initializers/secure_headers.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-# frozen_string_literal: true
-
-#-- copyright
-# OpenProject is an open source project management software.
-# Copyright (C) the OpenProject GmbH
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License version 3.
-#
-# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
-# Copyright (C) 2006-2013 Jean-Philippe Lang
-# Copyright (C) 2010-2013 the ChiliProject Team
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# See COPYRIGHT and LICENSE files for more details.
-#++
-
-# rubocop:disable Lint/PercentStringArray
-Rails.application.config.after_initialize do
- SecureHeaders::Configuration.default do |config|
- config.cookies = {
- secure: true,
- httponly: true
- }
-
- # Let Rails ActionDispatch::SSL middleware handle the Strict-Transport-Security header
- config.hsts = SecureHeaders::OPT_OUT
-
- config.x_frame_options = "SAMEORIGIN"
- config.x_content_type_options = "nosniff"
- config.x_xss_protection = "1; mode=block"
- config.x_permitted_cross_domain_policies = "none"
- config.referrer_policy = "origin-when-cross-origin"
-
- # Valid for assets
- assets_src = ["'self'"]
- asset_host = OpenProject::Configuration.rails_asset_host
- assets_src << asset_host if asset_host.present?
-
- # Valid for iframes
- frame_src = %w['self' https://player.vimeo.com https://www.youtube.com]
- frame_src << OpenProject::Configuration[:security_badge_url]
-
- # Default src
- default_src = %w('self')
-
- # Attachment uploaders
- default_src += OpenProject::Configuration.remote_storage_hosts
-
- # Chargebee self-service
- frame_src += [
- "https://js.chargebee.com/",
- "#{OpenProject::Configuration.enterprise_chargebee_site}.chargebee.com"
- ]
-
- default_src << "#{OpenProject::Configuration.enterprise_chargebee_site}.chargebee.com"
-
- # Allow requests to CLI in dev mode
- connect_src = default_src + [OpenProject::Configuration.enterprise_trial_creation_host]
-
- # Rules for media (e.g. video sources)
- media_src = default_src
- media_src << asset_host if asset_host.present?
-
- if OpenProject::Configuration.appsignal_frontend_key
- connect_src += ["https://appsignal-endpoint.net"]
- end
-
- # Add proxy configuration for Angular CLI to csp
- if FrontendAssetHelper.assets_proxied?
- proxied = ["ws://#{Setting.host_name}", "http://#{Setting.host_name}",
- FrontendAssetHelper.cli_proxy.sub("http", "ws"), FrontendAssetHelper.cli_proxy]
- connect_src += proxied
- assets_src += proxied
- media_src += proxied
- end
-
- # Allow to extend the script-src in specific situations
- script_src = assets_src + %w(js.chargebee.com)
-
- # Allow unsafe-eval for rack-mini-profiler
- if Rails.env.development? && ENV.fetch("OPENPROJECT_RACK_PROFILER_ENABLED", false)
- script_src += %w('unsafe-eval')
- end
-
- # Allow ANDI bookmarklet to run in development mode
- # https://www.ssa.gov/accessibility/andi/help/install.html
- if Rails.env.development?
- script_src += ["https://www.ssa.gov"]
- assets_src += ["https://www.ssa.gov"]
- end
-
- config.csp = {
- preserve_schemes: true,
- # Don't append unsafe-inline in CSP as a fallback
- disable_nonce_backwards_compatibility: true,
-
- # Fallback when no value is defined
- default_src:,
- # Allowed uri in tag
- base_uri: %w('self'),
-
- # Allow fonts from self, asset host, or DATA uri
- font_src: assets_src + %w(data: 'self'),
- # Form targets can only be self
- form_action: default_src,
- # Allow iframe from vimeo (welcome video)
- frame_src: frame_src + %w('self'),
- frame_ancestors: %w('self'),
- # Allow images from anywhere including data urls and blobs (used in resizing)
- img_src: %w(* data: blob:),
- # Allow scripts from self
- script_src:,
- script_src_attr: %w('none'),
- # Allow unsafe-inline styles
- style_src: assets_src + %w('unsafe-inline'),
- # Allow object-src from Release API
- object_src: [OpenProject::Configuration[:security_badge_url]],
-
- # Connect sources for CLI in dev mode
- connect_src:,
-
- # Allow videos from self and from the asset proxy in dev mode.
- media_src:
- }
- end
-end
-# rubocop:enable Lint/PercentStringArray
diff --git a/modules/recaptcha/app/controllers/recaptcha/request_controller.rb b/modules/recaptcha/app/controllers/recaptcha/request_controller.rb
index 320d60a2893..f8385b7a42b 100644
--- a/modules/recaptcha/app/controllers/recaptcha/request_controller.rb
+++ b/modules/recaptcha/app/controllers/recaptcha/request_controller.rb
@@ -30,11 +30,11 @@ module ::Recaptcha
# Request verification form
def perform
if OpenProject::Recaptcha::Configuration.use_hcaptcha?
- use_content_security_policy_named_append(:hcaptcha)
+ allow_captcha_service(:hcaptcha)
elsif OpenProject::Recaptcha::Configuration.use_turnstile?
- use_content_security_policy_named_append(:turnstile)
+ allow_captcha_service(:turnstile)
elsif OpenProject::Recaptcha::Configuration.use_recaptcha?
- use_content_security_policy_named_append(:recaptcha)
+ allow_captcha_service(:recaptcha)
end
end
@@ -163,5 +163,31 @@ module ::Recaptcha
def failure_stage_redirect
redirect_to authentication_stage_failure_path :recaptcha
end
+
+ ##
+ # Add CAPTCHA service CSP rules
+ def allow_captcha_service(service_type)
+ case service_type.to_sym
+ when :recaptcha
+ append_content_security_policy_directives(
+ frame_src: %w[https://www.recaptcha.net/recaptcha/ https://www.gstatic.com/recaptcha/]
+ )
+ when :hcaptcha
+ sources = %w[https://*.hcaptcha.com]
+ append_content_security_policy_directives(
+ frame_src: sources,
+ script_src: sources,
+ style_src: sources,
+ connect_src: sources
+ )
+ when :turnstile
+ sources = %w[https://challenges.cloudflare.com]
+ append_content_security_policy_directives(
+ frame_src: sources,
+ style_src: sources,
+ connect_src: sources
+ )
+ end
+ end
end
end
diff --git a/modules/recaptcha/app/views/recaptcha/request/perform.html.erb b/modules/recaptcha/app/views/recaptcha/request/perform.html.erb
index 8e7cf27be13..b2b970ccca0 100644
--- a/modules/recaptcha/app/views/recaptcha/request/perform.html.erb
+++ b/modules/recaptcha/app/views/recaptcha/request/perform.html.erb
@@ -6,7 +6,7 @@
<% input_name = "g-recaptcha-response" %>
<%= recaptcha_tags(
- nonce: content_security_policy_script_nonce,
+ nonce: content_security_policy_nonce,
callback: "submitRecaptchaForm",
site_key: recaptcha_settings["website_key"]
) %>
@@ -29,7 +29,7 @@
<% end %>
<% elsif recaptcha_settings['recaptcha_type'] == ::OpenProject::Recaptcha::TYPE_V3 %>
<%= recaptcha_v3 action: "login",
- nonce: content_security_policy_script_nonce,
+ nonce: content_security_policy_nonce,
callback: "submitRecaptchaForm",
site_key: recaptcha_settings["website_key"] %>
diff --git a/modules/recaptcha/lib/open_project/recaptcha/engine.rb b/modules/recaptcha/lib/open_project/recaptcha/engine.rb
index 023a9ba73b1..94a826e2a29 100644
--- a/modules/recaptcha/lib/open_project/recaptcha/engine.rb
+++ b/modules/recaptcha/lib/open_project/recaptcha/engine.rb
@@ -28,26 +28,6 @@ module OpenProject::Recaptcha
end
config.after_initialize do
- SecureHeaders::Configuration.named_append(:recaptcha) do
- {
- frame_src: %w[https://www.recaptcha.net/recaptcha/ https://www.gstatic.com/recaptcha/]
- }
- end
-
- SecureHeaders::Configuration.named_append(:hcaptcha) do
- value = %w(https://*.hcaptcha.com)
- keys = %i(frame_src script_src style_src connect_src)
-
- keys.index_with value
- end
-
- SecureHeaders::Configuration.named_append(:turnstile) do
- value = %w(https://challenges.cloudflare.com)
- keys = %i(frame_src style_src connect_src)
-
- keys.index_with value
- end
-
OpenProject::Authentication::Stage.register(
:recaptcha,
nil,
diff --git a/spec/support/shared/with_direct_uploads.rb b/spec/support/shared/with_direct_uploads.rb
index 523d80cc6b7..c82a1288070 100644
--- a/spec/support/shared/with_direct_uploads.rb
+++ b/spec/support/shared/with_direct_uploads.rb
@@ -65,19 +65,23 @@ class WithDirectUploads
def around(example)
example.metadata[:javascript_driver] = example.metadata[:driver] = :chrome_billy
- csp_config = SecureHeaders::Configuration.instance_variable_get(:@default_config).csp
+ # Temporarily modify CSP for direct uploads testing
+ original_csp = Rails.application.config.content_security_policy
- connect_src = csp_config[:connect_src].dup
- form_action = csp_config[:form_action].dup
+ Rails.application.config.content_security_policy do |policy|
+ # Copy existing policy and add test bucket domain
+ original_csp.call(policy) if original_csp
+
+ # Add test bucket to connect_src and form_action for direct uploads
+ policy.connect_src(*(policy.instance_variable_get(:@directives)[:connect_src] || []), "test-bucket.s3.amazonaws.com")
+ policy.form_action(*(policy.instance_variable_get(:@directives)[:form_action] || []), "test-bucket.s3.amazonaws.com")
+ end
begin
- csp_config[:connect_src] << "test-bucket.s3.amazonaws.com"
- csp_config[:form_action] << "test-bucket.s3.amazonaws.com"
-
example.run
ensure
- csp_config[:connect_src] = connect_src
- csp_config[:form_action] = form_action
+ # Restore original CSP configuration
+ Rails.application.config.content_security_policy = original_csp
end
end