secure_headers 3.5.1 → 3.6.7

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +2 -0
  5. data/CHANGELOG.md +34 -1
  6. data/CODE_OF_CONDUCT.md +46 -0
  7. data/CONTRIBUTING.md +41 -0
  8. data/LICENSE +4 -199
  9. data/README.md +36 -384
  10. data/docs/HPKP.md +17 -0
  11. data/docs/cookies.md +50 -0
  12. data/docs/hashes.md +64 -0
  13. data/docs/named_overrides_and_appends.md +107 -0
  14. data/docs/per_action_configuration.md +105 -0
  15. data/docs/sinatra.md +25 -0
  16. data/lib/secure_headers/configuration.rb +24 -5
  17. data/lib/secure_headers/headers/clear_site_data.rb +54 -0
  18. data/lib/secure_headers/headers/content_security_policy.rb +2 -2
  19. data/lib/secure_headers/headers/content_security_policy_config.rb +45 -12
  20. data/lib/secure_headers/headers/policy_management.rb +22 -22
  21. data/lib/secure_headers/headers/public_key_pins.rb +1 -1
  22. data/lib/secure_headers/headers/referrer_policy.rb +1 -1
  23. data/lib/secure_headers/headers/strict_transport_security.rb +1 -1
  24. data/lib/secure_headers/headers/x_content_type_options.rb +1 -1
  25. data/lib/secure_headers/headers/x_download_options.rb +1 -1
  26. data/lib/secure_headers/headers/x_frame_options.rb +1 -1
  27. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -1
  28. data/lib/secure_headers/headers/x_xss_protection.rb +1 -1
  29. data/lib/secure_headers/view_helper.rb +2 -2
  30. data/lib/secure_headers.rb +5 -2
  31. data/secure_headers.gemspec +2 -2
  32. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +86 -0
  33. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +12 -8
  34. data/spec/lib/secure_headers/headers/policy_management_spec.rb +1 -0
  35. data/spec/lib/secure_headers/view_helpers_spec.rb +0 -1
  36. data/spec/lib/secure_headers_spec.rb +39 -4
  37. metadata +15 -4
@@ -0,0 +1,107 @@
1
+ ## Named Appends
2
+
3
+ Named Appends are blocks of code that can be reused and composed during requests. e.g. If a certain partial is rendered conditionally, and the csp needs to be adjusted for that partial, you can create a named append for that situation. The value returned by the block will be passed into `append_content_security_policy_directives`. The current request object is passed as an argument to the block for even more flexibility.
4
+
5
+ ```ruby
6
+ def show
7
+ if include_widget?
8
+ @widget = widget.render
9
+ use_content_security_policy_named_append(:widget_partial)
10
+ end
11
+ end
12
+
13
+
14
+ SecureHeaders::Configuration.named_append(:widget_partial) do |request|
15
+ SecureHeaders.override_x_frame_options(request, "DENY")
16
+ if request.controller_instance.current_user.in_test_bucket?
17
+ { child_src: %w(beta.thirdpartyhost.com) }
18
+ else
19
+ { child_src: %w(thirdpartyhost.com) }
20
+ end
21
+ end
22
+ ```
23
+
24
+ You can use as many named appends as you would like per request, but be careful because order of inclusion matters. Consider the following:
25
+
26
+ ```ruby
27
+ SecureHeader::Configuration.default do |config|
28
+ config.csp = { default_src: %w('self')}
29
+ end
30
+
31
+ SecureHeaders::Configuration.named_append(:A) do |request|
32
+ { default_src: %w(myhost.com) }
33
+ end
34
+
35
+ SecureHeaders::Configuration.named_append(:B) do |request|
36
+ { script_src: %w('unsafe-eval') }
37
+ end
38
+ ```
39
+
40
+ The following code will produce different policies due to the way policies are normalized (e.g. providing a previously undefined directive that inherits from `default-src`, removing host source values when `*` is provided. Removing `'none'` when additional values are present, etc.):
41
+
42
+ ```ruby
43
+ def index
44
+ use_content_security_policy_named_append(:A)
45
+ use_content_security_policy_named_append(:B)
46
+ # produces default-src 'self' myhost.com; script-src 'self' myhost.com 'unsafe-eval';
47
+ end
48
+
49
+ def show
50
+ use_content_security_policy_named_append(:B)
51
+ use_content_security_policy_named_append(:A)
52
+ # produces default-src 'self' myhost.com; script-src 'self' 'unsafe-eval';
53
+ end
54
+ ```
55
+
56
+
57
+ ## Named overrides
58
+
59
+ Named overrides serve two purposes:
60
+
61
+ * To be able to refer to a configuration by simple name.
62
+ * By precomputing the headers for a named configuration, the headers generated once and reused over every request.
63
+
64
+ To use a named override, drop a `SecureHeaders::Configuration.override` block **outside** of method definitions and then declare which named override you'd like to use. You can even override an override.
65
+
66
+ ```ruby
67
+ class ApplicationController < ActionController::Base
68
+ SecureHeaders::Configuration.default do |config|
69
+ config.csp = {
70
+ default_src: %w('self'),
71
+ script_src: %w(example.org)
72
+ }
73
+ end
74
+
75
+ # override default configuration
76
+ SecureHeaders::Configuration.override(:script_from_otherdomain_com) do |config|
77
+ config.csp[:script_src] << "otherdomain.com"
78
+ end
79
+
80
+ # overrides the :script_from_otherdomain_com configuration
81
+ SecureHeaders::Configuration.override(:another_config, :script_from_otherdomain_com) do |config|
82
+ config.csp[:script_src] << "evenanotherdomain.com"
83
+ end
84
+ end
85
+
86
+ class MyController < ApplicationController
87
+ def index
88
+ # Produces default-src 'self'; script-src example.org otherdomain.com
89
+ use_secure_headers_override(:script_from_otherdomain_com)
90
+ end
91
+
92
+ def show
93
+ # Produces default-src 'self'; script-src example.org otherdomain.org evenanotherdomain.com
94
+ use_secure_headers_override(:another_config)
95
+ end
96
+ end
97
+ ```
98
+
99
+ By default, a no-op configuration is provided. No headers will be set when this default override is used.
100
+
101
+ ```ruby
102
+ class MyController < ApplicationController
103
+ def index
104
+ SecureHeaders.opt_out_of_all_protection(request)
105
+ end
106
+ end
107
+ ```
@@ -0,0 +1,105 @@
1
+ ## Per-action configuration
2
+
3
+ You can override the settings for a given action by producing a temporary override. Be aware that because of the dynamic nature of the value, the header values will be computed per request.
4
+
5
+ ```ruby
6
+ # Given a config of:
7
+ ::SecureHeaders::Configuration.default do |config|
8
+ config.csp = {
9
+ default_src: %w('self'),
10
+ script_src: %w('self')
11
+ }
12
+ end
13
+
14
+ class MyController < ApplicationController
15
+ def index
16
+ # Append value to the source list, override 'none' values
17
+ # Produces: default-src 'self'; script-src 'self' s3.amazonaws.com; object-src 'self' www.youtube.com
18
+ append_content_security_policy_directives(script_src: %w(s3.amazonaws.com), object_src: %w('self' www.youtube.com))
19
+
20
+ # Overrides the previously set source list, override 'none' values
21
+ # Produces: default-src 'self'; script-src s3.amazonaws.com; object-src 'self'
22
+ override_content_security_policy_directives(script_src: %w(s3.amazonaws.com), object_src: %w('self'))
23
+
24
+ # Global settings default to "sameorigin"
25
+ override_x_frame_options("DENY")
26
+ end
27
+ ```
28
+
29
+ The following methods are available as controller instance methods. They are also available as class methods, but require you to pass in the `request` object.
30
+ * `append_content_security_policy_directives(hash)`: appends each value to the corresponding CSP app-wide configuration.
31
+ * `override_content_security_policy_directives(hash)`: merges the hash into the app-wide configuration, overwriting any previous config
32
+ * `override_x_frame_options(value)`: sets the `X-Frame-Options header` to `value`
33
+
34
+ ## Appending / overriding Content Security Policy
35
+
36
+ When manipulating content security policy, there are a few things to consider. The default header value is `default-src https:` which corresponds to a default configuration of `{ default_src: %w(https:)}`.
37
+
38
+ #### Append to the policy with a directive other than `default_src`
39
+
40
+ The value of `default_src` is joined with the addition if the it is a [fetch directive](https://w3c.github.io/webappsec-csp/#directives-fetch). Note the `https:` is carried over from the `default-src` config. If you do not want this, use `override_content_security_policy_directives` instead. To illustrate:
41
+
42
+ ```ruby
43
+ ::SecureHeaders::Configuration.default do |config|
44
+ config.csp = {
45
+ default_src: %w('self')
46
+ }
47
+ end
48
+ ```
49
+
50
+ Code | Result
51
+ ------------- | -------------
52
+ `append_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src 'self'; script-src 'self' mycdn.com`
53
+ `override_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src 'self'; script-src mycdn.com`
54
+
55
+ #### Nonce
56
+
57
+ You can use a view helper to automatically add nonces to script tags:
58
+
59
+ ```erb
60
+ <%= nonced_javascript_tag do %>
61
+ console.log("nonced!");
62
+ <% end %>
63
+
64
+ <%= nonced_style_tag do %>
65
+ body {
66
+ background-color: black;
67
+ }
68
+ <% end %>
69
+ ```
70
+
71
+ becomes:
72
+
73
+ ```html
74
+ <script nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
75
+ console.log("nonced!")
76
+ </script>
77
+ <style nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
78
+ body {
79
+ background-color: black;
80
+ }
81
+ </style>
82
+ ```
83
+
84
+ ```
85
+
86
+ Content-Security-Policy: ...
87
+ script-src 'nonce-/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=' ...;
88
+ style-src 'nonce-/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=' ...;
89
+ ```
90
+
91
+ `script`/`style-nonce` can be used to whitelist inline content. To do this, call the `content_security_policy_script_nonce` or `content_security_policy_style_nonce` then set the nonce attributes on the various tags.
92
+
93
+ ```erb
94
+ <script nonce="<%= content_security_policy_script_nonce %>">
95
+ console.log("whitelisted, will execute")
96
+ </script>
97
+
98
+ <script nonce="lol">
99
+ console.log("won't execute, not whitelisted")
100
+ </script>
101
+
102
+ <script>
103
+ console.log("won't execute, not whitelisted")
104
+ </script>
105
+ ```
data/docs/sinatra.md ADDED
@@ -0,0 +1,25 @@
1
+ ## Sinatra
2
+
3
+ Here's an example using SecureHeaders for Sinatra applications:
4
+
5
+ ```ruby
6
+ require 'rubygems'
7
+ require 'sinatra'
8
+ require 'haml'
9
+ require 'secure_headers'
10
+
11
+ use SecureHeaders::Middleware
12
+
13
+ SecureHeaders::Configuration.default do |config|
14
+ ...
15
+ end
16
+
17
+ class Donkey < Sinatra::Application
18
+ set :root, APP_ROOT
19
+
20
+ get '/' do
21
+ SecureHeaders.override_x_frame_options(request, SecureHeaders::OPT_OUT)
22
+ haml :index
23
+ end
24
+ end
25
+ ```
@@ -31,7 +31,7 @@ module SecureHeaders
31
31
  raise NotYetConfiguredError, "#{base} policy not yet supplied"
32
32
  end
33
33
  override = @configurations[base].dup
34
- override.instance_eval &block if block_given?
34
+ override.instance_eval(&block) if block_given?
35
35
  add_configuration(name, override)
36
36
  end
37
37
 
@@ -116,23 +116,40 @@ module SecureHeaders
116
116
 
117
117
  attr_writer :hsts, :x_frame_options, :x_content_type_options,
118
118
  :x_xss_protection, :x_download_options, :x_permitted_cross_domain_policies,
119
- :referrer_policy
119
+ :referrer_policy, :clear_site_data
120
120
 
121
121
  attr_reader :cached_headers, :csp, :cookies, :csp_report_only, :hpkp, :hpkp_report_host
122
122
 
123
+ @script_hashes = nil
124
+ @style_hashes = nil
125
+
123
126
  HASH_CONFIG_FILE = ENV["secure_headers_generated_hashes_file"] || "config/secure_headers_generated_hashes.yml"
124
- if File.exists?(HASH_CONFIG_FILE)
127
+ if File.exist?(HASH_CONFIG_FILE)
125
128
  config = YAML.safe_load(File.open(HASH_CONFIG_FILE))
126
129
  @script_hashes = config["scripts"]
127
130
  @style_hashes = config["styles"]
128
131
  end
129
132
 
130
133
  def initialize(&block)
134
+ @cookies = nil
135
+ @clear_site_data = nil
136
+ @csp = nil
137
+ @csp_report_only = nil
138
+ @hpkp_report_host = nil
139
+ @hpkp = nil
140
+ @hsts = nil
141
+ @x_content_type_options = nil
142
+ @x_download_options = nil
143
+ @x_frame_options = nil
144
+ @x_permitted_cross_domain_policies = nil
145
+ @x_xss_protection = nil
146
+
131
147
  self.hpkp = OPT_OUT
132
148
  self.referrer_policy = OPT_OUT
133
149
  self.csp = ContentSecurityPolicyConfig.new(ContentSecurityPolicyConfig::DEFAULT)
134
150
  self.csp_report_only = OPT_OUT
135
- instance_eval &block if block_given?
151
+
152
+ instance_eval(&block) if block_given?
136
153
  end
137
154
 
138
155
  # Public: copy everything but the cached headers
@@ -150,6 +167,7 @@ module SecureHeaders
150
167
  copy.x_xss_protection = @x_xss_protection
151
168
  copy.x_download_options = @x_download_options
152
169
  copy.x_permitted_cross_domain_policies = @x_permitted_cross_domain_policies
170
+ copy.clear_site_data = @clear_site_data
153
171
  copy.referrer_policy = @referrer_policy
154
172
  copy.hpkp = @hpkp
155
173
  copy.hpkp_report_host = @hpkp_report_host
@@ -181,6 +199,7 @@ module SecureHeaders
181
199
  XXssProtection.validate_config!(@x_xss_protection)
182
200
  XDownloadOptions.validate_config!(@x_download_options)
183
201
  XPermittedCrossDomainPolicies.validate_config!(@x_permitted_cross_domain_policies)
202
+ ClearSiteData.validate_config!(@clear_site_data)
184
203
  PublicKeyPins.validate_config!(@hpkp)
185
204
  Cookie.validate_config!(@cookies)
186
205
  end
@@ -260,7 +279,7 @@ module SecureHeaders
260
279
  end
261
280
  end
262
281
 
263
- # Public: Precompute the header names and values for this configuraiton.
282
+ # Public: Precompute the header names and values for this configuration.
264
283
  # Ensures that headers generated at configure time, not on demand.
265
284
  #
266
285
  # Returns the cached headers
@@ -0,0 +1,54 @@
1
+ module SecureHeaders
2
+ class ClearSiteDataConfigError < StandardError; end
3
+ class ClearSiteData
4
+ HEADER_NAME = "Clear-Site-Data".freeze
5
+
6
+ # Valid `types`
7
+ CACHE = "cache".freeze
8
+ COOKIES = "cookies".freeze
9
+ STORAGE = "storage".freeze
10
+ EXECTION_CONTEXTS = "executionContexts".freeze
11
+ ALL_TYPES = [CACHE, COOKIES, STORAGE, EXECTION_CONTEXTS]
12
+
13
+ CONFIG_KEY = :clear_site_data
14
+
15
+ class << self
16
+ # Public: make an Clear-Site-Data header name, value pair
17
+ #
18
+ # Returns nil if not configured, returns header name and value if configured.
19
+ def make_header(config=nil)
20
+ case config
21
+ when nil, OPT_OUT, []
22
+ # noop
23
+ when Array
24
+ [HEADER_NAME, make_header_value(config)]
25
+ when true
26
+ [HEADER_NAME, make_header_value(ALL_TYPES)]
27
+ end
28
+ end
29
+
30
+ def validate_config!(config)
31
+ case config
32
+ when nil, OPT_OUT, true
33
+ # valid
34
+ when Array
35
+ unless config.all? { |t| t.is_a?(String) }
36
+ raise ClearSiteDataConfigError.new("types must be Strings")
37
+ end
38
+ else
39
+ raise ClearSiteDataConfigError.new("config must be an Array of Strings or `true`")
40
+ end
41
+ end
42
+
43
+ # Public: Transform a Clear-Site-Data config (an Array of Strings) into a
44
+ # String that can be used as the value for the Clear-Site-Data header.
45
+ #
46
+ # types - An Array of String of types of data to clear.
47
+ #
48
+ # Returns a String of quoted values that are comma separated.
49
+ def make_header_value(types)
50
+ types.map { |t| "\"#{t}\""}.join(", ")
51
+ end
52
+ end
53
+ end
54
+ end
@@ -59,8 +59,6 @@ module SecureHeaders
59
59
  def normalize_child_frame_src
60
60
  if @config.frame_src && @config.child_src && @config.frame_src != @config.child_src
61
61
  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.")
62
- elsif @config.frame_src
63
- Kernel.warn("#{Kernel.caller.first}: [DEPRECATION] :frame_src is deprecated, use :child_src instead. Provided: #{@config.frame_src}.")
64
62
  end
65
63
 
66
64
  if supported_directives.include?(:child_src)
@@ -216,6 +214,8 @@ module SecureHeaders
216
214
  @supported_directives ||= if VARIATIONS[@parsed_ua.browser]
217
215
  if @parsed_ua.browser == "Firefox" && ((@parsed_ua.version || FALLBACK_VERSION) >= VERSION_46)
218
216
  VARIATIONS["FirefoxTransitional"]
217
+ elsif @parsed_ua.browser == "Safari" && ((@parsed_ua.version || FALLBACK_VERSION) >= VERSION_10)
218
+ VARIATIONS["SafariTransitional"]
219
219
  else
220
220
  VARIATIONS[@parsed_ua.browser]
221
221
  end
@@ -6,12 +6,7 @@ module SecureHeaders
6
6
  base.attrs.each do |attr|
7
7
  base.send(:define_method, "#{attr}=") do |value|
8
8
  if self.class.attrs.include?(attr)
9
- value = value.dup if PolicyManagement::DIRECTIVE_VALUE_TYPES[attr] == :source_list
10
- prev_value = self.instance_variable_get("@#{attr}")
11
- self.instance_variable_set("@#{attr}", value)
12
- if prev_value != self.instance_variable_get("@#{attr}")
13
- @modified = true
14
- end
9
+ write_attribute(attr, value)
15
10
  else
16
11
  raise ContentSecurityPolicyConfigError, "Unknown config directive: #{attr}=#{value}"
17
12
  end
@@ -20,6 +15,30 @@ module SecureHeaders
20
15
  end
21
16
 
22
17
  def initialize(hash)
18
+ @base_uri = nil
19
+ @block_all_mixed_content = nil
20
+ @child_src = nil
21
+ @connect_src = nil
22
+ @default_src = nil
23
+ @font_src = nil
24
+ @form_action = nil
25
+ @frame_ancestors = nil
26
+ @frame_src = nil
27
+ @img_src = nil
28
+ @manifest_src = nil
29
+ @media_src = nil
30
+ @object_src = nil
31
+ @plugin_types = nil
32
+ @preserve_schemes = nil
33
+ @report_only = nil
34
+ @report_uri = nil
35
+ @sandbox = nil
36
+ @script_nonce = nil
37
+ @script_src = nil
38
+ @style_nonce = nil
39
+ @style_src = nil
40
+ @upgrade_insecure_requests = nil
41
+
23
42
  from_hash(hash)
24
43
  @modified = false
25
44
  end
@@ -52,8 +71,9 @@ module SecureHeaders
52
71
 
53
72
  def to_h
54
73
  self.class.attrs.each_with_object({}) do |key, hash|
55
- hash[key] = self.send(key)
56
- end.reject { |_, v| v.nil? }
74
+ value = self.send(key)
75
+ hash[key] = value unless value.nil?
76
+ end
57
77
  end
58
78
 
59
79
  def dup
@@ -73,14 +93,26 @@ module SecureHeaders
73
93
 
74
94
  private
75
95
  def from_hash(hash)
76
- hash.keys.reject { |k| hash[k].nil? }.map do |k|
96
+ hash.each_pair do |k, v|
97
+ next if v.nil?
98
+
77
99
  if self.class.attrs.include?(k)
78
- self.send("#{k}=", hash[k])
100
+ write_attribute(k, v)
79
101
  else
80
- raise ContentSecurityPolicyConfigError, "Unknown config directive: #{k}=#{hash[k]}"
102
+ raise ContentSecurityPolicyConfigError, "Unknown config directive: #{k}=#{v}"
81
103
  end
82
104
  end
83
105
  end
106
+
107
+ def write_attribute(attr, value)
108
+ value = value.dup if PolicyManagement::DIRECTIVE_VALUE_TYPES[attr] == :source_list
109
+ attr_variable = "@#{attr}"
110
+ prev_value = self.instance_variable_get(attr_variable)
111
+ self.instance_variable_set(attr_variable, value)
112
+ if prev_value != value
113
+ @modified = true
114
+ end
115
+ end
84
116
  end
85
117
 
86
118
  class ContentSecurityPolicyConfigError < StandardError; end
@@ -88,8 +120,9 @@ module SecureHeaders
88
120
  CONFIG_KEY = :csp
89
121
  HEADER_NAME = "Content-Security-Policy".freeze
90
122
 
123
+ ATTRS = PolicyManagement::ALL_DIRECTIVES + PolicyManagement::META_CONFIGS + PolicyManagement::NONCES
91
124
  def self.attrs
92
- PolicyManagement::ALL_DIRECTIVES + PolicyManagement::META_CONFIGS + PolicyManagement::NONCES
125
+ ATTRS
93
126
  end
94
127
 
95
128
  include DynamicConfig
@@ -62,25 +62,19 @@ module SecureHeaders
62
62
 
63
63
  # All the directives currently under consideration for CSP level 3.
64
64
  # https://w3c.github.io/webappsec/specs/CSP2/
65
+ BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
65
66
  MANIFEST_SRC = :manifest_src
66
- REFLECTED_XSS = :reflected_xss
67
+ UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
67
68
  DIRECTIVES_3_0 = [
68
69
  DIRECTIVES_2_0,
69
- MANIFEST_SRC,
70
- REFLECTED_XSS
71
- ].flatten.freeze
72
-
73
- # All the directives that are not currently in a formal spec, but have
74
- # been implemented somewhere.
75
- BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
76
- UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
77
- DIRECTIVES_DRAFT = [
78
70
  BLOCK_ALL_MIXED_CONTENT,
71
+ MANIFEST_SRC,
79
72
  UPGRADE_INSECURE_REQUESTS
80
- ].freeze
73
+ ].flatten.freeze
81
74
 
82
75
  EDGE_DIRECTIVES = DIRECTIVES_1_0
83
76
  SAFARI_DIRECTIVES = DIRECTIVES_1_0
77
+ SAFARI_10_DIRECTIVES = DIRECTIVES_2_0
84
78
 
85
79
  FIREFOX_UNSUPPORTED_DIRECTIVES = [
86
80
  BLOCK_ALL_MIXED_CONTENT,
@@ -98,18 +92,18 @@ module SecureHeaders
98
92
  ].freeze
99
93
 
100
94
  FIREFOX_DIRECTIVES = (
101
- DIRECTIVES_2_0 + DIRECTIVES_DRAFT - FIREFOX_UNSUPPORTED_DIRECTIVES
95
+ DIRECTIVES_3_0 - FIREFOX_UNSUPPORTED_DIRECTIVES
102
96
  ).freeze
103
97
 
104
98
  FIREFOX_46_DIRECTIVES = (
105
- DIRECTIVES_2_0 + DIRECTIVES_DRAFT - FIREFOX_46_UNSUPPORTED_DIRECTIVES - FIREFOX_46_DEPRECATED_DIRECTIVES
99
+ DIRECTIVES_3_0 - FIREFOX_46_UNSUPPORTED_DIRECTIVES - FIREFOX_46_DEPRECATED_DIRECTIVES
106
100
  ).freeze
107
101
 
108
102
  CHROME_DIRECTIVES = (
109
- DIRECTIVES_2_0 + DIRECTIVES_DRAFT
103
+ DIRECTIVES_3_0
110
104
  ).freeze
111
105
 
112
- ALL_DIRECTIVES = [DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0 + DIRECTIVES_DRAFT].flatten.uniq.sort
106
+ ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0).uniq.sort
113
107
 
114
108
  # Think of default-src and report-uri as the beginning and end respectively,
115
109
  # everything else is in between.
@@ -133,6 +127,7 @@ module SecureHeaders
133
127
  "Firefox" => FIREFOX_DIRECTIVES,
134
128
  "FirefoxTransitional" => FIREFOX_46_DIRECTIVES,
135
129
  "Safari" => SAFARI_DIRECTIVES,
130
+ "SafariTransitional" => SAFARI_10_DIRECTIVES,
136
131
  "Edge" => EDGE_DIRECTIVES,
137
132
  "Other" => CHROME_DIRECTIVES
138
133
  }.freeze
@@ -154,7 +149,6 @@ module SecureHeaders
154
149
  MEDIA_SRC => :source_list,
155
150
  OBJECT_SRC => :source_list,
156
151
  PLUGIN_TYPES => :source_list,
157
- REFLECTED_XSS => :string,
158
152
  REPORT_URI => :source_list,
159
153
  SANDBOX => :source_list,
160
154
  SCRIPT_SRC => :source_list,
@@ -271,20 +265,26 @@ module SecureHeaders
271
265
  # copy the default-src value to the original config. This modifies the original hash.
272
266
  def populate_fetch_source_with_default!(original, additions)
273
267
  # in case we would be appending to an empty directive, fill it with the default-src value
274
- additions.keys.each do |directive|
268
+ additions.each_key do |directive|
275
269
  if !original[directive] && ((source_list?(directive) && FETCH_SOURCES.include?(directive)) || nonce_added?(original, additions))
276
270
  if nonce_added?(original, additions)
277
271
  inferred_directive = directive.to_s.gsub(/_nonce/, "_src").to_sym
278
272
  unless original[inferred_directive] || NON_FETCH_SOURCES.include?(inferred_directive)
279
- original[inferred_directive] = original[:default_src]
273
+ original[inferred_directive] = default_for(directive, original)
280
274
  end
281
275
  else
282
- original[directive] = original[:default_src]
276
+ original[directive] = default_for(directive, original)
283
277
  end
284
278
  end
285
279
  end
286
280
  end
287
281
 
282
+ def default_for(directive, original)
283
+ return original[FRAME_SRC] if directive == CHILD_SRC && original[FRAME_SRC]
284
+ return original[CHILD_SRC] if directive == FRAME_SRC && original[CHILD_SRC]
285
+ original[DEFAULT_SRC]
286
+ end
287
+
288
288
  def nonce_added?(original, additions)
289
289
  [:script_nonce, :style_nonce].each do |nonce|
290
290
  if additions[nonce] && !original[nonce]
@@ -340,9 +340,9 @@ module SecureHeaders
340
340
  end
341
341
 
342
342
  def ensure_valid_sources!(directive, source_expression)
343
- source_expression.each do |source_expression|
344
- if ContentSecurityPolicy::DEPRECATED_SOURCE_VALUES.include?(source_expression)
345
- raise ContentSecurityPolicyConfigError.new("#{directive} contains an invalid keyword source (#{source_expression}). This value must be single quoted.")
343
+ source_expression.each do |expression|
344
+ if ContentSecurityPolicy::DEPRECATED_SOURCE_VALUES.include?(expression)
345
+ raise ContentSecurityPolicyConfigError.new("#{directive} contains an invalid keyword source (#{expression}). This value must be single quoted.")
346
346
  end
347
347
  end
348
348
  end
@@ -49,7 +49,7 @@ module SecureHeaders
49
49
  end
50
50
 
51
51
  def value
52
- header_value = [
52
+ [
53
53
  max_age_directive,
54
54
  pin_directives,
55
55
  report_uri_directive,
@@ -1,7 +1,7 @@
1
1
  module SecureHeaders
2
2
  class ReferrerPolicyConfigError < StandardError; end
3
3
  class ReferrerPolicy
4
- HEADER_NAME = "Referrer-Policy"
4
+ HEADER_NAME = "Referrer-Policy".freeze
5
5
  DEFAULT_VALUE = "origin-when-cross-origin"
6
6
  VALID_POLICIES = %w(
7
7
  no-referrer
@@ -2,7 +2,7 @@ module SecureHeaders
2
2
  class STSConfigError < StandardError; end
3
3
 
4
4
  class StrictTransportSecurity
5
- HEADER_NAME = 'Strict-Transport-Security'
5
+ HEADER_NAME = 'Strict-Transport-Security'.freeze
6
6
  HSTS_MAX_AGE = "631138519"
7
7
  DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
8
8
  VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
@@ -2,7 +2,7 @@ module SecureHeaders
2
2
  class XContentTypeOptionsConfigError < StandardError; end
3
3
 
4
4
  class XContentTypeOptions
5
- HEADER_NAME = "X-Content-Type-Options"
5
+ HEADER_NAME = "X-Content-Type-Options".freeze
6
6
  DEFAULT_VALUE = "nosniff"
7
7
  CONFIG_KEY = :x_content_type_options
8
8
 
@@ -1,7 +1,7 @@
1
1
  module SecureHeaders
2
2
  class XDOConfigError < StandardError; end
3
3
  class XDownloadOptions
4
- HEADER_NAME = "X-Download-Options"
4
+ HEADER_NAME = "X-Download-Options".freeze
5
5
  DEFAULT_VALUE = 'noopen'
6
6
  CONFIG_KEY = :x_download_options
7
7
 
@@ -1,7 +1,7 @@
1
1
  module SecureHeaders
2
2
  class XFOConfigError < StandardError; end
3
3
  class XFrameOptions
4
- HEADER_NAME = "X-Frame-Options"
4
+ HEADER_NAME = "X-Frame-Options".freeze
5
5
  CONFIG_KEY = :x_frame_options
6
6
  SAMEORIGIN = "sameorigin"
7
7
  DENY = "deny"
@@ -1,7 +1,7 @@
1
1
  module SecureHeaders
2
2
  class XPCDPConfigError < StandardError; end
3
3
  class XPermittedCrossDomainPolicies
4
- HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
4
+ HEADER_NAME = "X-Permitted-Cross-Domain-Policies".freeze
5
5
  DEFAULT_VALUE = 'none'
6
6
  VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
7
7
  CONFIG_KEY = :x_permitted_cross_domain_policies
@@ -1,7 +1,7 @@
1
1
  module SecureHeaders
2
2
  class XXssProtectionConfigError < StandardError; end
3
3
  class XXssProtection
4
- HEADER_NAME = 'X-XSS-Protection'
4
+ HEADER_NAME = 'X-XSS-Protection'.freeze
5
5
  DEFAULT_VALUE = "1; mode=block"
6
6
  VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
7
7
  CONFIG_KEY = :x_xss_protection