From 0d44152867a5031e116183743d6a908fe47d2307 Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Wed, 30 Mar 2022 17:53:32 +0100 Subject: [PATCH 1/5] normalize domains with trailing slashes CSP3 more explicitly calls this out: > If path A consists of one character that is equal to the U+002F > SOLIDUS character (/) and path B is empty, return "Matches". A URL like `example.com/foo` will match a connect-src of `example.com`, as well as `example.com/`, so having two connect-srcs listed like this is redundant. --- .../headers/content_security_policy.rb | 14 ++++++++++++++ .../headers/content_security_policy_spec.rb | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb index cb75050c..a84ba921 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,19 @@ 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 accomplisheg the same. + # https://www.w3.org/TR/CSP3/#match-paths § 6.6.2.10 Step 2 + 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..17d2c535 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/ c.example.net/foo/ b.example.co/bar) + } + csp = ContentSecurityPolicy.new(config) + expect(csp.value).to eq("default-src a.example.org b.example.com c.example.net/foo/ b.example.co/bar") + 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") From 6ffcee7537cd67b317c45001e4a25c538de9cbe3 Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Mon, 4 Apr 2022 16:29:58 +0100 Subject: [PATCH 2/5] fix: allow URIs with schema to have trailing slashes normalised Co-authored-by: Dusty Greif --- lib/secure_headers/headers/content_security_policy.rb | 8 ++++++++ .../headers/content_security_policy_spec.rb | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb index a84ba921..ea2473f9 100644 --- a/lib/secure_headers/headers/content_security_policy.rb +++ b/lib/secure_headers/headers/content_security_policy.rb @@ -152,6 +152,14 @@ def normalize_uri_paths(source_list) source_list.map do |source| # Normalize domains ending in a single / as without omitting the slash accomplisheg 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 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 17d2c535..182ff43d 100644 --- a/spec/lib/secure_headers/headers/content_security_policy_spec.rb +++ b/spec/lib/secure_headers/headers/content_security_policy_spec.rb @@ -50,10 +50,10 @@ module SecureHeaders it "normalizes source expressions that end with a trailing /" do config = { - default_src: %w(a.example.org/ b.example.com/ c.example.net/foo/ b.example.co/bar) + 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 c.example.net/foo/ b.example.co/bar") + 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 From 67ca492b445f626fb9ca55c298efa211fdf4538d Mon Sep 17 00:00:00 2001 From: Keith Cirkel Date: Mon, 4 Apr 2022 16:31:52 +0100 Subject: [PATCH 3/5] fix typo Co-authored-by: Dusty Greif --- lib/secure_headers/headers/content_security_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb index ea2473f9..13ad65a7 100644 --- a/lib/secure_headers/headers/content_security_policy.rb +++ b/lib/secure_headers/headers/content_security_policy.rb @@ -150,7 +150,7 @@ def reject_all_values_if_none(source_list) def normalize_uri_paths(source_list) source_list.map do |source| - # Normalize domains ending in a single / as without omitting the slash accomplisheg the same. + # 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) From 756b6169d7ca122d4a50b35dd428eae04c3f9803 Mon Sep 17 00:00:00 2001 From: Matt Langlois Date: Mon, 15 Dec 2025 10:40:13 -0700 Subject: [PATCH 4/5] Fix merge to remove old code --- .../headers/content_security_policy.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb index 101fdd90..06c81b30 100644 --- a/lib/secure_headers/headers/content_security_policy.rb +++ b/lib/secure_headers/headers/content_security_policy.rb @@ -172,24 +172,6 @@ def normalize_uri_paths(source_list) end end - - # Removes duplicates and sources that already match an existing wild card. - # - # e.g. *.github.com asdf.github.com becomes *.github.com - def dedup_source_list(sources) - sources = sources.uniq - wild_sources = sources.select { |source| source =~ STAR_REGEXP } - - if wild_sources.any? - sources.reject do |source| - !wild_sources.include?(source) && - wild_sources.any? { |pattern| File.fnmatch(pattern, source) } - end - else - sources - end - end - # Private: append a nonce to the script/style directories if script_nonce # or style_nonce are provided. def populate_nonces(directive, source_list) From a4a4b1b927534c45085864eb631f7c53bbca32c3 Mon Sep 17 00:00:00 2001 From: Matt Langlois Date: Tue, 16 Dec 2025 12:38:28 -0700 Subject: [PATCH 5/5] Fix tests (sources are no longer minified since #499) --- spec/lib/secure_headers/headers/content_security_policy_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6750bd0e..c2047bcc 100644 --- a/spec/lib/secure_headers/headers/content_security_policy_spec.rb +++ b/spec/lib/secure_headers/headers/content_security_policy_spec.rb @@ -61,7 +61,7 @@ module SecureHeaders 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 a.example.org b.example.org *.example.org") + expect(csp.value).to eq("default-src a.example.org b.example.org *.example.org c.example.org") end it "removes http/s schemes from hosts" do