lib/rack/protection/content_security_policy.rb
# frozen_string_literal: true require 'rack/protection' module Rack module Protection ## # Prevented attack:: XSS and others # Supported browsers:: Firefox 23+, Safari 7+, Chrome 25+, Opera 15+ # # Description:: Content Security Policy, a mechanism web applications # can use to mitigate a broad class of content injection # vulnerabilities, such as cross-site scripting (XSS). # Content Security Policy is a declarative policy that lets # the authors (or server administrators) of a web application # inform the client about the sources from which the # application expects to load resources. # # More info:: W3C CSP Level 1 : https://www.w3.org/TR/CSP1/ (deprecated) # W3C CSP Level 2 : https://www.w3.org/TR/CSP2/ (current) # W3C CSP Level 3 : https://www.w3.org/TR/CSP3/ (draft) # https://developer.mozilla.org/en-US/docs/Web/Security/CSP # http://caniuse.com/#search=ContentSecurityPolicy # http://content-security-policy.com/ # https://securityheaders.io # https://scotthelme.co.uk/csp-cheat-sheet/ # http://www.html5rocks.com/en/tutorials/security/content-security-policy/ # # Sets the 'Content-Security-Policy[-Report-Only]' header. # # Options: ContentSecurityPolicy configuration is a complex topic with # several levels of support that has evolved over time. # See the W3C documentation and the links in the more info # section for CSP usage examples and best practices. The # CSP3 directives in the 'NO_ARG_DIRECTIVES' constant need to be # presented in the options hash with a boolean 'true' in order # to be used in a policy. # class ContentSecurityPolicy < Base default_options default_src: "'self'", report_only: false DIRECTIVES = %i[base_uri child_src connect_src default_src font_src form_action frame_ancestors frame_src img_src manifest_src media_src object_src plugin_types referrer reflected_xss report_to report_uri require_sri_for sandbox script_src style_src worker_src webrtc_src navigate_to prefetch_src].freeze NO_ARG_DIRECTIVES = %i[block_all_mixed_content disown_opener upgrade_insecure_requests].freeze def csp_policy directives = [] DIRECTIVES.each do |d| if options.key?(d) directives << "#{d.to_s.sub(/_/, '-')} #{options[d]}" end end # Set these key values to boolean 'true' to include in policy NO_ARG_DIRECTIVES.each do |d| if options.key?(d) && options[d].is_a?(TrueClass) directives << d.to_s.tr('_', '-') end end directives.compact.sort.join('; ') end def call(env) status, headers, body = @app.call(env) header = options[:report_only] ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy' headers[header] ||= csp_policy if html? headers [status, headers, body] end end end end