From 0d44152867a5031e116183743d6a908fe47d2307 Mon Sep 17 00:00:00 2001 From: Keith Cirkel <keithamus@users.noreply.github.com> Date: Wed, 30 Mar 2022 17:53:32 +0100 Subject: [PATCH 1/3] 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 <keithamus@users.noreply.github.com> Date: Mon, 4 Apr 2022 16:29:58 +0100 Subject: [PATCH 2/3] fix: allow URIs with schema to have trailing slashes normalised Co-authored-by: Dusty Greif <dgreif@users.noreply.github.com> --- 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 <keithamus@users.noreply.github.com> Date: Mon, 4 Apr 2022 16:31:52 +0100 Subject: [PATCH 3/3] fix typo Co-authored-by: Dusty Greif <dgreif@users.noreply.github.com> --- 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)