module SecureHeaders::PolicyManagement::ClassMethods
Public Instance Methods
Public: combine the values from two different configs.
original - the main config additions - values to be merged in
raises an error if the original config is OPT_OUT
-
for non-source-list values (report_only, block_all_mixed_content, upgrade_insecure_requests),
additions will overwrite the original value.
-
if a value in additions does not exist in the original config, the
default-src value is included to match original behavior.
-
if a value in additions does exist in the original config, the two
values are joined.
# File lib/secure_headers/headers/policy_management.rb, line 250 def combine_policies(original, additions) if original == {} raise ContentSecurityPolicyConfigError.new("Attempted to override an opt-out CSP config.") end original = Configuration.send(:deep_copy, original) populate_fetch_source_with_default!(original, additions) merge_policy_additions(original, additions) end
Public: generate a header name, value array that is user-agent-aware.
Returns a default policy if no configuration is provided, or a header name and value based on the config.
# File lib/secure_headers/headers/policy_management.rb, line 199 def make_header(config) return if config.nil? || config == OPT_OUT header = new(config) [header.name, header.value] end
# File lib/secure_headers/headers/policy_management.rb, line 260 def ua_to_variation(user_agent) family = user_agent.browser if family && VARIATIONS.key?(family) family else OTHER end end
Public: Validates each source expression.
Does not validate the invididual values of the source expression (e.g. script_src => h*t*t*p: will not raise an exception)
# File lib/secure_headers/headers/policy_management.rb, line 209 def validate_config!(config) return if config.nil? || config.opt_out? raise ContentSecurityPolicyConfigError.new(":default_src is required") unless config.directive_value(:default_src) if config.directive_value(:script_src).nil? raise ContentSecurityPolicyConfigError.new(":script_src is required, falling back to default-src is too dangerous. Use `script_src: OPT_OUT` to override") end if !config.report_only? && config.directive_value(:report_only) raise ContentSecurityPolicyConfigError.new("Only the csp_report_only config should set :report_only to true") end if config.report_only? && config.directive_value(:report_only) == false raise ContentSecurityPolicyConfigError.new("csp_report_only config must have :report_only set to true") end ContentSecurityPolicyConfig.attrs.each do |key| value = config.directive_value(key) next unless value if META_CONFIGS.include?(key) raise ContentSecurityPolicyConfigError.new("#{key} must be a boolean value") unless boolean?(value) || value.nil? elsif NONCES.include?(key) raise ContentSecurityPolicyConfigError.new("#{key} must be a non-nil value") if value.nil? else validate_directive!(key, value) end end end
Private Instance Methods
# File lib/secure_headers/headers/policy_management.rb, line 438 def boolean?(source_expression) source_expression.is_a?(TrueClass) || source_expression.is_a?(FalseClass) end
# File lib/secure_headers/headers/policy_management.rb, line 423 def ensure_array_of_strings!(directive, value) if (!value.is_a?(Array) || !value.compact.all? { |v| v.is_a?(String) }) raise ContentSecurityPolicyConfigError.new("#{directive} must be an array of strings") end end
# File lib/secure_headers/headers/policy_management.rb, line 417 def ensure_valid_directive!(directive) unless ContentSecurityPolicy::ALL_DIRECTIVES.include?(directive) raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}") end end
# File lib/secure_headers/headers/policy_management.rb, line 429 def ensure_valid_sources!(directive, source_expression) return if source_expression == OPT_OUT source_expression.each do |expression| if ContentSecurityPolicy::DEPRECATED_SOURCE_VALUES.include?(expression) raise ContentSecurityPolicyConfigError.new("#{directive} contains an invalid keyword source (#{expression}). This value must be single quoted.") end end end
Returns True if a directive expects a list of values and False otherwise.
# File lib/secure_headers/headers/policy_management.rb, line 284 def list_directive?(directive) source_list?(directive) || sandbox_list?(directive) || media_type_list?(directive) || require_sri_for_list?(directive) || require_trusted_types_for_list?(directive) end
# File lib/secure_headers/headers/policy_management.rb, line 319 def media_type_list?(directive) DIRECTIVE_VALUE_TYPES[directive] == :media_type_list end
merge the two hashes. combine (instead of overwrite) the array values when each hash contains a value for a given key.
# File lib/secure_headers/headers/policy_management.rb, line 273 def merge_policy_additions(original, additions) original.merge(additions) do |directive, lhs, rhs| if list_directive?(directive) (lhs.to_a + rhs.to_a).compact.uniq else rhs end end.reject { |_, value| value.nil? || value == [] } # this mess prevents us from adding empty directives. end
For each directive in additions that does not exist in the original config, copy the default-src value to the original config. This modifies the original hash.
# File lib/secure_headers/headers/policy_management.rb, line 294 def populate_fetch_source_with_default!(original, additions) # in case we would be appending to an empty directive, fill it with the default-src value additions.each_key do |directive| directive = if directive.to_s.end_with?("_nonce") directive.to_s.gsub(/_nonce/, "_src").to_sym else directive end # Don't set a default if directive has an existing value next if original[directive] if FETCH_SOURCES.include?(directive) original[directive] = original[DEFAULT_SRC] end end end
# File lib/secure_headers/headers/policy_management.rb, line 323 def require_sri_for_list?(directive) DIRECTIVE_VALUE_TYPES[directive] == :require_sri_for_list end
# File lib/secure_headers/headers/policy_management.rb, line 327 def require_trusted_types_for_list?(directive) DIRECTIVE_VALUE_TYPES[directive] == :require_trusted_types_for_list end
# File lib/secure_headers/headers/policy_management.rb, line 315 def sandbox_list?(directive) DIRECTIVE_VALUE_TYPES[directive] == :sandbox_list end
# File lib/secure_headers/headers/policy_management.rb, line 311 def source_list?(directive) DIRECTIVE_VALUE_TYPES[directive] == :source_list end
Private: Validates that the configuration has a valid type, or that it is a valid source expression.
# File lib/secure_headers/headers/policy_management.rb, line 333 def validate_directive!(directive, value) ensure_valid_directive!(directive) case ContentSecurityPolicy::DIRECTIVE_VALUE_TYPES[directive] when :source_list validate_source_expression!(directive, value) when :boolean unless boolean?(value) raise ContentSecurityPolicyConfigError.new("#{directive} must be a boolean. Found #{value.class} value") end when :sandbox_list validate_sandbox_expression!(directive, value) when :media_type_list 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 end
Private: validates that a media type expression:
-
is an array of strings
-
each element is of the form type/subtype
# File lib/secure_headers/headers/policy_management.rb, line 373 def validate_media_type_expression!(directive, media_type_expression) ensure_array_of_strings!(directive, media_type_expression) valid = media_type_expression.compact.all? do |v| # All media types are of the form: <type from RFC 2045> "/" <subtype from RFC 2045>. v =~ /\A.+\/.+\z/ end if !valid raise ContentSecurityPolicyConfigError.new("#{directive} must be an array of valid media types (ex. application/pdf)") end end
Private: validates that a require sri for expression:
-
is an array of strings
-
is a subset of [“string”, “style”]
# File lib/secure_headers/headers/policy_management.rb, line 387 def validate_require_sri_source_expression!(directive, require_sri_for_expression) ensure_array_of_strings!(directive, require_sri_for_expression) unless require_sri_for_expression.to_set.subset?(REQUIRE_SRI_FOR_VALUES) raise ContentSecurityPolicyConfigError.new(%(require-sri for must be a subset of #{REQUIRE_SRI_FOR_VALUES.to_a} but was #{require_sri_for_expression})) end end
Private: validates that a require trusted types for expression:
-
is an array of strings
-
is a subset of [“'script'”]
# File lib/secure_headers/headers/policy_management.rb, line 397 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 sandbox token expression:
-
is an array of strings or optionally `true` (to enable maximal sandboxing)
-
For arrays, each element is of the form allow-*
# File lib/secure_headers/headers/policy_management.rb, line 358 def validate_sandbox_expression!(directive, sandbox_token_expression) # We support sandbox: true to indicate a maximally secure sandbox. return if boolean?(sandbox_token_expression) && sandbox_token_expression == true ensure_array_of_strings!(directive, sandbox_token_expression) valid = sandbox_token_expression.compact.all? do |v| v.is_a?(String) && v.start_with?("allow-") end if !valid raise ContentSecurityPolicyConfigError.new("#{directive} must be True or an array of zero or more sandbox token strings (ex. allow-forms)") end end
Private: validates that a source expression:
-
is an array of strings
-
does not contain any deprecated, now invalid values (inline, eval, self, none)
Does not validate the invididual values of the source expression (e.g. script_src => h*t*t*p: will not raise an exception)
# File lib/secure_headers/headers/policy_management.rb, line 410 def validate_source_expression!(directive, source_expression) if source_expression != OPT_OUT ensure_array_of_strings!(directive, source_expression) end ensure_valid_sources!(directive, source_expression) end