diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb index cb75050c..13ad65a7 100644 --- a/lib/secure_headers/headers/content_security_policy.rb +++ b/lib/secure_headers/headers/content_security_policy.rb @@ -125,6 +125,7 @@ def minify_source_list(directive, source_list) else source_list = populate_nonces(directive, source_list) source_list = reject_all_values_if_none(source_list) + source_list = normalize_uri_paths(source_list) unless directive == REPORT_URI || @preserve_schemes source_list = strip_source_schemes(source_list) @@ -147,6 +148,27 @@ def reject_all_values_if_none(source_list) end end + def normalize_uri_paths(source_list) + source_list.map do |source| + # Normalize domains ending in a single / as without omitting the slash accomplishes the same. + # https://www.w3.org/TR/CSP3/#match-paths ยง 6.6.2.10 Step 2 + begin + uri = URI(source) + if uri.path == "/" + next source.chomp("/") + end + rescue URI::InvalidURIError + end + + if source.chomp("/").include?("/") + source + else + source.chomp("/") + end + end + end + + # Removes duplicates and sources that already match an existing wild card. # # e.g. *.github.com asdf.github.com becomes *.github.com diff --git a/spec/lib/secure_headers/headers/content_security_policy_spec.rb b/spec/lib/secure_headers/headers/content_security_policy_spec.rb index c080283d..182ff43d 100644 --- a/spec/lib/secure_headers/headers/content_security_policy_spec.rb +++ b/spec/lib/secure_headers/headers/content_security_policy_spec.rb @@ -48,9 +48,17 @@ module SecureHeaders expect(csp.value).to eq("default-src * 'unsafe-inline' 'unsafe-eval' data: blob:") end + it "normalizes source expressions that end with a trailing /" do + config = { + default_src: %w(a.example.org/ b.example.com/ wss://c.example.com/ c.example.net/foo/ b.example.co/bar wss://b.example.co/) + } + csp = ContentSecurityPolicy.new(config) + expect(csp.value).to eq("default-src a.example.org b.example.com wss://c.example.com c.example.net/foo/ b.example.co/bar wss://b.example.co") + end + it "minifies source expressions based on overlapping wildcards" do config = { - default_src: %w(a.example.org b.example.org *.example.org https://*.example.org) + default_src: %w(a.example.org b.example.org *.example.org https://*.example.org c.example.org/) } csp = ContentSecurityPolicy.new(config) expect(csp.value).to eq("default-src *.example.org")