secure_headers 3.9.0 → 4.0.0.alpha01
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- checksums.yaml +5 -5
 - data/.rspec +1 -0
 - data/.rubocop.yml +3 -0
 - data/.ruby-version +1 -1
 - data/.travis.yml +8 -6
 - data/CHANGELOG.md +2 -34
 - data/CONTRIBUTING.md +1 -1
 - data/Gemfile +7 -4
 - data/Guardfile +1 -0
 - data/README.md +4 -25
 - data/Rakefile +22 -18
 - data/docs/cookies.md +18 -5
 - data/lib/secure_headers.rb +1 -2
 - data/lib/secure_headers/configuration.rb +6 -16
 - data/lib/secure_headers/hash_helper.rb +2 -1
 - data/lib/secure_headers/headers/clear_site_data.rb +2 -1
 - data/lib/secure_headers/headers/content_security_policy.rb +14 -60
 - data/lib/secure_headers/headers/content_security_policy_config.rb +1 -1
 - data/lib/secure_headers/headers/cookie.rb +22 -10
 - data/lib/secure_headers/headers/policy_management.rb +57 -98
 - data/lib/secure_headers/headers/public_key_pins.rb +4 -3
 - data/lib/secure_headers/headers/referrer_policy.rb +1 -0
 - data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
 - data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
 - data/lib/secure_headers/headers/x_download_options.rb +2 -1
 - data/lib/secure_headers/headers/x_frame_options.rb +1 -0
 - data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -1
 - data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
 - data/lib/secure_headers/middleware.rb +10 -9
 - data/lib/secure_headers/railtie.rb +7 -6
 - data/lib/secure_headers/utils/cookies_config.rb +17 -18
 - data/lib/secure_headers/view_helper.rb +2 -1
 - data/lib/tasks/tasks.rake +2 -1
 - data/secure_headers.gemspec +13 -3
 - data/spec/lib/secure_headers/configuration_spec.rb +9 -8
 - data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +2 -1
 - data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +17 -53
 - data/spec/lib/secure_headers/headers/cookie_spec.rb +58 -37
 - data/spec/lib/secure_headers/headers/policy_management_spec.rb +20 -41
 - data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
 - data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
 - data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
 - data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
 - data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
 - data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
 - data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
 - data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
 - data/spec/lib/secure_headers/middleware_spec.rb +18 -21
 - data/spec/lib/secure_headers/view_helpers_spec.rb +5 -4
 - data/spec/lib/secure_headers_spec.rb +92 -120
 - data/spec/spec_helper.rb +9 -23
 - data/upgrading-to-4-0.md +49 -0
 - metadata +16 -11
 - data/lib/secure_headers/headers/expect_certificate_transparency.rb +0 -70
 - data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +0 -42
 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       2 
3 
     | 
    
         
             
              module DynamicConfig
         
     | 
| 
       3 
4 
     | 
    
         
             
                def self.included(base)
         
     | 
| 
         @@ -37,7 +38,6 @@ module SecureHeaders 
     | 
|
| 
       37 
38 
     | 
    
         
             
                  @script_src = nil
         
     | 
| 
       38 
39 
     | 
    
         
             
                  @style_nonce = nil
         
     | 
| 
       39 
40 
     | 
    
         
             
                  @style_src = nil
         
     | 
| 
       40 
     | 
    
         
            -
                  @worker_src = nil
         
     | 
| 
       41 
41 
     | 
    
         
             
                  @upgrade_insecure_requests = nil
         
     | 
| 
       42 
42 
     | 
    
         | 
| 
       43 
43 
     | 
    
         
             
                  from_hash(hash)
         
     | 
| 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
     | 
    
         
            -
            require  
     | 
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "cgi"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "secure_headers/utils/cookies_config"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       5 
7 
     | 
    
         
             
              class CookiesConfigError < StandardError; end
         
     | 
| 
         @@ -13,8 +15,18 @@ module SecureHeaders 
     | 
|
| 
       13 
15 
     | 
    
         | 
| 
       14 
16 
     | 
    
         
             
                attr_reader :raw_cookie, :config
         
     | 
| 
       15 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
                COOKIE_DEFAULTS = {
         
     | 
| 
      
 19 
     | 
    
         
            +
                  httponly: true,
         
     | 
| 
      
 20 
     | 
    
         
            +
                  secure: true,
         
     | 
| 
      
 21 
     | 
    
         
            +
                  samesite: { lax: true },
         
     | 
| 
      
 22 
     | 
    
         
            +
                }.freeze
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
       16 
24 
     | 
    
         
             
                def initialize(cookie, config)
         
     | 
| 
       17 
25 
     | 
    
         
             
                  @raw_cookie = cookie
         
     | 
| 
      
 26 
     | 
    
         
            +
                  unless config == OPT_OUT
         
     | 
| 
      
 27 
     | 
    
         
            +
                    config ||= {}
         
     | 
| 
      
 28 
     | 
    
         
            +
                    config = COOKIE_DEFAULTS.merge(config)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
       18 
30 
     | 
    
         
             
                  @config = config
         
     | 
| 
       19 
31 
     | 
    
         
             
                  @attributes = {
         
     | 
| 
       20 
32 
     | 
    
         
             
                    httponly: nil,
         
     | 
| 
         @@ -56,6 +68,7 @@ module SecureHeaders 
     | 
|
| 
       56 
68 
     | 
    
         
             
                end
         
     | 
| 
       57 
69 
     | 
    
         | 
| 
       58 
70 
     | 
    
         
             
                def flag_cookie?(attribute)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  return false if config == OPT_OUT
         
     | 
| 
       59 
72 
     | 
    
         
             
                  case config[attribute]
         
     | 
| 
       60 
73 
     | 
    
         
             
                  when TrueClass
         
     | 
| 
       61 
74 
     | 
    
         
             
                    true
         
     | 
| 
         @@ -81,13 +94,12 @@ module SecureHeaders 
     | 
|
| 
       81 
94 
     | 
    
         
             
                    "SameSite=Lax"
         
     | 
| 
       82 
95 
     | 
    
         
             
                  elsif flag_samesite_strict?
         
     | 
| 
       83 
96 
     | 
    
         
             
                    "SameSite=Strict"
         
     | 
| 
       84 
     | 
    
         
            -
                  elsif flag_samesite_none?
         
     | 
| 
       85 
     | 
    
         
            -
                    "SameSite=None"
         
     | 
| 
       86 
97 
     | 
    
         
             
                  end
         
     | 
| 
       87 
98 
     | 
    
         
             
                end
         
     | 
| 
       88 
99 
     | 
    
         | 
| 
       89 
100 
     | 
    
         
             
                def flag_samesite?
         
     | 
| 
       90 
     | 
    
         
            -
                   
     | 
| 
      
 101 
     | 
    
         
            +
                  return false if config == OPT_OUT || config[:samesite] == OPT_OUT
         
     | 
| 
      
 102 
     | 
    
         
            +
                  flag_samesite_lax? || flag_samesite_strict?
         
     | 
| 
       91 
103 
     | 
    
         
             
                end
         
     | 
| 
       92 
104 
     | 
    
         | 
| 
       93 
105 
     | 
    
         
             
                def flag_samesite_lax?
         
     | 
| 
         @@ -98,13 +110,13 @@ module SecureHeaders 
     | 
|
| 
       98 
110 
     | 
    
         
             
                  flag_samesite_enforcement?(:strict)
         
     | 
| 
       99 
111 
     | 
    
         
             
                end
         
     | 
| 
       100 
112 
     | 
    
         | 
| 
       101 
     | 
    
         
            -
                def flag_samesite_none?
         
     | 
| 
       102 
     | 
    
         
            -
                  flag_samesite_enforcement?(:none)
         
     | 
| 
       103 
     | 
    
         
            -
                end
         
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
113 
     | 
    
         
             
                def flag_samesite_enforcement?(mode)
         
     | 
| 
       106 
114 
     | 
    
         
             
                  return unless config[:samesite]
         
     | 
| 
       107 
115 
     | 
    
         | 
| 
      
 116 
     | 
    
         
            +
                  if config[:samesite].is_a?(TrueClass) && mode == :lax
         
     | 
| 
      
 117 
     | 
    
         
            +
                    return true
         
     | 
| 
      
 118 
     | 
    
         
            +
                  end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
       108 
120 
     | 
    
         
             
                  case config[:samesite][mode]
         
     | 
| 
       109 
121 
     | 
    
         
             
                  when Hash
         
     | 
| 
       110 
122 
     | 
    
         
             
                    conditionally_flag?(config[:samesite][mode])
         
     | 
| 
         @@ -119,7 +131,7 @@ module SecureHeaders 
     | 
|
| 
       119 
131 
     | 
    
         
             
                  return unless cookie
         
     | 
| 
       120 
132 
     | 
    
         | 
| 
       121 
133 
     | 
    
         
             
                  cookie.split(/[;,]\s?/).each do |pairs|
         
     | 
| 
       122 
     | 
    
         
            -
                    name, values = pairs.split( 
     | 
| 
      
 134 
     | 
    
         
            +
                    name, values = pairs.split("=", 2)
         
     | 
| 
       123 
135 
     | 
    
         
             
                    name = CGI.unescape(name)
         
     | 
| 
       124 
136 
     | 
    
         | 
| 
       125 
137 
     | 
    
         
             
                    attribute = name.downcase.to_sym
         
     | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       2 
3 
     | 
    
         
             
              module PolicyManagement
         
     | 
| 
       3 
4 
     | 
    
         
             
                def self.included(base)
         
     | 
| 
         @@ -5,8 +6,14 @@ module SecureHeaders 
     | 
|
| 
       5 
6 
     | 
    
         
             
                end
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
                MODERN_BROWSERS = %w(Chrome Opera Firefox)
         
     | 
| 
       8 
     | 
    
         
            -
                 
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
      
 9 
     | 
    
         
            +
                DEFAULT_CONFIG = {
         
     | 
| 
      
 10 
     | 
    
         
            +
                  default_src: %w(https:),
         
     | 
| 
      
 11 
     | 
    
         
            +
                  img_src: %w(https: data: 'self'),
         
     | 
| 
      
 12 
     | 
    
         
            +
                  object_src: %w('none'),
         
     | 
| 
      
 13 
     | 
    
         
            +
                  script_src: %w(https:),
         
     | 
| 
      
 14 
     | 
    
         
            +
                  style_src: %w('self' 'unsafe-inline' https:),
         
     | 
| 
      
 15 
     | 
    
         
            +
                  form_action: %w('self')
         
     | 
| 
      
 16 
     | 
    
         
            +
                }.freeze
         
     | 
| 
       10 
17 
     | 
    
         
             
                DATA_PROTOCOL = "data:".freeze
         
     | 
| 
       11 
18 
     | 
    
         
             
                BLOB_PROTOCOL = "blob:".freeze
         
     | 
| 
       12 
19 
     | 
    
         
             
                SELF = "'self'".freeze
         
     | 
| 
         @@ -65,13 +72,10 @@ module SecureHeaders 
     | 
|
| 
       65 
72 
     | 
    
         
             
                BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
         
     | 
| 
       66 
73 
     | 
    
         
             
                MANIFEST_SRC = :manifest_src
         
     | 
| 
       67 
74 
     | 
    
         
             
                UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
         
     | 
| 
       68 
     | 
    
         
            -
                WORKER_SRC = :worker_src
         
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
75 
     | 
    
         
             
                DIRECTIVES_3_0 = [
         
     | 
| 
       71 
76 
     | 
    
         
             
                  DIRECTIVES_2_0,
         
     | 
| 
       72 
77 
     | 
    
         
             
                  BLOCK_ALL_MIXED_CONTENT,
         
     | 
| 
       73 
78 
     | 
    
         
             
                  MANIFEST_SRC,
         
     | 
| 
       74 
     | 
    
         
            -
                  WORKER_SRC,
         
     | 
| 
       75 
79 
     | 
    
         
             
                  UPGRADE_INSECURE_REQUESTS
         
     | 
| 
       76 
80 
     | 
    
         
             
                ].flatten.freeze
         
     | 
| 
       77 
81 
     | 
    
         | 
| 
         @@ -82,7 +86,6 @@ module SecureHeaders 
     | 
|
| 
       82 
86 
     | 
    
         
             
                FIREFOX_UNSUPPORTED_DIRECTIVES = [
         
     | 
| 
       83 
87 
     | 
    
         
             
                  BLOCK_ALL_MIXED_CONTENT,
         
     | 
| 
       84 
88 
     | 
    
         
             
                  CHILD_SRC,
         
     | 
| 
       85 
     | 
    
         
            -
                  WORKER_SRC,
         
     | 
| 
       86 
89 
     | 
    
         
             
                  PLUGIN_TYPES
         
     | 
| 
       87 
90 
     | 
    
         
             
                ].freeze
         
     | 
| 
       88 
91 
     | 
    
         | 
| 
         @@ -92,7 +95,6 @@ module SecureHeaders 
     | 
|
| 
       92 
95 
     | 
    
         | 
| 
       93 
96 
     | 
    
         
             
                FIREFOX_46_UNSUPPORTED_DIRECTIVES = [
         
     | 
| 
       94 
97 
     | 
    
         
             
                  BLOCK_ALL_MIXED_CONTENT,
         
     | 
| 
       95 
     | 
    
         
            -
                  WORKER_SRC,
         
     | 
| 
       96 
98 
     | 
    
         
             
                  PLUGIN_TYPES
         
     | 
| 
       97 
99 
     | 
    
         
             
                ].freeze
         
     | 
| 
       98 
100 
     | 
    
         | 
| 
         @@ -114,6 +116,18 @@ module SecureHeaders 
     | 
|
| 
       114 
116 
     | 
    
         
             
                # everything else is in between.
         
     | 
| 
       115 
117 
     | 
    
         
             
                BODY_DIRECTIVES = ALL_DIRECTIVES - [DEFAULT_SRC, REPORT_URI]
         
     | 
| 
       116 
118 
     | 
    
         | 
| 
      
 119 
     | 
    
         
            +
                # These are directives that do not inherit the default-src value. This is
         
     | 
| 
      
 120 
     | 
    
         
            +
                # useful when calling #combine_policies.
         
     | 
| 
      
 121 
     | 
    
         
            +
                NON_FETCH_SOURCES = [
         
     | 
| 
      
 122 
     | 
    
         
            +
                  BASE_URI,
         
     | 
| 
      
 123 
     | 
    
         
            +
                  FORM_ACTION,
         
     | 
| 
      
 124 
     | 
    
         
            +
                  FRAME_ANCESTORS,
         
     | 
| 
      
 125 
     | 
    
         
            +
                  PLUGIN_TYPES,
         
     | 
| 
      
 126 
     | 
    
         
            +
                  REPORT_URI
         
     | 
| 
      
 127 
     | 
    
         
            +
                ]
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                FETCH_SOURCES = ALL_DIRECTIVES - NON_FETCH_SOURCES
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
       117 
131 
     | 
    
         
             
                VARIATIONS = {
         
     | 
| 
       118 
132 
     | 
    
         
             
                  "Chrome" => CHROME_DIRECTIVES,
         
     | 
| 
       119 
133 
     | 
    
         
             
                  "Opera" => CHROME_DIRECTIVES,
         
     | 
| 
         @@ -141,31 +155,14 @@ module SecureHeaders 
     | 
|
| 
       141 
155 
     | 
    
         
             
                  MANIFEST_SRC              => :source_list,
         
     | 
| 
       142 
156 
     | 
    
         
             
                  MEDIA_SRC                 => :source_list,
         
     | 
| 
       143 
157 
     | 
    
         
             
                  OBJECT_SRC                => :source_list,
         
     | 
| 
       144 
     | 
    
         
            -
                  PLUGIN_TYPES              => : 
     | 
| 
      
 158 
     | 
    
         
            +
                  PLUGIN_TYPES              => :source_list,
         
     | 
| 
       145 
159 
     | 
    
         
             
                  REPORT_URI                => :source_list,
         
     | 
| 
       146 
     | 
    
         
            -
                  SANDBOX                   => : 
     | 
| 
      
 160 
     | 
    
         
            +
                  SANDBOX                   => :source_list,
         
     | 
| 
       147 
161 
     | 
    
         
             
                  SCRIPT_SRC                => :source_list,
         
     | 
| 
       148 
162 
     | 
    
         
             
                  STYLE_SRC                 => :source_list,
         
     | 
| 
       149 
     | 
    
         
            -
                  WORKER_SRC                => :source_list,
         
     | 
| 
       150 
163 
     | 
    
         
             
                  UPGRADE_INSECURE_REQUESTS => :boolean
         
     | 
| 
       151 
164 
     | 
    
         
             
                }.freeze
         
     | 
| 
       152 
165 
     | 
    
         | 
| 
       153 
     | 
    
         
            -
                # These are directives that don't have use a source list, and hence do not
         
     | 
| 
       154 
     | 
    
         
            -
                # inherit the default-src value.
         
     | 
| 
       155 
     | 
    
         
            -
                NON_SOURCE_LIST_SOURCES = DIRECTIVE_VALUE_TYPES.select do |_, type|
         
     | 
| 
       156 
     | 
    
         
            -
                  type != :source_list
         
     | 
| 
       157 
     | 
    
         
            -
                end.keys.freeze
         
     | 
| 
       158 
     | 
    
         
            -
             
     | 
| 
       159 
     | 
    
         
            -
                # These are directives that take a source list, but that do not inherit
         
     | 
| 
       160 
     | 
    
         
            -
                # the default-src value.
         
     | 
| 
       161 
     | 
    
         
            -
                NON_FETCH_SOURCES = [
         
     | 
| 
       162 
     | 
    
         
            -
                  BASE_URI,
         
     | 
| 
       163 
     | 
    
         
            -
                  FORM_ACTION,
         
     | 
| 
       164 
     | 
    
         
            -
                  FRAME_ANCESTORS,
         
     | 
| 
       165 
     | 
    
         
            -
                  REPORT_URI
         
     | 
| 
       166 
     | 
    
         
            -
                ]
         
     | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
       168 
     | 
    
         
            -
                FETCH_SOURCES = ALL_DIRECTIVES - NON_FETCH_SOURCES - NON_SOURCE_LIST_SOURCES
         
     | 
| 
       169 
166 
     | 
    
         | 
| 
       170 
167 
     | 
    
         
             
                STAR_REGEXP = Regexp.new(Regexp.escape(STAR))
         
     | 
| 
       171 
168 
     | 
    
         
             
                HTTP_SCHEME_REGEX = %r{\Ahttps?://}
         
     | 
| 
         @@ -205,6 +202,7 @@ module SecureHeaders 
     | 
|
| 
       205 
202 
     | 
    
         
             
                  def validate_config!(config)
         
     | 
| 
       206 
203 
     | 
    
         
             
                    return if config.nil? || config.opt_out?
         
     | 
| 
       207 
204 
     | 
    
         
             
                    raise ContentSecurityPolicyConfigError.new(":default_src is required") unless config.directive_value(:default_src)
         
     | 
| 
      
 205 
     | 
    
         
            +
                    raise ContentSecurityPolicyConfigError.new(":script_src is required, falling back to default-src is too dangerous") unless config.directive_value(:script_src)
         
     | 
| 
       208 
206 
     | 
    
         
             
                    ContentSecurityPolicyConfig.attrs.each do |key|
         
     | 
| 
       209 
207 
     | 
    
         
             
                      value = config.directive_value(key)
         
     | 
| 
       210 
208 
     | 
    
         
             
                      next unless value
         
     | 
| 
         @@ -263,7 +261,7 @@ module SecureHeaders 
     | 
|
| 
       263 
261 
     | 
    
         
             
                  # when each hash contains a value for a given key.
         
     | 
| 
       264 
262 
     | 
    
         
             
                  def merge_policy_additions(original, additions)
         
     | 
| 
       265 
263 
     | 
    
         
             
                    original.merge(additions) do |directive, lhs, rhs|
         
     | 
| 
       266 
     | 
    
         
            -
                      if  
     | 
| 
      
 264 
     | 
    
         
            +
                      if source_list?(directive)
         
     | 
| 
       267 
265 
     | 
    
         
             
                        (lhs.to_a + rhs.to_a).compact.uniq
         
     | 
| 
       268 
266 
     | 
    
         
             
                      else
         
     | 
| 
       269 
267 
     | 
    
         
             
                        rhs
         
     | 
| 
         @@ -271,27 +269,20 @@ module SecureHeaders 
     | 
|
| 
       271 
269 
     | 
    
         
             
                    end.reject { |_, value| value.nil? || value == [] } # this mess prevents us from adding empty directives.
         
     | 
| 
       272 
270 
     | 
    
         
             
                  end
         
     | 
| 
       273 
271 
     | 
    
         | 
| 
       274 
     | 
    
         
            -
                  # Returns True if a directive expects a list of values and False otherwise.
         
     | 
| 
       275 
     | 
    
         
            -
                  def list_directive?(directive)
         
     | 
| 
       276 
     | 
    
         
            -
                    source_list?(directive) ||
         
     | 
| 
       277 
     | 
    
         
            -
                      sandbox_list?(directive) ||
         
     | 
| 
       278 
     | 
    
         
            -
                      media_type_list?(directive)
         
     | 
| 
       279 
     | 
    
         
            -
                  end
         
     | 
| 
       280 
     | 
    
         
            -
             
     | 
| 
       281 
272 
     | 
    
         
             
                  # For each directive in additions that does not exist in the original config,
         
     | 
| 
       282 
273 
     | 
    
         
             
                  # copy the default-src value to the original config. This modifies the original hash.
         
     | 
| 
       283 
274 
     | 
    
         
             
                  def populate_fetch_source_with_default!(original, additions)
         
     | 
| 
       284 
275 
     | 
    
         
             
                    # in case we would be appending to an empty directive, fill it with the default-src value
         
     | 
| 
       285 
276 
     | 
    
         
             
                    additions.each_key do |directive|
         
     | 
| 
       286 
     | 
    
         
            -
                      directive  
     | 
| 
       287 
     | 
    
         
            -
                         
     | 
| 
       288 
     | 
    
         
            -
             
     | 
| 
       289 
     | 
    
         
            -
             
     | 
| 
       290 
     | 
    
         
            -
             
     | 
| 
       291 
     | 
    
         
            -
             
     | 
| 
       292 
     | 
    
         
            -
             
     | 
| 
       293 
     | 
    
         
            -
             
     | 
| 
       294 
     | 
    
         
            -
                         
     | 
| 
      
 277 
     | 
    
         
            +
                      if !original[directive] && ((source_list?(directive) && FETCH_SOURCES.include?(directive)) || nonce_added?(original, additions))
         
     | 
| 
      
 278 
     | 
    
         
            +
                        if nonce_added?(original, additions)
         
     | 
| 
      
 279 
     | 
    
         
            +
                          inferred_directive = directive.to_s.gsub(/_nonce/, "_src").to_sym
         
     | 
| 
      
 280 
     | 
    
         
            +
                          unless original[inferred_directive] || NON_FETCH_SOURCES.include?(inferred_directive)
         
     | 
| 
      
 281 
     | 
    
         
            +
                            original[inferred_directive] = default_for(directive, original)
         
     | 
| 
      
 282 
     | 
    
         
            +
                          end
         
     | 
| 
      
 283 
     | 
    
         
            +
                        else
         
     | 
| 
      
 284 
     | 
    
         
            +
                          original[directive] = default_for(directive, original)
         
     | 
| 
      
 285 
     | 
    
         
            +
                        end
         
     | 
| 
       295 
286 
     | 
    
         
             
                      end
         
     | 
| 
       296 
287 
     | 
    
         
             
                    end
         
     | 
| 
       297 
288 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -302,77 +293,45 @@ module SecureHeaders 
     | 
|
| 
       302 
293 
     | 
    
         
             
                    original[DEFAULT_SRC]
         
     | 
| 
       303 
294 
     | 
    
         
             
                  end
         
     | 
| 
       304 
295 
     | 
    
         | 
| 
       305 
     | 
    
         
            -
                  def  
     | 
| 
       306 
     | 
    
         
            -
                     
     | 
| 
       307 
     | 
    
         
            -
             
     | 
| 
       308 
     | 
    
         
            -
             
     | 
| 
       309 
     | 
    
         
            -
             
     | 
| 
       310 
     | 
    
         
            -
                     
     | 
| 
      
 296 
     | 
    
         
            +
                  def nonce_added?(original, additions)
         
     | 
| 
      
 297 
     | 
    
         
            +
                    [:script_nonce, :style_nonce].each do |nonce|
         
     | 
| 
      
 298 
     | 
    
         
            +
                      if additions[nonce] && !original[nonce]
         
     | 
| 
      
 299 
     | 
    
         
            +
                        return true
         
     | 
| 
      
 300 
     | 
    
         
            +
                      end
         
     | 
| 
      
 301 
     | 
    
         
            +
                    end
         
     | 
| 
       311 
302 
     | 
    
         
             
                  end
         
     | 
| 
       312 
303 
     | 
    
         | 
| 
       313 
     | 
    
         
            -
                  def  
     | 
| 
       314 
     | 
    
         
            -
                    DIRECTIVE_VALUE_TYPES[directive] == : 
     | 
| 
      
 304 
     | 
    
         
            +
                  def source_list?(directive)
         
     | 
| 
      
 305 
     | 
    
         
            +
                    DIRECTIVE_VALUE_TYPES[directive] == :source_list
         
     | 
| 
       315 
306 
     | 
    
         
             
                  end
         
     | 
| 
       316 
307 
     | 
    
         | 
| 
       317 
308 
     | 
    
         
             
                  # Private: Validates that the configuration has a valid type, or that it is a valid
         
     | 
| 
       318 
309 
     | 
    
         
             
                  # source expression.
         
     | 
| 
       319 
     | 
    
         
            -
                  def validate_directive!(directive,  
     | 
| 
       320 
     | 
    
         
            -
                    ensure_valid_directive!(directive)
         
     | 
| 
      
 310 
     | 
    
         
            +
                  def validate_directive!(directive, source_expression)
         
     | 
| 
       321 
311 
     | 
    
         
             
                    case ContentSecurityPolicy::DIRECTIVE_VALUE_TYPES[directive]
         
     | 
| 
       322 
312 
     | 
    
         
             
                    when :boolean
         
     | 
| 
       323 
     | 
    
         
            -
                      unless boolean?( 
     | 
| 
       324 
     | 
    
         
            -
                        raise ContentSecurityPolicyConfigError.new("#{directive} must be a boolean 
     | 
| 
      
 313 
     | 
    
         
            +
                      unless boolean?(source_expression)
         
     | 
| 
      
 314 
     | 
    
         
            +
                        raise ContentSecurityPolicyConfigError.new("#{directive} must be a boolean value")
         
     | 
| 
      
 315 
     | 
    
         
            +
                      end
         
     | 
| 
      
 316 
     | 
    
         
            +
                    when :string
         
     | 
| 
      
 317 
     | 
    
         
            +
                      unless source_expression.is_a?(String)
         
     | 
| 
      
 318 
     | 
    
         
            +
                        raise ContentSecurityPolicyConfigError.new("#{directive} Must be a string. Found #{config.class}: #{config} value")
         
     | 
| 
       325 
319 
     | 
    
         
             
                      end
         
     | 
| 
       326 
     | 
    
         
            -
                    when :sandbox_list
         
     | 
| 
       327 
     | 
    
         
            -
                      validate_sandbox_expression!(directive, value)
         
     | 
| 
       328 
     | 
    
         
            -
                    when :media_type_list
         
     | 
| 
       329 
     | 
    
         
            -
                      validate_media_type_expression!(directive, value)
         
     | 
| 
       330 
     | 
    
         
            -
                    when :source_list
         
     | 
| 
       331 
     | 
    
         
            -
                      validate_source_expression!(directive, value)
         
     | 
| 
       332 
320 
     | 
    
         
             
                    else
         
     | 
| 
       333 
     | 
    
         
            -
                       
     | 
| 
       334 
     | 
    
         
            -
                    end
         
     | 
| 
       335 
     | 
    
         
            -
                  end
         
     | 
| 
       336 
     | 
    
         
            -
             
     | 
| 
       337 
     | 
    
         
            -
                  # Private: validates that a sandbox token expression:
         
     | 
| 
       338 
     | 
    
         
            -
                  # 1. is an array of strings or optionally `true` (to enable maximal sandboxing)
         
     | 
| 
       339 
     | 
    
         
            -
                  # 2. For arrays, each element is of the form allow-*
         
     | 
| 
       340 
     | 
    
         
            -
                  def validate_sandbox_expression!(directive, sandbox_token_expression)
         
     | 
| 
       341 
     | 
    
         
            -
                    # We support sandbox: true to indicate a maximally secure sandbox.
         
     | 
| 
       342 
     | 
    
         
            -
                    return if boolean?(sandbox_token_expression) && sandbox_token_expression == true
         
     | 
| 
       343 
     | 
    
         
            -
                    ensure_array_of_strings!(directive, sandbox_token_expression)
         
     | 
| 
       344 
     | 
    
         
            -
                    valid = sandbox_token_expression.compact.all? do |v|
         
     | 
| 
       345 
     | 
    
         
            -
                      v.is_a?(String) && v.start_with?("allow-")
         
     | 
| 
       346 
     | 
    
         
            -
                    end
         
     | 
| 
       347 
     | 
    
         
            -
                    if !valid
         
     | 
| 
       348 
     | 
    
         
            -
                      raise ContentSecurityPolicyConfigError.new("#{directive} must be True or an array of zero or more sandbox token strings (ex. allow-forms)")
         
     | 
| 
       349 
     | 
    
         
            -
                    end
         
     | 
| 
       350 
     | 
    
         
            -
                  end
         
     | 
| 
       351 
     | 
    
         
            -
             
     | 
| 
       352 
     | 
    
         
            -
                  # Private: validates that a media type expression:
         
     | 
| 
       353 
     | 
    
         
            -
                  # 1. is an array of strings
         
     | 
| 
       354 
     | 
    
         
            -
                  # 2. each element is of the form type/subtype
         
     | 
| 
       355 
     | 
    
         
            -
                  def validate_media_type_expression!(directive, media_type_expression)
         
     | 
| 
       356 
     | 
    
         
            -
                    ensure_array_of_strings!(directive, media_type_expression)
         
     | 
| 
       357 
     | 
    
         
            -
                    valid = media_type_expression.compact.all? do |v|
         
     | 
| 
       358 
     | 
    
         
            -
                      # All media types are of the form: <type from RFC 2045> "/" <subtype from RFC 2045>.
         
     | 
| 
       359 
     | 
    
         
            -
                      v =~ /\A.+\/.+\z/
         
     | 
| 
       360 
     | 
    
         
            -
                    end
         
     | 
| 
       361 
     | 
    
         
            -
                    if !valid
         
     | 
| 
       362 
     | 
    
         
            -
                      raise ContentSecurityPolicyConfigError.new("#{directive} must be an array of valid media types (ex. application/pdf)")
         
     | 
| 
      
 321 
     | 
    
         
            +
                      validate_source_expression!(directive, source_expression)
         
     | 
| 
       363 
322 
     | 
    
         
             
                    end
         
     | 
| 
       364 
323 
     | 
    
         
             
                  end
         
     | 
| 
       365 
324 
     | 
    
         | 
| 
       366 
325 
     | 
    
         
             
                  # Private: validates that a source expression:
         
     | 
| 
       367 
     | 
    
         
            -
                  # 1.  
     | 
| 
       368 
     | 
    
         
            -
                  # 2.  
     | 
| 
      
 326 
     | 
    
         
            +
                  # 1. has a valid name
         
     | 
| 
      
 327 
     | 
    
         
            +
                  # 2. is an array of strings
         
     | 
| 
      
 328 
     | 
    
         
            +
                  # 3. does not contain any depreated, now invalid values (inline, eval, self, none)
         
     | 
| 
       369 
329 
     | 
    
         
             
                  #
         
     | 
| 
       370 
330 
     | 
    
         
             
                  # Does not validate the invididual values of the source expression (e.g.
         
     | 
| 
       371 
331 
     | 
    
         
             
                  # script_src => h*t*t*p: will not raise an exception)
         
     | 
| 
       372 
332 
     | 
    
         
             
                  def validate_source_expression!(directive, source_expression)
         
     | 
| 
       373 
     | 
    
         
            -
                     
     | 
| 
       374 
     | 
    
         
            -
             
     | 
| 
       375 
     | 
    
         
            -
                    end
         
     | 
| 
      
 333 
     | 
    
         
            +
                    ensure_valid_directive!(directive)
         
     | 
| 
      
 334 
     | 
    
         
            +
                    ensure_array_of_strings!(directive, source_expression)
         
     | 
| 
       376 
335 
     | 
    
         
             
                    ensure_valid_sources!(directive, source_expression)
         
     | 
| 
       377 
336 
     | 
    
         
             
                  end
         
     | 
| 
       378 
337 
     | 
    
         | 
| 
         @@ -382,8 +341,8 @@ module SecureHeaders 
     | 
|
| 
       382 
341 
     | 
    
         
             
                    end
         
     | 
| 
       383 
342 
     | 
    
         
             
                  end
         
     | 
| 
       384 
343 
     | 
    
         | 
| 
       385 
     | 
    
         
            -
                  def ensure_array_of_strings!(directive,  
     | 
| 
       386 
     | 
    
         
            -
                     
     | 
| 
      
 344 
     | 
    
         
            +
                  def ensure_array_of_strings!(directive, source_expression)
         
     | 
| 
      
 345 
     | 
    
         
            +
                    unless source_expression.is_a?(Array) && source_expression.compact.all? { |v| v.is_a?(String) }
         
     | 
| 
       387 
346 
     | 
    
         
             
                      raise ContentSecurityPolicyConfigError.new("#{directive} must be an array of strings")
         
     | 
| 
       388 
347 
     | 
    
         
             
                    end
         
     | 
| 
       389 
348 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -1,3 +1,4 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       2 
3 
     | 
    
         
             
              class PublicKeyPinsConfigError < StandardError; end
         
     | 
| 
       3 
4 
     | 
    
         
             
              class PublicKeyPins
         
     | 
| 
         @@ -54,7 +55,7 @@ module SecureHeaders 
     | 
|
| 
       54 
55 
     | 
    
         
             
                    pin_directives,
         
     | 
| 
       55 
56 
     | 
    
         
             
                    report_uri_directive,
         
     | 
| 
       56 
57 
     | 
    
         
             
                    subdomain_directive
         
     | 
| 
       57 
     | 
    
         
            -
                  ].compact.join( 
     | 
| 
      
 58 
     | 
    
         
            +
                  ].compact.join("; ").strip
         
     | 
| 
       58 
59 
     | 
    
         
             
                end
         
     | 
| 
       59 
60 
     | 
    
         | 
| 
       60 
61 
     | 
    
         
             
                def pin_directives
         
     | 
| 
         @@ -63,7 +64,7 @@ module SecureHeaders 
     | 
|
| 
       63 
64 
     | 
    
         
             
                    pin.map do |token, hash|
         
     | 
| 
       64 
65 
     | 
    
         
             
                      "pin-#{token}=\"#{hash}\"" if HASH_ALGORITHMS.include?(token)
         
     | 
| 
       65 
66 
     | 
    
         
             
                    end
         
     | 
| 
       66 
     | 
    
         
            -
                  end.join( 
     | 
| 
      
 67 
     | 
    
         
            +
                  end.join("; ")
         
     | 
| 
       67 
68 
     | 
    
         
             
                end
         
     | 
| 
       68 
69 
     | 
    
         | 
| 
       69 
70 
     | 
    
         
             
                def max_age_directive
         
     | 
| 
         @@ -75,7 +76,7 @@ module SecureHeaders 
     | 
|
| 
       75 
76 
     | 
    
         
             
                end
         
     | 
| 
       76 
77 
     | 
    
         | 
| 
       77 
78 
     | 
    
         
             
                def subdomain_directive
         
     | 
| 
       78 
     | 
    
         
            -
                  @include_subdomains ?  
     | 
| 
      
 79 
     | 
    
         
            +
                  @include_subdomains ? "includeSubDomains" : nil
         
     | 
| 
       79 
80 
     | 
    
         
             
                end
         
     | 
| 
       80 
81 
     | 
    
         
             
              end
         
     | 
| 
       81 
82 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,8 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       2 
3 
     | 
    
         
             
              class STSConfigError < StandardError; end
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
5 
     | 
    
         
             
              class StrictTransportSecurity
         
     | 
| 
       5 
     | 
    
         
            -
                HEADER_NAME =  
     | 
| 
      
 6 
     | 
    
         
            +
                HEADER_NAME = "Strict-Transport-Security".freeze
         
     | 
| 
       6 
7 
     | 
    
         
             
                HSTS_MAX_AGE = "631138519"
         
     | 
| 
       7 
8 
     | 
    
         
             
                DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
         
     | 
| 
       8 
9 
     | 
    
         
             
                VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
         
     | 
| 
         @@ -1,8 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       2 
3 
     | 
    
         
             
              class XDOConfigError < StandardError; end
         
     | 
| 
       3 
4 
     | 
    
         
             
              class XDownloadOptions
         
     | 
| 
       4 
5 
     | 
    
         
             
                HEADER_NAME = "X-Download-Options".freeze
         
     | 
| 
       5 
     | 
    
         
            -
                DEFAULT_VALUE =  
     | 
| 
      
 6 
     | 
    
         
            +
                DEFAULT_VALUE = "noopen"
         
     | 
| 
       6 
7 
     | 
    
         
             
                CONFIG_KEY = :x_download_options
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
       8 
9 
     | 
    
         
             
                class << self
         
     | 
| 
         @@ -1,8 +1,9 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       2 
3 
     | 
    
         
             
              class XPCDPConfigError < StandardError; end
         
     | 
| 
       3 
4 
     | 
    
         
             
              class XPermittedCrossDomainPolicies
         
     | 
| 
       4 
5 
     | 
    
         
             
                HEADER_NAME = "X-Permitted-Cross-Domain-Policies".freeze
         
     | 
| 
       5 
     | 
    
         
            -
                DEFAULT_VALUE =  
     | 
| 
      
 6 
     | 
    
         
            +
                DEFAULT_VALUE = "none"
         
     | 
| 
       6 
7 
     | 
    
         
             
                VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
         
     | 
| 
       7 
8 
     | 
    
         
             
                CONFIG_KEY = :x_permitted_cross_domain_policies
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
         @@ -1,7 +1,8 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
       1 
2 
     | 
    
         
             
            module SecureHeaders
         
     | 
| 
       2 
3 
     | 
    
         
             
              class XXssProtectionConfigError < StandardError; end
         
     | 
| 
       3 
4 
     | 
    
         
             
              class XXssProtection
         
     | 
| 
       4 
     | 
    
         
            -
                HEADER_NAME =  
     | 
| 
      
 5 
     | 
    
         
            +
                HEADER_NAME = "X-XSS-Protection".freeze
         
     | 
| 
       5 
6 
     | 
    
         
             
                DEFAULT_VALUE = "1; mode=block"
         
     | 
| 
       6 
7 
     | 
    
         
             
                VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
         
     | 
| 
       7 
8 
     | 
    
         
             
                CONFIG_KEY = :x_xss_protection
         
     |