class SecureHeaders::ContentSecurityPolicy

Constants

VERSION_46

constants to be used for version-specific UA sniffing

Public Class Methods

new(config = nil, user_agent = OTHER) click to toggle source
# File lib/secure_headers/headers/content_security_policy.rb, line 12
def initialize(config = nil, user_agent = OTHER)
  @config = Configuration.send(:deep_copy, config || DEFAULT_CONFIG)
  @parsed_ua = if user_agent.is_a?(UserAgent::Browsers::Base)
    user_agent
  else
    UserAgent.parse(user_agent)
  end
  normalize_child_frame_src
  @report_only = @config[:report_only]
  @preserve_schemes = @config[:preserve_schemes]
  @script_nonce = @config[:script_nonce]
  @style_nonce = @config[:style_nonce]
end

Public Instance Methods

name() click to toggle source

Returns the name to use for the header. Either “Content-Security-Policy” or “Content-Security-Policy-Report-Only”

# File lib/secure_headers/headers/content_security_policy.rb, line 29
def name
  if @report_only
    REPORT_ONLY
  else
    HEADER_NAME
  end
end
value() click to toggle source

Return the value of the CSP header

# File lib/secure_headers/headers/content_security_policy.rb, line 39
def value
  @value ||= if @config
    build_value
  else
    DEFAULT_VALUE
  end
end

Private Instance Methods

append_nonce(source_list, nonce) click to toggle source

Private: adds a nonce or 'unsafe-inline' depending on browser support. If a nonce is populated, inline content is assumed.

While CSP is backward compatible in that a policy with a nonce will ignore unsafe-inline, this is more concise.

# File lib/secure_headers/headers/content_security_policy.rb, line 160
def append_nonce(source_list, nonce)
  if nonce
    if nonces_supported?
      source_list << "'nonce-#{nonce}'"
    else
      source_list << UNSAFE_INLINE
    end
  end
end
build_directive(directive) click to toggle source

Private: builds a string that represents one directive in a minified form.

directive_name - a symbol representing the various ALL_DIRECTIVES

Returns a string representing a directive.

# File lib/secure_headers/headers/content_security_policy.rb, line 90
def build_directive(directive)
  return if @config[directive].nil?

  source_list = @config[directive].compact
  return if source_list.empty?

  normalized_source_list = minify_source_list(directive, source_list)
  [symbol_to_hyphen_case(directive), normalized_source_list].join(" ")
end
build_value() click to toggle source

Private: converts the config object into a string representing a policy. Places default-src at the first directive and report-uri as the last. All others are presented in alphabetical order.

Unsupported directives are filtered based on the user agent.

Returns a content security policy header value.

# File lib/secure_headers/headers/content_security_policy.rb, line 72
def build_value
  directives.map do |directive_name|
    case DIRECTIVE_VALUE_TYPES[directive_name]
    when :boolean
      symbol_to_hyphen_case(directive_name) if @config[directive_name]
    when :string
      [symbol_to_hyphen_case(directive_name), @config[directive_name]].join(" ")
    else
      build_directive(directive_name)
    end
  end.compact.join("; ")
end
dedup_source_list(sources) click to toggle source

Removes duplicates and sources that already match an existing wild card.

e.g. *.github.com asdf.github.com becomes *.github.com

# File lib/secure_headers/headers/content_security_policy.rb, line 130
def dedup_source_list(sources)
  sources = sources.uniq
  wild_sources = sources.select { |source| source =~ STAR_REGEXP }

  if wild_sources.any?
    sources.reject do |source|
      !wild_sources.include?(source) &&
        wild_sources.any? { |pattern| File.fnmatch(pattern, source) }
    end
  else
    sources
  end
end
directives() click to toggle source

Private: return the list of directives that are supported by the user agent, starting with default-src and ending with report-uri.

# File lib/secure_headers/headers/content_security_policy.rb, line 172
def directives
  [DEFAULT_SRC,
    BODY_DIRECTIVES.select { |key| supported_directives.include?(key) },
    REPORT_URI].flatten.select { |directive| @config.key?(directive) }
end
keep_wildcard_sources(source_list) click to toggle source

Discard trailing entries (excluding unsafe-*) since * accomplishes the same.

# File lib/secure_headers/headers/content_security_policy.rb, line 118
def keep_wildcard_sources(source_list)
  source_list.select { |value| WILDCARD_SOURCES.include?(value) }
end
minify_source_list(directive, source_list) click to toggle source

If a directive contains *, all other values are omitted. If a directive contains 'none' but has other values, 'none' is ommitted. Schemes are stripped (see www.w3.org/TR/CSP2/#match-source-expression)

# File lib/secure_headers/headers/content_security_policy.rb, line 103
def minify_source_list(directive, source_list)
  if source_list.include?(STAR)
    keep_wildcard_sources(source_list)
  else
    populate_nonces!(directive, source_list)
    reject_all_values_if_none!(source_list)

    unless directive == REPORT_URI || @preserve_schemes
      strip_source_schemes!(source_list)
    end
    dedup_source_list(source_list).join(" ")
  end
end
nonces_supported?() click to toggle source
# File lib/secure_headers/headers/content_security_policy.rb, line 200
def nonces_supported?
  @nonces_supported ||= MODERN_BROWSERS.include?(@parsed_ua.browser)
end
normalize_child_frame_src() click to toggle source

frame-src is deprecated, child-src is being implemented. They are very similar and in most cases, the same value can be used for both.

# File lib/secure_headers/headers/content_security_policy.rb, line 51
def normalize_child_frame_src
  if @config[:frame_src] && @config[:child_src] && @config[:frame_src] != @config[:child_src]
    Kernel.warn("#{Kernel.caller.first}: [DEPRECATION] both :child_src and :frame_src supplied and do not match. This can lead to inconsistent behavior across browsers.")
  elsif @config[:frame_src]
    Kernel.warn("#{Kernel.caller.first}: [DEPRECATION] :frame_src is deprecated, use :child_src instead. Provided: #{@config[:frame_src]}.")
  end

  if supported_directives.include?(:child_src)
    @config[:child_src] = @config[:child_src] || @config[:frame_src]
  else
    @config[:frame_src] = @config[:frame_src] || @config[:child_src]
  end
end
populate_nonces!(directive, source_list) click to toggle source

Private: append a nonce to the script/style directories if script_nonce or style_nonce are provided.

# File lib/secure_headers/headers/content_security_policy.rb, line 146
def populate_nonces!(directive, source_list)
  case directive
  when SCRIPT_SRC
    append_nonce(source_list, @script_nonce)
  when STYLE_SRC
    append_nonce(source_list, @style_nonce)
  end
end
reject_all_values_if_none!(source_list) click to toggle source

Discard any 'none' values if more directives are supplied since none may override values.

# File lib/secure_headers/headers/content_security_policy.rb, line 123
def reject_all_values_if_none!(source_list)
  source_list.reject! { |value| value == NONE } if source_list.length > 1
end
strip_source_schemes!(source_list) click to toggle source

Private: Remove scheme from source expressions.

# File lib/secure_headers/headers/content_security_policy.rb, line 179
def strip_source_schemes!(source_list)
  source_list.map! { |source_expression| source_expression.sub(HTTP_SCHEME_REGEX, "") }
end
supported_directives() click to toggle source

Private: determine which directives are supported for the given user agent.

Add UA-sniffing special casing here.

Returns an array of symbols representing the directives.

# File lib/secure_headers/headers/content_security_policy.rb, line 188
def supported_directives
  @supported_directives ||= if VARIATIONS[@parsed_ua.browser]
    if @parsed_ua.browser == "Firefox" && @parsed_ua.version >= VERSION_46
      VARIATIONS["FirefoxTransitional"]
    else
      VARIATIONS[@parsed_ua.browser]
    end
  else
    VARIATIONS[OTHER]
  end
end
symbol_to_hyphen_case(sym) click to toggle source
# File lib/secure_headers/headers/content_security_policy.rb, line 204
def symbol_to_hyphen_case(sym)
  sym.to_s.tr('_', '-')
end