diff --git a/lib/secure_headers/headers/content_security_policy.rb b/lib/secure_headers/headers/content_security_policy.rb index 38ac6cc0..2c9ef501 100644 --- a/lib/secure_headers/headers/content_security_policy.rb +++ b/lib/secure_headers/headers/content_security_policy.rb @@ -53,7 +53,9 @@ def value def build_value directives.map do |directive_name| case DIRECTIVE_VALUE_TYPES[directive_name] - when :source_list, :require_sri_for_list # require_sri is a simple set of strings that don't need to deal with symbol casing + when :source_list, + :require_sri_for_list, # require_sri is a simple set of strings that don't need to deal with symbol casing + :require_trusted_types_for_list build_source_list_directive(directive_name) when :boolean symbol_to_hyphen_case(directive_name) if @config.directive_value(directive_name) diff --git a/lib/secure_headers/headers/content_security_policy_config.rb b/lib/secure_headers/headers/content_security_policy_config.rb index 3c477161..5d3c7550 100644 --- a/lib/secure_headers/headers/content_security_policy_config.rb +++ b/lib/secure_headers/headers/content_security_policy_config.rb @@ -35,6 +35,7 @@ def initialize(hash) @report_only = nil @report_uri = nil @require_sri_for = nil + @require_trusted_types_for = nil @sandbox = nil @script_nonce = nil @script_src = nil @@ -44,6 +45,7 @@ def initialize(hash) @style_src = nil @style_src_elem = nil @style_src_attr = nil + @trusted_types = nil @worker_src = nil @upgrade_insecure_requests = nil @disable_nonce_backwards_compatibility = nil diff --git a/lib/secure_headers/headers/policy_management.rb b/lib/secure_headers/headers/policy_management.rb index d3ce5702..a4d9635e 100644 --- a/lib/secure_headers/headers/policy_management.rb +++ b/lib/secure_headers/headers/policy_management.rb @@ -98,7 +98,19 @@ def self.included(base) STYLE_SRC_ATTR ].flatten.freeze - ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0).uniq.sort + # Experimental directives - these vary greatly in support + # See MDN for details. + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types + TRUSTED_TYPES = :trusted_types + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-trusted-types-for + REQUIRE_TRUSTED_TYPES_FOR = :require_trusted_types_for + + DIRECTIVES_EXPERIMENTAL = [ + TRUSTED_TYPES, + REQUIRE_TRUSTED_TYPES_FOR, + ].flatten.freeze + + ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0 + DIRECTIVES_EXPERIMENTAL).uniq.sort # Think of default-src and report-uri as the beginning and end respectively, # everything else is in between. @@ -121,6 +133,7 @@ def self.included(base) OBJECT_SRC => :source_list, PLUGIN_TYPES => :media_type_list, REQUIRE_SRI_FOR => :require_sri_for_list, + REQUIRE_TRUSTED_TYPES_FOR => :require_trusted_types_for_list, REPORT_URI => :source_list, PREFETCH_SRC => :source_list, SANDBOX => :sandbox_list, @@ -130,6 +143,7 @@ def self.included(base) STYLE_SRC => :source_list, STYLE_SRC_ELEM => :source_list, STYLE_SRC_ATTR => :source_list, + TRUSTED_TYPES => :source_list, WORKER_SRC => :source_list, UPGRADE_INSECURE_REQUESTS => :boolean, }.freeze @@ -175,6 +189,7 @@ def self.included(base) ].freeze REQUIRE_SRI_FOR_VALUES = Set.new(%w(script style)) + REQUIRE_TRUSTED_TYPES_FOR_VALUES = Set.new(%w(script)) module ClassMethods # Public: generate a header name, value array that is user-agent-aware. @@ -270,7 +285,8 @@ def list_directive?(directive) source_list?(directive) || sandbox_list?(directive) || media_type_list?(directive) || - require_sri_for_list?(directive) + require_sri_for_list?(directive) || + require_trusted_types_for_list?(directive) end # For each directive in additions that does not exist in the original config, @@ -308,6 +324,10 @@ def require_sri_for_list?(directive) DIRECTIVE_VALUE_TYPES[directive] == :require_sri_for_list end + def require_trusted_types_for_list?(directive) + DIRECTIVE_VALUE_TYPES[directive] == :require_trusted_types_for_list + end + # Private: Validates that the configuration has a valid type, or that it is a valid # source expression. def validate_directive!(directive, value) @@ -325,6 +345,8 @@ def validate_directive!(directive, value) validate_media_type_expression!(directive, value) when :require_sri_for_list validate_require_sri_source_expression!(directive, value) + when :require_trusted_types_for_list + validate_require_trusted_types_for_source_expression!(directive, value) else raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}") end @@ -369,6 +391,16 @@ def validate_require_sri_source_expression!(directive, require_sri_for_expressio end end + # Private: validates that a require trusted types for expression: + # 1. is an array of strings + # 2. is a subset of ["script"] + def validate_require_trusted_types_for_source_expression!(directive, require_trusted_types_for_expression) + ensure_array_of_strings!(directive, require_trusted_types_for_expression) + unless require_trusted_types_for_expression.to_set.subset?(REQUIRE_TRUSTED_TYPES_FOR_VALUES) + 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})) + end + end + # Private: validates that a source expression: # 1. is an array of strings # 2. does not contain any deprecated, now invalid values (inline, eval, self, none) 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 dbd878a9..53f8cfa2 100644 --- a/spec/lib/secure_headers/headers/content_security_policy_spec.rb +++ b/spec/lib/secure_headers/headers/content_security_policy_spec.rb @@ -146,6 +146,11 @@ module SecureHeaders expect(csp.value).to eq("default-src 'self'; require-sri-for script style") end + it "allows style as a require-trusted-types-for source" do + csp = ContentSecurityPolicy.new(default_src: %w('self'), require_trusted_types_for: %w(script)) + expect(csp.value).to eq("default-src 'self'; require-trusted-types-for script") + end + it "includes prefetch-src" do csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com)) expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com") @@ -185,6 +190,21 @@ module SecureHeaders csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')}) expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'") end + + it "supports trusted-types directive" do + csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy)}) + expect(csp.value).to eq("trusted-types blahblahpolicy") + end + + it "supports trusted-types directive with 'none'" do + csp = ContentSecurityPolicy.new({trusted_types: %w(none)}) + expect(csp.value).to eq("trusted-types none") + end + + it "allows duplicate policy names in trusted-types directive" do + csp = ContentSecurityPolicy.new({trusted_types: %w(blahblahpolicy 'allow-duplicates')}) + expect(csp.value).to eq("trusted-types blahblahpolicy 'allow-duplicates'") + end end end end diff --git a/spec/lib/secure_headers/headers/policy_management_spec.rb b/spec/lib/secure_headers/headers/policy_management_spec.rb index 1a9ac4eb..d9c85279 100644 --- a/spec/lib/secure_headers/headers/policy_management_spec.rb +++ b/spec/lib/secure_headers/headers/policy_management_spec.rb @@ -45,6 +45,7 @@ module SecureHeaders plugin_types: %w(application/x-shockwave-flash), prefetch_src: %w(fetch.com), require_sri_for: %w(script style), + require_trusted_types_for: %w(script), script_src: %w('self'), style_src: %w('unsafe-inline'), upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/ @@ -53,6 +54,7 @@ module SecureHeaders script_src_attr: %w(example.com), style_src_elem: %w(example.com), style_src_attr: %w(example.com), + trusted_types: %w(abcpolicy), report_uri: %w(https://example.com/uri-directive), } @@ -120,6 +122,12 @@ module SecureHeaders end.to raise_error(ContentSecurityPolicyConfigError) end + it "rejects style for trusted types" do + expect do + ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(style_src: %w('self'), require_trusted_types_for: %w(script style), trusted_types: %w(abcpolicy)))) + end + end + # this is mostly to ensure people don't use the antiquated shorthands common in other configs it "performs light validation on source lists" do expect do