From 850ab3216c65fb69bfde82953c6cf27810404274 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 19:51:32 +0000 Subject: [PATCH 1/5] Initial plan From 096d8dbe37ec275826d5b656b75376ec5cce72a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:00:09 +0000 Subject: [PATCH 2/5] Don't set upgrade_insecure_requests for HTTP requests Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com> --- .gitignore | 1 + lib/secure_headers.rb | 17 +++++++++ spec/lib/secure_headers_spec.rb | 62 +++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/.gitignore b/.gitignore index 0b32062a..1e500bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ coverage pkg rdoc spec/reports +vendor/bundle diff --git a/lib/secure_headers.rb b/lib/secure_headers.rb index 6426e538..a876fc59 100644 --- a/lib/secure_headers.rb +++ b/lib/secure_headers.rb @@ -133,6 +133,7 @@ def opt_out_of_all_protection(request) # request. # # StrictTransportSecurity is not applied to http requests. + # upgrade_insecure_requests is not applied to http requests. # See #config_for to determine which config is used for a given request. # # Returns a hash of header names => header values. The value @@ -146,6 +147,22 @@ def header_hash_for(request) if request.scheme != HTTPS headers.delete(StrictTransportSecurity::HEADER_NAME) + + # Remove upgrade_insecure_requests from CSP headers for HTTP requests + # as it doesn't make sense to upgrade requests when the page itself is served over HTTP + if !config.csp.opt_out? && config.csp.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS) + modified_csp_config = config.csp.dup + modified_csp_config.update_directive(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS, false) + header_name, value = ContentSecurityPolicy.make_header(modified_csp_config) + headers[header_name] = value if header_name && value + end + + if !config.csp_report_only.opt_out? && config.csp_report_only.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS) + modified_csp_report_only_config = config.csp_report_only.dup + modified_csp_report_only_config.update_directive(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS, false) + header_name, value = ContentSecurityPolicy.make_header(modified_csp_report_only_config) + headers[header_name] = value if header_name && value + end end headers end diff --git a/spec/lib/secure_headers_spec.rb b/spec/lib/secure_headers_spec.rb index fd66d487..292a007b 100644 --- a/spec/lib/secure_headers_spec.rb +++ b/spec/lib/secure_headers_spec.rb @@ -436,6 +436,68 @@ module SecureHeaders end end + + it "does not set upgrade-insecure-requests if request is over HTTP" do + reset_config + Configuration.default do |config| + config.csp = { + default_src: %w('self'), + script_src: %w('self'), + upgrade_insecure_requests: true + } + end + + plaintext_request = Rack::Request.new({}) + hash = SecureHeaders.header_hash_for(plaintext_request) + expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'") + expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).not_to include("upgrade-insecure-requests") + end + + it "sets upgrade-insecure-requests if request is over HTTPS" do + reset_config + Configuration.default do |config| + config.csp = { + default_src: %w('self'), + script_src: %w('self'), + upgrade_insecure_requests: true + } + end + + https_request = Rack::Request.new("HTTPS" => "on") + hash = SecureHeaders.header_hash_for(https_request) + expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'; upgrade-insecure-requests") + end + + it "does not set upgrade-insecure-requests in report-only mode if request is over HTTP" do + reset_config + Configuration.default do |config| + config.csp_report_only = { + default_src: %w('self'), + script_src: %w('self'), + upgrade_insecure_requests: true + } + end + + plaintext_request = Rack::Request.new({}) + hash = SecureHeaders.header_hash_for(plaintext_request) + expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'") + expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).not_to include("upgrade-insecure-requests") + end + + it "sets upgrade-insecure-requests in report-only mode if request is over HTTPS" do + reset_config + Configuration.default do |config| + config.csp_report_only = { + default_src: %w('self'), + script_src: %w('self'), + upgrade_insecure_requests: true + } + end + + https_request = Rack::Request.new("HTTPS" => "on") + hash = SecureHeaders.header_hash_for(https_request) + expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'; upgrade-insecure-requests") + end end end From 7b19bd1943ad01728db011f9327f3b691ac89eb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:01:00 +0000 Subject: [PATCH 3/5] Fix rubocop style issues (trailing whitespace) Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com> --- lib/secure_headers.rb | 4 ++-- spec/lib/secure_headers_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/secure_headers.rb b/lib/secure_headers.rb index a876fc59..dff3ea1f 100644 --- a/lib/secure_headers.rb +++ b/lib/secure_headers.rb @@ -147,7 +147,7 @@ def header_hash_for(request) if request.scheme != HTTPS headers.delete(StrictTransportSecurity::HEADER_NAME) - + # Remove upgrade_insecure_requests from CSP headers for HTTP requests # as it doesn't make sense to upgrade requests when the page itself is served over HTTP if !config.csp.opt_out? && config.csp.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS) @@ -156,7 +156,7 @@ def header_hash_for(request) header_name, value = ContentSecurityPolicy.make_header(modified_csp_config) headers[header_name] = value if header_name && value end - + if !config.csp_report_only.opt_out? && config.csp_report_only.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS) modified_csp_report_only_config = config.csp_report_only.dup modified_csp_report_only_config.update_directive(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS, false) diff --git a/spec/lib/secure_headers_spec.rb b/spec/lib/secure_headers_spec.rb index 292a007b..ae717c88 100644 --- a/spec/lib/secure_headers_spec.rb +++ b/spec/lib/secure_headers_spec.rb @@ -446,7 +446,7 @@ module SecureHeaders upgrade_insecure_requests: true } end - + plaintext_request = Rack::Request.new({}) hash = SecureHeaders.header_hash_for(plaintext_request) expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'") @@ -462,7 +462,7 @@ module SecureHeaders upgrade_insecure_requests: true } end - + https_request = Rack::Request.new("HTTPS" => "on") hash = SecureHeaders.header_hash_for(https_request) expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'; upgrade-insecure-requests") @@ -477,7 +477,7 @@ module SecureHeaders upgrade_insecure_requests: true } end - + plaintext_request = Rack::Request.new({}) hash = SecureHeaders.header_hash_for(plaintext_request) expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'") @@ -493,7 +493,7 @@ module SecureHeaders upgrade_insecure_requests: true } end - + https_request = Rack::Request.new("HTTPS" => "on") hash = SecureHeaders.header_hash_for(https_request) expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'; upgrade-insecure-requests") From f1167c86a5e30996070d0cd04eeee1a3c0db9003 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:04:23 +0000 Subject: [PATCH 4/5] Refactor: Extract helper method for removing upgrade_insecure_requests Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com> --- lib/secure_headers.rb | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/secure_headers.rb b/lib/secure_headers.rb index dff3ea1f..d2be32e7 100644 --- a/lib/secure_headers.rb +++ b/lib/secure_headers.rb @@ -150,19 +150,8 @@ def header_hash_for(request) # Remove upgrade_insecure_requests from CSP headers for HTTP requests # as it doesn't make sense to upgrade requests when the page itself is served over HTTP - if !config.csp.opt_out? && config.csp.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS) - modified_csp_config = config.csp.dup - modified_csp_config.update_directive(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS, false) - header_name, value = ContentSecurityPolicy.make_header(modified_csp_config) - headers[header_name] = value if header_name && value - end - - if !config.csp_report_only.opt_out? && config.csp_report_only.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS) - modified_csp_report_only_config = config.csp_report_only.dup - modified_csp_report_only_config.update_directive(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS, false) - header_name, value = ContentSecurityPolicy.make_header(modified_csp_report_only_config) - headers[header_name] = value if header_name && value - end + remove_upgrade_insecure_requests_from_csp!(headers, config.csp) + remove_upgrade_insecure_requests_from_csp!(headers, config.csp_report_only) end headers end @@ -259,6 +248,23 @@ def content_security_policy_nonce(request, script_or_style) def override_secure_headers_request_config(request, config) request.env[SECURE_HEADERS_CONFIG] = config end + + # Private: removes upgrade_insecure_requests directive from a CSP config + # if it's present, and updates the headers hash with the modified CSP. + # + # headers - the headers hash to update + # csp_config - the CSP config to check and potentially modify + # + # Returns nothing (modifies headers in place) + def remove_upgrade_insecure_requests_from_csp!(headers, csp_config) + return if csp_config.opt_out? + return unless csp_config.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS) + + modified_config = csp_config.dup + modified_config.update_directive(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS, false) + header_name, value = ContentSecurityPolicy.make_header(modified_config) + headers[header_name] = value if header_name && value + end end # These methods are mixed into controllers and delegate to the class method From 1c66c22fae3fd6a0213c0d4e4d51b50997d9398d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 19:23:28 +0000 Subject: [PATCH 5/5] Revert .gitignore changes Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com> --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1e500bfa..0b32062a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,3 @@ coverage pkg rdoc spec/reports -vendor/bundle