Skip to content

Commit 494b75f

Browse files
authored
Merge pull request #486 from github/kyfast-add-trusted-types
Add trusted-types and require-trusted-types-for CSP Directive
2 parents 5f91c40 + f40910c commit 494b75f

File tree

5 files changed

+67
-3
lines changed

5 files changed

+67
-3
lines changed

lib/secure_headers/headers/content_security_policy.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ def value
5353
def build_value
5454
directives.map do |directive_name|
5555
case DIRECTIVE_VALUE_TYPES[directive_name]
56-
when :source_list, :require_sri_for_list # require_sri is a simple set of strings that don't need to deal with symbol casing
56+
when :source_list,
57+
:require_sri_for_list, # require_sri is a simple set of strings that don't need to deal with symbol casing
58+
:require_trusted_types_for_list
5759
build_source_list_directive(directive_name)
5860
when :boolean
5961
symbol_to_hyphen_case(directive_name) if @config.directive_value(directive_name)

lib/secure_headers/headers/content_security_policy_config.rb

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def initialize(hash)
3535
@report_only = nil
3636
@report_uri = nil
3737
@require_sri_for = nil
38+
@require_trusted_types_for = nil
3839
@sandbox = nil
3940
@script_nonce = nil
4041
@script_src = nil
@@ -44,6 +45,7 @@ def initialize(hash)
4445
@style_src = nil
4546
@style_src_elem = nil
4647
@style_src_attr = nil
48+
@trusted_types = nil
4749
@worker_src = nil
4850
@upgrade_insecure_requests = nil
4951
@disable_nonce_backwards_compatibility = nil

lib/secure_headers/headers/policy_management.rb

+34-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,19 @@ def self.included(base)
9898
STYLE_SRC_ATTR
9999
].flatten.freeze
100100

101-
ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0).uniq.sort
101+
# Experimental directives - these vary greatly in support
102+
# See MDN for details.
103+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types
104+
TRUSTED_TYPES = :trusted_types
105+
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for
106+
REQUIRE_TRUSTED_TYPES_FOR = :require_trusted_types_for
107+
108+
DIRECTIVES_EXPERIMENTAL = [
109+
TRUSTED_TYPES,
110+
REQUIRE_TRUSTED_TYPES_FOR,
111+
].flatten.freeze
112+
113+
ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0 + DIRECTIVES_EXPERIMENTAL).uniq.sort
102114

103115
# Think of default-src and report-uri as the beginning and end respectively,
104116
# everything else is in between.
@@ -121,6 +133,7 @@ def self.included(base)
121133
OBJECT_SRC => :source_list,
122134
PLUGIN_TYPES => :media_type_list,
123135
REQUIRE_SRI_FOR => :require_sri_for_list,
136+
REQUIRE_TRUSTED_TYPES_FOR => :require_trusted_types_for_list,
124137
REPORT_URI => :source_list,
125138
PREFETCH_SRC => :source_list,
126139
SANDBOX => :sandbox_list,
@@ -130,6 +143,7 @@ def self.included(base)
130143
STYLE_SRC => :source_list,
131144
STYLE_SRC_ELEM => :source_list,
132145
STYLE_SRC_ATTR => :source_list,
146+
TRUSTED_TYPES => :source_list,
133147
WORKER_SRC => :source_list,
134148
UPGRADE_INSECURE_REQUESTS => :boolean,
135149
}.freeze
@@ -175,6 +189,7 @@ def self.included(base)
175189
].freeze
176190

177191
REQUIRE_SRI_FOR_VALUES = Set.new(%w(script style))
192+
REQUIRE_TRUSTED_TYPES_FOR_VALUES = Set.new(%w(script))
178193

179194
module ClassMethods
180195
# Public: generate a header name, value array that is user-agent-aware.
@@ -270,7 +285,8 @@ def list_directive?(directive)
270285
source_list?(directive) ||
271286
sandbox_list?(directive) ||
272287
media_type_list?(directive) ||
273-
require_sri_for_list?(directive)
288+
require_sri_for_list?(directive) ||
289+
require_trusted_types_for_list?(directive)
274290
end
275291

276292
# For each directive in additions that does not exist in the original config,
@@ -308,6 +324,10 @@ def require_sri_for_list?(directive)
308324
DIRECTIVE_VALUE_TYPES[directive] == :require_sri_for_list
309325
end
310326

327+
def require_trusted_types_for_list?(directive)
328+
DIRECTIVE_VALUE_TYPES[directive] == :require_trusted_types_for_list
329+
end
330+
311331
# Private: Validates that the configuration has a valid type, or that it is a valid
312332
# source expression.
313333
def validate_directive!(directive, value)
@@ -325,6 +345,8 @@ def validate_directive!(directive, value)
325345
validate_media_type_expression!(directive, value)
326346
when :require_sri_for_list
327347
validate_require_sri_source_expression!(directive, value)
348+
when :require_trusted_types_for_list
349+
validate_require_trusted_types_for_source_expression!(directive, value)
328350
else
329351
raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}")
330352
end
@@ -369,6 +391,16 @@ def validate_require_sri_source_expression!(directive, require_sri_for_expressio
369391
end
370392
end
371393

394+
# Private: validates that a require trusted types for expression:
395+
# 1. is an array of strings
396+
# 2. is a subset of ["script"]
397+
def validate_require_trusted_types_for_source_expression!(directive, require_trusted_types_for_expression)
398+
ensure_array_of_strings!(directive, require_trusted_types_for_expression)
399+
unless require_trusted_types_for_expression.to_set.subset?(REQUIRE_TRUSTED_TYPES_FOR_VALUES)
400+
raise ContentSecurityPolicyConfigError.new(%(require-trusted-types-for for must be a subset of #{REQUIRE_TRUSTED_TYPES_FOR_VALUES.to_a} but was #{require_trusted_types_for_expression}))
401+
end
402+
end
403+
372404
# Private: validates that a source expression:
373405
# 1. is an array of strings
374406
# 2. does not contain any deprecated, now invalid values (inline, eval, self, none)

spec/lib/secure_headers/headers/content_security_policy_spec.rb

+20
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ module SecureHeaders
146146
expect(csp.value).to eq("default-src 'self'; require-sri-for script style")
147147
end
148148

149+
it "allows style as a require-trusted-types-for source" do
150+
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_trusted_types_for: %w(script))
151+
expect(csp.value).to eq("default-src 'self'; require-trusted-types-for script")
152+
end
153+
149154
it "includes prefetch-src" do
150155
csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com))
151156
expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com")
@@ -185,6 +190,21 @@ module SecureHeaders
185190
csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
186191
expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
187192
end
193+
194+
it "supports trusted-types directive" do
195+
csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy)})
196+
expect(csp.value).to eq("trusted-types blahblahpolicy")
197+
end
198+
199+
it "supports trusted-types directive with 'none'" do
200+
csp = ContentSecurityPolicy.new({trusted_types: %w(none)})
201+
expect(csp.value).to eq("trusted-types none")
202+
end
203+
204+
it "allows duplicate policy names in trusted-types directive" do
205+
csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy 'allow-duplicates')})
206+
expect(csp.value).to eq("trusted-types blahblahpolicy 'allow-duplicates'")
207+
end
188208
end
189209
end
190210
end

spec/lib/secure_headers/headers/policy_management_spec.rb

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ module SecureHeaders
4545
plugin_types: %w(application/x-shockwave-flash),
4646
prefetch_src: %w(fetch.com),
4747
require_sri_for: %w(script style),
48+
require_trusted_types_for: %w(script),
4849
script_src: %w('self'),
4950
style_src: %w('unsafe-inline'),
5051
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
@@ -53,6 +54,7 @@ module SecureHeaders
5354
script_src_attr: %w(example.com),
5455
style_src_elem: %w(example.com),
5556
style_src_attr: %w(example.com),
57+
trusted_types: %w(abcpolicy),
5658

5759
report_uri: %w(https://example.com/uri-directive),
5860
}
@@ -120,6 +122,12 @@ module SecureHeaders
120122
end.to raise_error(ContentSecurityPolicyConfigError)
121123
end
122124

125+
it "rejects style for trusted types" do
126+
expect do
127+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(style_src: %w('self'), require_trusted_types_for: %w(script style), trusted_types: %w(abcpolicy))))
128+
end
129+
end
130+
123131
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
124132
it "performs light validation on source lists" do
125133
expect do

0 commit comments

Comments
 (0)