module SecureHeaders::PolicyManagement::ClassMethods

Public Instance Methods

combine_policies(original, additions) click to toggle source

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

  1. for non-source-list values (report_only, block_all_mixed_content, upgrade_insecure_requests),

additions will overwrite the original value.

  1. if a value in additions does not exist in the original config, the

default-src value is included to match original behavior.

  1. 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
make_header(config) click to toggle source

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
ua_to_variation(user_agent) click to toggle source
# 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
validate_config!(config) click to toggle source

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

boolean?(source_expression) click to toggle source
# 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
ensure_array_of_strings!(directive, value) click to toggle source
# 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
ensure_valid_directive!(directive) click to toggle source
# 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
ensure_valid_sources!(directive, source_expression) click to toggle source
# 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
list_directive?(directive) click to toggle source

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
media_type_list?(directive) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 319
def media_type_list?(directive)
  DIRECTIVE_VALUE_TYPES[directive] == :media_type_list
end
merge_policy_additions(original, additions) click to toggle source

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
populate_fetch_source_with_default!(original, additions) click to toggle source

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
require_sri_for_list?(directive) click to toggle source
# 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
require_trusted_types_for_list?(directive) click to toggle source
# 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
sandbox_list?(directive) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 315
def sandbox_list?(directive)
  DIRECTIVE_VALUE_TYPES[directive] == :sandbox_list
end
source_list?(directive) click to toggle source
# File lib/secure_headers/headers/policy_management.rb, line 311
def source_list?(directive)
  DIRECTIVE_VALUE_TYPES[directive] == :source_list
end
validate_directive!(directive, value) click to toggle source

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
validate_media_type_expression!(directive, media_type_expression) click to toggle source

Private: validates that a media type expression:

  1. is an array of strings

  2. 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
validate_require_sri_source_expression!(directive, require_sri_for_expression) click to toggle source

Private: validates that a require sri for expression:

  1. is an array of strings

  2. 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
validate_require_trusted_types_for_source_expression!(directive, require_trusted_types_for_expression) click to toggle source

Private: validates that a require trusted types for expression:

  1. is an array of strings

  2. 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
validate_sandbox_expression!(directive, sandbox_token_expression) click to toggle source

Private: validates that a sandbox token expression:

  1. is an array of strings or optionally `true` (to enable maximal sandboxing)

  2. 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
validate_source_expression!(directive, source_expression) click to toggle source

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)

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