Skip to content

Commit fcf1723

Browse files
Copilotfletchto99
andcommitted
Initial plan
Don't set upgrade_insecure_requests for HTTP requests Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com> Fix rubocop style issues (trailing whitespace) Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com> Refactor: Extract helper method for removing upgrade_insecure_requests Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com> Revert .gitignore changes Co-authored-by: fletchto99 <718681+fletchto99@users.noreply.github.com>
1 parent b3557f7 commit fcf1723

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

lib/secure_headers.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def opt_out_of_all_protection(request)
133133
# request.
134134
#
135135
# StrictTransportSecurity is not applied to http requests.
136+
# upgrade_insecure_requests is not applied to http requests.
136137
# See #config_for to determine which config is used for a given request.
137138
#
138139
# Returns a hash of header names => header values. The value
@@ -146,6 +147,11 @@ def header_hash_for(request)
146147

147148
if request.scheme != HTTPS
148149
headers.delete(StrictTransportSecurity::HEADER_NAME)
150+
151+
# Remove upgrade_insecure_requests from CSP headers for HTTP requests
152+
# as it doesn't make sense to upgrade requests when the page itself is served over HTTP
153+
remove_upgrade_insecure_requests_from_csp!(headers, config.csp)
154+
remove_upgrade_insecure_requests_from_csp!(headers, config.csp_report_only)
149155
end
150156
headers
151157
end
@@ -242,6 +248,23 @@ def content_security_policy_nonce(request, script_or_style)
242248
def override_secure_headers_request_config(request, config)
243249
request.env[SECURE_HEADERS_CONFIG] = config
244250
end
251+
252+
# Private: removes upgrade_insecure_requests directive from a CSP config
253+
# if it's present, and updates the headers hash with the modified CSP.
254+
#
255+
# headers - the headers hash to update
256+
# csp_config - the CSP config to check and potentially modify
257+
#
258+
# Returns nothing (modifies headers in place)
259+
def remove_upgrade_insecure_requests_from_csp!(headers, csp_config)
260+
return if csp_config.opt_out?
261+
return unless csp_config.directive_value(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS)
262+
263+
modified_config = csp_config.dup
264+
modified_config.update_directive(ContentSecurityPolicy::UPGRADE_INSECURE_REQUESTS, false)
265+
header_name, value = ContentSecurityPolicy.make_header(modified_config)
266+
headers[header_name] = value if header_name && value
267+
end
245268
end
246269

247270
# These methods are mixed into controllers and delegate to the class method

spec/lib/secure_headers_spec.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,68 @@ module SecureHeaders
436436

437437
end
438438
end
439+
440+
it "does not set upgrade-insecure-requests if request is over HTTP" do
441+
reset_config
442+
Configuration.default do |config|
443+
config.csp = {
444+
default_src: %w('self'),
445+
script_src: %w('self'),
446+
upgrade_insecure_requests: true
447+
}
448+
end
449+
450+
plaintext_request = Rack::Request.new({})
451+
hash = SecureHeaders.header_hash_for(plaintext_request)
452+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'")
453+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).not_to include("upgrade-insecure-requests")
454+
end
455+
456+
it "sets upgrade-insecure-requests if request is over HTTPS" do
457+
reset_config
458+
Configuration.default do |config|
459+
config.csp = {
460+
default_src: %w('self'),
461+
script_src: %w('self'),
462+
upgrade_insecure_requests: true
463+
}
464+
end
465+
466+
https_request = Rack::Request.new("HTTPS" => "on")
467+
hash = SecureHeaders.header_hash_for(https_request)
468+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'; upgrade-insecure-requests")
469+
end
470+
471+
it "does not set upgrade-insecure-requests in report-only mode if request is over HTTP" do
472+
reset_config
473+
Configuration.default do |config|
474+
config.csp_report_only = {
475+
default_src: %w('self'),
476+
script_src: %w('self'),
477+
upgrade_insecure_requests: true
478+
}
479+
end
480+
481+
plaintext_request = Rack::Request.new({})
482+
hash = SecureHeaders.header_hash_for(plaintext_request)
483+
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'")
484+
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).not_to include("upgrade-insecure-requests")
485+
end
486+
487+
it "sets upgrade-insecure-requests in report-only mode if request is over HTTPS" do
488+
reset_config
489+
Configuration.default do |config|
490+
config.csp_report_only = {
491+
default_src: %w('self'),
492+
script_src: %w('self'),
493+
upgrade_insecure_requests: true
494+
}
495+
end
496+
497+
https_request = Rack::Request.new("HTTPS" => "on")
498+
hash = SecureHeaders.header_hash_for(https_request)
499+
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src 'self'; upgrade-insecure-requests")
500+
end
439501
end
440502
end
441503

0 commit comments

Comments
 (0)