secure_headers 3.1.2 → 3.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ebe74bc751469bb4305dd36b59fe4881a87ec3d
4
- data.tar.gz: 83fed6de7e4cab010cab6010caa0ba005ac43fc7
3
+ metadata.gz: c3acd5dfb3fc3a7c4037ef42b72765a4f1b34ede
4
+ data.tar.gz: 98f0ce842bac69bf7c5f4d3d4cc19e4281ebe00e
5
5
  SHA512:
6
- metadata.gz: bd02880ba737a5a9489dd6e8209420259fec8d78330f5992e7b34f61ebe16677a93ff123dd3b417f6973bfcd85e03524b053bcd1487458492aa4c7b8a7a9bb40
7
- data.tar.gz: 550c48cfd47e656e70dd3ab39ed6386b8d148231d55a553395da6076bc88bdaaa7fe1803695b093cf3e4fd49cb30bed0aff15a2455652efd00d7a27d9ce55636
6
+ metadata.gz: 75d3b9033b09f0ac40675e8e8706185d9c0ca465a38f857228bf56857f5f44ba1eab5559b9c9f64f34ee398226ccd9dda5e26923f5325dcadd7be8ff0a957314
7
+ data.tar.gz: f6ffb2babf72009e60a4d4663ee6e933db8f8acb7159021e66a07168e1c6743df621309fb63e884f3257217d28951b196b2a192739860ce459f4fbabbab35622
data/CHANGELOG.md CHANGED
@@ -1,3 +1,129 @@
1
+ ## 3.3.0 referrer-policy support
2
+
3
+ While not officially part of the spec and not implemented anywhere, support for the experimental [`referrer-policy` header](https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-header) was [preemptively added](https://github.com/twitter/secureheaders/pull/249).
4
+
5
+ Additionally, two minor enhancements were added this version:
6
+ 1. [Warn when the HPKP report host is the same as the current host](https://github.com/twitter/secureheaders/pull/246). By definition any generated reports would be reporting to a known compromised connection.
7
+ 1. [Filter unsupported CSP directives when using Edge](https://github.com/twitter/secureheaders/pull/247). Previously, this was causing many warnings in the developer console.
8
+
9
+ ## 3.2.0 Cookie settings and CSP hash sources
10
+
11
+ ### Cookies
12
+
13
+ SecureHeaders supports `Secure`, `HttpOnly` and [`SameSite`](https://tools.ietf.org/html/draft-west-first-party-cookies-07) cookies. These can be defined in the form of a boolean, or as a Hash for more refined configuration.
14
+
15
+ __Note__: Regardless of the configuration specified, Secure cookies are only enabled for HTTPS requests.
16
+
17
+ #### Boolean-based configuration
18
+
19
+ Boolean-based configuration is intended to globally enable or disable a specific cookie attribute.
20
+
21
+ ```ruby
22
+ config.cookies = {
23
+ secure: true, # mark all cookies as Secure
24
+ httponly: false, # do not mark any cookies as HttpOnly
25
+ }
26
+ ```
27
+
28
+ #### Hash-based configuration
29
+
30
+ Hash-based configuration allows for fine-grained control.
31
+
32
+ ```ruby
33
+ config.cookies = {
34
+ secure: { except: ['_guest'] }, # mark all but the `_guest` cookie as Secure
35
+ httponly: { only: ['_rails_session'] }, # only mark the `_rails_session` cookie as HttpOnly
36
+ }
37
+ ```
38
+
39
+ #### SameSite cookie configuration
40
+
41
+ SameSite cookies permit either `Strict` or `Lax` enforcement mode options.
42
+
43
+ ```ruby
44
+ config.cookies = {
45
+ samesite: {
46
+ strict: true # mark all cookies as SameSite=Strict
47
+ }
48
+ }
49
+ ```
50
+
51
+ `Strict` and `Lax` enforcement modes can also be specified using a Hash.
52
+
53
+ ```ruby
54
+ config.cookies = {
55
+ samesite: {
56
+ strict: { only: ['_rails_session'] },
57
+ lax: { only: ['_guest'] }
58
+ }
59
+ }
60
+ ```
61
+
62
+ #### Hash
63
+
64
+ `script`/`style-src` hashes can be used to whitelist inline content that is static. This has the benefit of allowing inline content without opening up the possibility of dynamic javascript like you would with a `nonce`.
65
+
66
+ You can add hash sources directly to your policy :
67
+
68
+ ```ruby
69
+ ::SecureHeaders::Configuration.default do |config|
70
+ config.csp = {
71
+ default_src: %w('self')
72
+
73
+ # this is a made up value but browsers will show the expected hash in the console.
74
+ script_src: %w(sha256-123456)
75
+ }
76
+ end
77
+ ```
78
+
79
+ You can also use the automated inline script detection/collection/computation of hash source values in your app.
80
+
81
+ ```bash
82
+ rake secure_headers:generate_hashes
83
+ ```
84
+
85
+ This will generate a file (`config/config/secure_headers_generated_hashes.yml` by default, you can override by setting `ENV["secure_headers_generated_hashes_file"]`) containing a mapping of file names with the array of hash values found on that page. When ActionView renders a given file, we check if there are any known hashes for that given file. If so, they are added as values to the header.
86
+
87
+ ```yaml
88
+ ---
89
+ scripts:
90
+ app/views/asdfs/index.html.erb:
91
+ - "'sha256-yktKiAsZWmc8WpOyhnmhQoDf9G2dAZvuBBC+V0LGQhg='"
92
+ styles:
93
+ app/views/asdfs/index.html.erb:
94
+ - "'sha256-SLp6LO3rrKDJwsG9uJUxZapb4Wp2Zhj6Bu3l+d9rnAY='"
95
+ - "'sha256-HSGHqlRoKmHAGTAJ2Rq0piXX4CnEbOl1ArNd6ejp2TE='"
96
+ ```
97
+
98
+ ##### Helpers
99
+
100
+ **This will not compute dynamic hashes** by design. The output of both helpers will be a plain `script`/`style` tag without modification and the known hashes for a given file will be added to `script-src`/`style-src` when `hashed_javascript_tag` and `hashed_style_tag` are used. You can use `raise_error_on_unrecognized_hash = true` to be extra paranoid that you have precomputed hash values for all of your inline content. By default, this will raise an error in non-production environments.
101
+
102
+ ```erb
103
+ <%= hashed_style_tag do %>
104
+ body {
105
+ background-color: black;
106
+ }
107
+ <% end %>
108
+
109
+ <%= hashed_style_tag do %>
110
+ body {
111
+ font-size: 30px;
112
+ font-color: green;
113
+ }
114
+ <% end %>
115
+
116
+ <%= hashed_javascript_tag do %>
117
+ console.log(1)
118
+ <% end %>
119
+ ```
120
+
121
+ ```
122
+ Content-Security-Policy: ...
123
+ script-src 'sha256-yktKiAsZWmc8WpOyhnmhQoDf9G2dAZvuBBC+V0LGQhg=' ... ;
124
+ style-src 'sha256-SLp6LO3rrKDJwsG9uJUxZapb4Wp2Zhj6Bu3l+d9rnAY=' 'sha256-HSGHqlRoKmHAGTAJ2Rq0piXX4CnEbOl1ArNd6ejp2TE=' ...;
125
+ ```
126
+
1
127
  ## 3.1.2 Bug fix for regression
2
128
 
3
129
  See https://github.com/twitter/secureheaders/pull/239
data/README.md CHANGED
@@ -13,9 +13,10 @@ The gem will automatically apply several headers that are related to security.
13
13
  - X-Content-Type-Options - [Prevent content type sniffing](https://msdn.microsoft.com/library/gg622941\(v=vs.85\).aspx)
14
14
  - X-Download-Options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
15
15
  - X-Permitted-Cross-Domain-Policies - [Restrict Adobe Flash Player's access to data](https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html)
16
+ - Referrer-Policy - [Referrer Policy draft](https://w3c.github.io/webappsec-referrer-policy/)
16
17
  - Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key Pinning Specification](https://tools.ietf.org/html/rfc7469)
17
18
 
18
- It can also mark all http cookies with the secure attribute (when configured to do so).
19
+ It can also mark all http cookies with the Secure, HttpOnly and SameSite attributes (when configured to do so).
19
20
 
20
21
  `secure_headers` is a library with a global config, per request overrides, and rack middleware that enables you customize your application settings.
21
22
 
@@ -31,13 +32,20 @@ All `nil` values will fallback to their default values. `SecureHeaders::OPT_OUT`
31
32
 
32
33
  ```ruby
33
34
  SecureHeaders::Configuration.default do |config|
34
- config.secure_cookies = true # mark all cookies as "secure"
35
+ config.cookies = {
36
+ secure: true, # mark all cookies as "Secure"
37
+ httponly: true, # mark all cookies as "HttpOnly"
38
+ samesite: {
39
+ strict: true # mark all cookies as SameSite=Strict
40
+ }
41
+ }
35
42
  config.hsts = "max-age=#{20.years.to_i}; includeSubdomains; preload"
36
43
  config.x_frame_options = "DENY"
37
44
  config.x_content_type_options = "nosniff"
38
45
  config.x_xss_protection = "1; mode=block"
39
46
  config.x_download_options = "noopen"
40
47
  config.x_permitted_cross_domain_policies = "none"
48
+ config.referrer_policy = "origin-when-cross-origin"
41
49
  config.csp = {
42
50
  # "meta" values. these will shaped the header, but the values are not included in the header.
43
51
  report_only: true, # default: false
@@ -195,24 +203,6 @@ Code | Result
195
203
 
196
204
  #### Nonce
197
205
 
198
- script/style-nonce can be used to whitelist inline content. To do this, call the `SecureHeaders.content_security_policy_nonce` then set the nonce attributes on the various tags.
199
-
200
- Setting a nonce will also set 'unsafe-inline' for browsers that don't support nonces for backwards compatibility. 'unsafe-inline' is ignored if a nonce is present in a directive in compliant browsers.
201
-
202
- ```erb
203
- <script nonce="<%= content_security_policy_nonce %>">
204
- console.log("whitelisted, will execute")
205
- </script>
206
-
207
- <script nonce="lol">
208
- console.log("won't execute, not whitelisted")
209
- </script>
210
-
211
- <script>
212
- console.log("won't execute, not whitelisted")
213
- </script>
214
- ```
215
-
216
206
  You can use a view helper to automatically add nonces to script tags:
217
207
 
218
208
  ```erb
@@ -240,9 +230,93 @@ body {
240
230
  </style>
241
231
  ```
242
232
 
233
+ ```
234
+
235
+ Content-Security-Policy: ...
236
+ script-src 'nonce-/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=' ...;
237
+ style-src 'nonce-/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=' ...;
238
+ ```
239
+
240
+ `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.
241
+
242
+ ```erb
243
+ <script nonce="<%= content_security_policy_script_nonce %>">
244
+ console.log("whitelisted, will execute")
245
+ </script>
246
+
247
+ <script nonce="lol">
248
+ console.log("won't execute, not whitelisted")
249
+ </script>
250
+
251
+ <script>
252
+ console.log("won't execute, not whitelisted")
253
+ </script>
254
+ ```
255
+
243
256
  #### Hash
244
257
 
245
- The hash feature has been removed, for now.
258
+ `script`/`style-src` hashes can be used to whitelist inline content that is static. This has the benefit of allowing inline content without opening up the possibility of dynamic javascript like you would with a `nonce`.
259
+
260
+ You can add hash sources directly to your policy :
261
+
262
+ ```ruby
263
+ ::SecureHeaders::Configuration.default do |config|
264
+ config.csp = {
265
+ default_src: %w('self')
266
+
267
+ # this is a made up value but browsers will show the expected hash in the console.
268
+ script_src: %w(sha256-123456)
269
+ }
270
+ end
271
+ ```
272
+
273
+ You can also use the automated inline script detection/collection/computation of hash source values in your app.
274
+
275
+ ```bash
276
+ rake secure_headers:generate_hashes
277
+ ```
278
+
279
+ This will generate a file (`config/config/secure_headers_generated_hashes.yml` by default, you can override by setting `ENV["secure_headers_generated_hashes_file"]`) containing a mapping of file names with the array of hash values found on that page. When ActionView renders a given file, we check if there are any known hashes for that given file. If so, they are added as values to the header.
280
+
281
+ ```yaml
282
+ ---
283
+ scripts:
284
+ app/views/asdfs/index.html.erb:
285
+ - "'sha256-yktKiAsZWmc8WpOyhnmhQoDf9G2dAZvuBBC+V0LGQhg='"
286
+ styles:
287
+ app/views/asdfs/index.html.erb:
288
+ - "'sha256-SLp6LO3rrKDJwsG9uJUxZapb4Wp2Zhj6Bu3l+d9rnAY='"
289
+ - "'sha256-HSGHqlRoKmHAGTAJ2Rq0piXX4CnEbOl1ArNd6ejp2TE='"
290
+ ```
291
+
292
+ ##### Helpers
293
+
294
+ **This will not compute dynamic hashes** by design. The output of both helpers will be a plain `script`/`style` tag without modification and the known hashes for a given file will be added to `script-src`/`style-src` when `hashed_javascript_tag` and `hashed_style_tag` are used. You can use `raise_error_on_unrecognized_hash = true` to be extra paranoid that you have precomputed hash values for all of your inline content. By default, this will raise an error in non-production environments.
295
+
296
+ ```erb
297
+ <%= hashed_style_tag do %>
298
+ body {
299
+ background-color: black;
300
+ }
301
+ <% end %>
302
+
303
+ <%= hashed_style_tag do %>
304
+ body {
305
+ font-size: 30px;
306
+ font-color: green;
307
+ }
308
+ <% end %>
309
+
310
+ <%= hashed_javascript_tag do %>
311
+ console.log(1)
312
+ <% end %>
313
+ ```
314
+
315
+ ```
316
+ Content-Security-Policy: ...
317
+ script-src 'sha256-yktKiAsZWmc8WpOyhnmhQoDf9G2dAZvuBBC+V0LGQhg=' ... ;
318
+ style-src 'sha256-SLp6LO3rrKDJwsG9uJUxZapb4Wp2Zhj6Bu3l+d9rnAY=' 'sha256-HSGHqlRoKmHAGTAJ2Rq0piXX4CnEbOl1ArNd6ejp2TE=' ...;
319
+ ```
246
320
 
247
321
  ### Public Key Pins
248
322
 
@@ -258,9 +332,58 @@ config.hpkp = {
258
332
  {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
259
333
  ],
260
334
  report_only: true, # defaults to false (report-only mode)
261
- report_uri: 'https://report-uri.io/example-hpkp',
262
- app_name: 'example',
263
- tag_report_uri: true
335
+ report_uri: 'https://report-uri.io/example-hpkp'
336
+ }
337
+ ```
338
+
339
+ ### Cookies
340
+
341
+ SecureHeaders supports `Secure`, `HttpOnly` and [`SameSite`](https://tools.ietf.org/html/draft-west-first-party-cookies-07) cookies. These can be defined in the form of a boolean, or as a Hash for more refined configuration.
342
+
343
+ __Note__: Regardless of the configuration specified, Secure cookies are only enabled for HTTPS requests.
344
+
345
+ #### Boolean-based configuration
346
+
347
+ Boolean-based configuration is intended to globally enable or disable a specific cookie attribute.
348
+
349
+ ```ruby
350
+ config.cookies = {
351
+ secure: true, # mark all cookies as Secure
352
+ httponly: false, # do not mark any cookies as HttpOnly
353
+ }
354
+ ```
355
+
356
+ #### Hash-based configuration
357
+
358
+ Hash-based configuration allows for fine-grained control.
359
+
360
+ ```ruby
361
+ config.cookies = {
362
+ secure: { except: ['_guest'] }, # mark all but the `_guest` cookie as Secure
363
+ httponly: { only: ['_rails_session'] }, # only mark the `_rails_session` cookie as HttpOnly
364
+ }
365
+ ```
366
+
367
+ #### SameSite cookie configuration
368
+
369
+ SameSite cookies permit either `Strict` or `Lax` enforcement mode options.
370
+
371
+ ```ruby
372
+ config.cookies = {
373
+ samesite: {
374
+ strict: true # mark all cookies as SameSite=Strict
375
+ }
376
+ }
377
+ ```
378
+
379
+ `Strict` and `Lax` enforcement modes can also be specified using a Hash.
380
+
381
+ ```ruby
382
+ config.cookies = {
383
+ samesite: {
384
+ strict: { only: ['_rails_session'] },
385
+ lax: { only: ['_guest'] }
386
+ }
264
387
  }
265
388
  ```
266
389
 
@@ -1,3 +1,5 @@
1
+ require 'yaml'
2
+
1
3
  module SecureHeaders
2
4
  class Configuration
3
5
  DEFAULT_CONFIG = :default
@@ -59,6 +61,7 @@ module SecureHeaders
59
61
  config.validate_config!
60
62
  @configurations ||= {}
61
63
  config.send(:cache_headers!)
64
+ config.send(:cache_hpkp_report_host)
62
65
  config.freeze
63
66
  @configurations[name] = config
64
67
  end
@@ -100,14 +103,24 @@ module SecureHeaders
100
103
  end
101
104
  end
102
105
 
106
+ attr_accessor :dynamic_csp
107
+
103
108
  attr_writer :hsts, :x_frame_options, :x_content_type_options,
104
109
  :x_xss_protection, :x_download_options, :x_permitted_cross_domain_policies,
105
- :hpkp, :dynamic_csp, :secure_cookies
110
+ :referrer_policy
106
111
 
107
- attr_reader :cached_headers, :csp, :dynamic_csp, :secure_cookies
112
+ attr_reader :cached_headers, :csp, :cookies, :hpkp, :hpkp_report_host
113
+
114
+ HASH_CONFIG_FILE = ENV["secure_headers_generated_hashes_file"] || "config/secure_headers_generated_hashes.yml"
115
+ if File.exists?(HASH_CONFIG_FILE)
116
+ config = YAML.safe_load(File.open(HASH_CONFIG_FILE))
117
+ @script_hashes = config["scripts"]
118
+ @style_hashes = config["styles"]
119
+ end
108
120
 
109
121
  def initialize(&block)
110
122
  self.hpkp = OPT_OUT
123
+ self.referrer_policy = OPT_OUT
111
124
  self.csp = self.class.send(:deep_copy, CSP::DEFAULT_CONFIG)
112
125
  instance_eval &block if block_given?
113
126
  end
@@ -117,7 +130,7 @@ module SecureHeaders
117
130
  # Returns a deep-dup'd copy of this configuration.
118
131
  def dup
119
132
  copy = self.class.new
120
- copy.secure_cookies = @secure_cookies
133
+ copy.cookies = @cookies
121
134
  copy.csp = self.class.send(:deep_copy_if_hash, @csp)
122
135
  copy.dynamic_csp = self.class.send(:deep_copy_if_hash, @dynamic_csp)
123
136
  copy.cached_headers = self.class.send(:deep_copy_if_hash, @cached_headers)
@@ -127,7 +140,9 @@ module SecureHeaders
127
140
  copy.x_xss_protection = @x_xss_protection
128
141
  copy.x_download_options = @x_download_options
129
142
  copy.x_permitted_cross_domain_policies = @x_permitted_cross_domain_policies
143
+ copy.referrer_policy = @referrer_policy
130
144
  copy.hpkp = @hpkp
145
+ copy.hpkp_report_host = @hpkp_report_host
131
146
  copy
132
147
  end
133
148
 
@@ -166,30 +181,57 @@ module SecureHeaders
166
181
  def validate_config!
167
182
  StrictTransportSecurity.validate_config!(@hsts)
168
183
  ContentSecurityPolicy.validate_config!(@csp)
184
+ ReferrerPolicy.validate_config!(@referrer_policy)
169
185
  XFrameOptions.validate_config!(@x_frame_options)
170
186
  XContentTypeOptions.validate_config!(@x_content_type_options)
171
187
  XXssProtection.validate_config!(@x_xss_protection)
172
188
  XDownloadOptions.validate_config!(@x_download_options)
173
189
  XPermittedCrossDomainPolicies.validate_config!(@x_permitted_cross_domain_policies)
174
190
  PublicKeyPins.validate_config!(@hpkp)
191
+ Cookie.validate_config!(@cookies)
192
+ end
193
+
194
+ def secure_cookies=(secure_cookies)
195
+ Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#secure_cookies=` is deprecated. Please use `#cookies=` to configure secure cookies instead."
196
+ @cookies = (@cookies || {}).merge(secure: secure_cookies)
175
197
  end
176
198
 
177
199
  protected
178
200
 
179
201
  def csp=(new_csp)
180
202
  if self.dynamic_csp
181
- raise IllegalPolicyModificationError, "You are attempting to modify CSP settings directly. Use dynamic_csp= isntead."
203
+ raise IllegalPolicyModificationError, "You are attempting to modify CSP settings directly. Use dynamic_csp= instead."
182
204
  end
183
205
 
184
206
  @csp = new_csp
185
207
  end
186
208
 
209
+ def cookies=(cookies)
210
+ @cookies = cookies
211
+ end
212
+
187
213
  def cached_headers=(headers)
188
214
  @cached_headers = headers
189
215
  end
190
216
 
217
+ def hpkp=(hpkp)
218
+ @hpkp = hpkp
219
+ end
220
+
221
+ def hpkp_report_host=(hpkp_report_host)
222
+ @hpkp_report_host = hpkp_report_host
223
+ end
224
+
191
225
  private
192
226
 
227
+ def cache_hpkp_report_host
228
+ has_report_uri = @hpkp && @hpkp != OPT_OUT && @hpkp[:report_uri]
229
+ self.hpkp_report_host = if has_report_uri
230
+ parsed_report_uri = URI.parse(@hpkp[:report_uri])
231
+ parsed_report_uri.host
232
+ end
233
+ end
234
+
193
235
  # Public: Precompute the header names and values for this configuraiton.
194
236
  # Ensures that headers generated at configure time, not on demand.
195
237
  #
@@ -0,0 +1,10 @@
1
+ require 'base64'
2
+
3
+ module SecureHeaders
4
+ module HashHelper
5
+ def hash_source(inline_script, digest = :SHA256)
6
+ base64_hashed_content = Base64.encode64(Digest.const_get(digest).digest(inline_script)).chomp
7
+ "'#{digest.to_s.downcase}-#{base64_hashed_content}'"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,126 @@
1
+ require 'cgi'
2
+ require 'secure_headers/utils/cookies_config'
3
+
4
+ module SecureHeaders
5
+ class CookiesConfigError < StandardError; end
6
+ class Cookie
7
+
8
+ class << self
9
+ def validate_config!(config)
10
+ CookiesConfig.new(config).validate!
11
+ end
12
+ end
13
+
14
+ attr_reader :raw_cookie, :config
15
+
16
+ def initialize(cookie, config)
17
+ @raw_cookie = cookie
18
+ @config = config
19
+ @attributes = {
20
+ httponly: nil,
21
+ samesite: nil,
22
+ secure: nil,
23
+ }
24
+
25
+ parse(cookie)
26
+ end
27
+
28
+ def to_s
29
+ @raw_cookie.dup.tap do |c|
30
+ c << "; secure" if secure?
31
+ c << "; HttpOnly" if httponly?
32
+ c << "; #{samesite_cookie}" if samesite?
33
+ end
34
+ end
35
+
36
+ def secure?
37
+ flag_cookie?(:secure) && !already_flagged?(:secure)
38
+ end
39
+
40
+ def httponly?
41
+ flag_cookie?(:httponly) && !already_flagged?(:httponly)
42
+ end
43
+
44
+ def samesite?
45
+ flag_samesite? && !already_flagged?(:samesite)
46
+ end
47
+
48
+ private
49
+
50
+ def parsed_cookie
51
+ @parsed_cookie ||= CGI::Cookie.parse(raw_cookie)
52
+ end
53
+
54
+ def already_flagged?(attribute)
55
+ @attributes[attribute]
56
+ end
57
+
58
+ def flag_cookie?(attribute)
59
+ case config[attribute]
60
+ when TrueClass
61
+ true
62
+ when Hash
63
+ conditionally_flag?(config[attribute])
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ def conditionally_flag?(configuration)
70
+ if(Array(configuration[:only]).any? && (Array(configuration[:only]) & parsed_cookie.keys).any?)
71
+ true
72
+ elsif(Array(configuration[:except]).any? && (Array(configuration[:except]) & parsed_cookie.keys).none?)
73
+ true
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ def samesite_cookie
80
+ if flag_samesite_lax?
81
+ "SameSite=Lax"
82
+ elsif flag_samesite_strict?
83
+ "SameSite=Strict"
84
+ end
85
+ end
86
+
87
+ def flag_samesite?
88
+ flag_samesite_lax? || flag_samesite_strict?
89
+ end
90
+
91
+ def flag_samesite_lax?
92
+ flag_samesite_enforcement?(:lax)
93
+ end
94
+
95
+ def flag_samesite_strict?
96
+ flag_samesite_enforcement?(:strict)
97
+ end
98
+
99
+ def flag_samesite_enforcement?(mode)
100
+ return unless config[:samesite]
101
+
102
+ case config[:samesite][mode]
103
+ when Hash
104
+ conditionally_flag?(config[:samesite][mode])
105
+ when TrueClass
106
+ true
107
+ else
108
+ false
109
+ end
110
+ end
111
+
112
+ def parse(cookie)
113
+ return unless cookie
114
+
115
+ cookie.split(/[;,]\s?/).each do |pairs|
116
+ name, values = pairs.split('=',2)
117
+ name = CGI.unescape(name)
118
+
119
+ attribute = name.downcase.to_sym
120
+ if @attributes.has_key?(attribute)
121
+ @attributes[attribute] = values || true
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -91,6 +91,7 @@ module SecureHeaders
91
91
  UPGRADE_INSECURE_REQUESTS
92
92
  ].freeze
93
93
 
94
+ EDGE_DIRECTIVES = DIRECTIVES_1_0
94
95
  SAFARI_DIRECTIVES = DIRECTIVES_1_0
95
96
 
96
97
  FIREFOX_UNSUPPORTED_DIRECTIVES = [
@@ -118,6 +119,7 @@ module SecureHeaders
118
119
  "Opera" => CHROME_DIRECTIVES,
119
120
  "Firefox" => FIREFOX_DIRECTIVES,
120
121
  "Safari" => SAFARI_DIRECTIVES,
122
+ "Edge" => EDGE_DIRECTIVES,
121
123
  "Other" => CHROME_DIRECTIVES
122
124
  }.freeze
123
125
 
@@ -0,0 +1,33 @@
1
+ module SecureHeaders
2
+ class ReferrerPolicyConfigError < StandardError; end
3
+ class ReferrerPolicy
4
+ HEADER_NAME = "Referrer-Policy"
5
+ DEFAULT_VALUE = "origin-when-cross-origin"
6
+ VALID_POLICIES = %w(
7
+ no-referrer
8
+ no-referrer-when-downgrade
9
+ origin
10
+ origin-when-cross-origin
11
+ unsafe-url
12
+ )
13
+ CONFIG_KEY = :referrer_policy
14
+
15
+ class << self
16
+ # Public: generate an Referrer Policy header.
17
+ #
18
+ # Returns a default header if no configuration is provided, or a
19
+ # header name and value based on the config.
20
+ def make_header(config = nil)
21
+ [HEADER_NAME, config || DEFAULT_VALUE]
22
+ end
23
+
24
+ def validate_config!(config)
25
+ return if config.nil? || config == OPT_OUT
26
+ raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
27
+ unless VALID_POLICIES.include?(config.downcase)
28
+ raise ReferrerPolicyConfigError.new("Value can only be one of #{VALID_POLICIES.join(', ')}")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end