Skip to content

Commit 362aae2

Browse files
committed
fix source dedup breaking with port wildcards
1 parent 5f91c40 commit 362aae2

File tree

3 files changed

+24
-4
lines changed

3 files changed

+24
-4
lines changed

lib/secure_headers/headers/content_security_policy.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ def reject_all_values_if_none(source_list)
154154
# e.g. *.github.jpy.wang asdf.github.com becomes *.github.jpy.wang
155155
def dedup_source_list(sources)
156156
sources = sources.uniq
157-
wild_sources = sources.select { |source| source =~ STAR_REGEXP }
157+
wild_sources = sources.select { |source| source =~ DOMAIN_WILDCARD_REGEX || source =~ PORT_WILDCARD_REGEX }
158158

159159
if wild_sources.any?
160-
schemes = sources.map { |source| [source, URI(source).scheme] }.to_h
160+
schemes = sources.map { |source| [source, source_scheme(source)] }.to_h
161161
sources.reject do |source|
162162
!wild_sources.include?(source) &&
163163
wild_sources.any? { |pattern| schemes[pattern] == schemes[source] && File.fnmatch(pattern, source) }
@@ -212,5 +212,13 @@ def strip_source_schemes(source_list)
212212
def symbol_to_hyphen_case(sym)
213213
sym.to_s.tr("_", "-")
214214
end
215+
216+
def source_scheme(source)
217+
uri = URI(source.sub(PORT_WILDCARD_REGEX, ""))
218+
# If host is nil the given source doesn't contain a scheme
219+
# e.g. for `example.org:443` it would return `example.org` as the scheme
220+
# which is of course incorrect
221+
uri.scheme if uri.host
222+
end
215223
end
216224
end

lib/secure_headers/headers/policy_management.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ def self.included(base)
152152

153153
FETCH_SOURCES = ALL_DIRECTIVES - NON_FETCH_SOURCES - NON_SOURCE_LIST_SOURCES
154154

155-
STAR_REGEXP = Regexp.new(Regexp.escape(STAR))
155+
DOMAIN_WILDCARD_REGEX = /(?<=\A|[^:])\*/
156+
PORT_WILDCARD_REGEX = /:\*/
156157
HTTP_SCHEME_REGEX = %r{\Ahttps?://}
157158

158159
WILDCARD_SOURCES = [

spec/lib/secure_headers/headers/content_security_policy_spec.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ module SecureHeaders
5656
expect(csp.value).to eq("default-src *.example.org")
5757
end
5858

59+
it "minifies overlapping port wildcards" do
60+
csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org:* example.org:443 https://example.org:80))
61+
expect(csp.value).to eq("default-src example.org example.org:*")
62+
end
63+
64+
it "allows for port wildcards" do
65+
csp = ContentSecurityPolicy.new(connect_src: %w(ws://localhost:*))
66+
expect(csp.value).to eq("connect-src ws://localhost:*")
67+
end
68+
5969
it "removes http/s schemes from hosts" do
6070
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org))
6171
expect(csp.value).to eq("default-src example.org")
@@ -102,7 +112,8 @@ module SecureHeaders
102112
end
103113

104114
it "deduplicates any source expressions" do
105-
csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
115+
src = %w(example.org example.org http://example.org https://example.org)
116+
csp = ContentSecurityPolicy.new(default_src: src)
106117
expect(csp.value).to eq("default-src example.org")
107118
end
108119

0 commit comments

Comments
 (0)