Skip to content

Commit

Permalink
Merge pull request #486 from github/kyfast-add-trusted-types
Browse files Browse the repository at this point in the history
Add trusted-types and require-trusted-types-for CSP Directive
  • Loading branch information
KyFaSt authored Aug 2, 2022
2 parents 5f91c40 + f40910c commit 494b75f
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 3 deletions.
4 changes: 3 additions & 1 deletion lib/secure_headers/headers/content_security_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions lib/secure_headers/headers/content_security_policy_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
36 changes: 34 additions & 2 deletions lib/secure_headers/headers/policy_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions spec/lib/secure_headers/headers/content_security_policy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
8 changes: 8 additions & 0 deletions spec/lib/secure_headers/headers/policy_management_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand All @@ -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),
}
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 494b75f

Please sign in to comment.