secure_headers 3.2.0 → 3.4.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: a02f16867ec55f8c168ace664cc63d760785256f
4
- data.tar.gz: db54c113af8919984c8f382b957834f38210cf3e
3
+ metadata.gz: 88233251c7a26e1269f77957d56823619e3d9d11
4
+ data.tar.gz: 8e9c0e69fcca0298ee52223d587134f5a8df062f
5
5
  SHA512:
6
- metadata.gz: 6d9ddcb98a8c646d4e7a8cdb79bf3d8638cae4cb230ff268660e8eab7000fba99bb65e126ce55b3ff5f2a0346013fcf8b769dab4db697c82ab4461d444d0e2bd
7
- data.tar.gz: b63c79ac3e9b37a4cff698f979f1ca6fd7e92567e35b1f9026be2e68a205e5badff302d512938a4d36517e8ec5b752d4833082e1b733ff73916fbf0b326641aa
6
+ metadata.gz: f421d0d58eb9b5a7b22fd13932644c2c3868e0294886cbf5250127e8807fa20dec95717219e0a82ce59f49f376b32e17b97b77c7e011d2e2d1633162c1297bbc
7
+ data.tar.gz: ae63cbdd588bf6c31e531bfec5480e022a5df96c8ee2cc4c824d1e869f64df2a44073d795cc6d5e7ffeca6d6369f0d2e33f202ce8ceda04d274849e95789ff43
@@ -0,0 +1,41 @@
1
+ # Feature Requests
2
+
3
+ ## Adding a new header
4
+
5
+ Generally, adding a new header is always OK.
6
+
7
+ * Is the header supported by any user agent? If so, which?
8
+ * What does it do?
9
+ * What are the valid values for the header?
10
+ * Where does the specification live?
11
+
12
+ ## Adding a new CSP directive
13
+
14
+ * Is the directive supported by any user agent? If so, which?
15
+ * What does it do?
16
+ * What are the valid values for the directive?
17
+
18
+ ---
19
+
20
+ # Bugs
21
+
22
+ Console errors and deprecation warnings are considered bugs that should be addressed with more precise UA sniffing. Bugs caused by incorrect or invalid UA sniffing are also bugs.
23
+
24
+ ### Expected outcome
25
+
26
+ Describe what you expected to happen
27
+
28
+ 1. I configure CSP to do X
29
+ 1. When I inspect the response headers, the CSP should have included X
30
+
31
+ ### Actual outcome
32
+
33
+ 1. The generated policy did not include X
34
+
35
+ ### Config
36
+
37
+ Please provide the configuration (`SecureHeaders::Configuration.default`) you are using including any overrides (`SecureHeaders::Configuration.override`).
38
+
39
+ ### Generated headers
40
+
41
+ Provide a sample response containing the headers
@@ -0,0 +1,20 @@
1
+ ## All PRs:
2
+
3
+ * [ ] Has tests
4
+ * [ ] Documentation updated
5
+
6
+ ## Adding a new header
7
+
8
+ Generally, adding a new header is always OK.
9
+
10
+ * Is the header supported by any user agent? If so, which?
11
+ * What does it do?
12
+ * What are the valid values for the header?
13
+ * Where does the specification live?
14
+
15
+ ## Adding a new CSP directive
16
+
17
+ * Is the directive supported by any user agent? If so, which?
18
+ * What does it do?
19
+ * What are the valid values for the directive?
20
+
data/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## 3.4.0 the frame-src/child-src transition for Firefox.
2
+
3
+ Handle the `child-src`/`frame-src` transition semi-intelligently across versions. I think the code best descibes the behavior here:
4
+
5
+ ```ruby
6
+ if supported_directives.include?(:child_src)
7
+ @config[:child_src] = @config[:child_src] || @config[:frame_src]
8
+ else
9
+ @config[:frame_src] = @config[:frame_src] || @config[:child_src]
10
+ end
11
+ ```
12
+
13
+ Also, @koenpunt noticed that we were [loading view helpers](https://github.com/twitter/secureheaders/pull/272) in a way that Rails 5 did not like.
14
+
15
+ ## 3.3.2 minor fix to silence warnings when using rake
16
+
17
+ [@dankohn](https://github.com/twitter/secureheaders/issues/257) was seeing "already initialized" errors in his output. This change conditionally defines the constants.
18
+
19
+ ## 3.3.1 bugfix for boolean CSP directives
20
+
21
+ [@stefansundin](https://github.com/twitter/secureheaders/pull/253) noticed that supplying `false` to "boolean" CSP directives (e.g. `upgrade-insecure-requests` and `block-all-mixed-content`) would still include the value.
22
+
23
+ ## 3.3.0 referrer-policy support
24
+
25
+ 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).
26
+
27
+ Additionally, two minor enhancements were added this version:
28
+ 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.
29
+ 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.
30
+
1
31
  ## 3.2.0 Cookie settings and CSP hash sources
2
32
 
3
33
  ### Cookies
@@ -114,6 +144,7 @@ console.log(1)
114
144
  Content-Security-Policy: ...
115
145
  script-src 'sha256-yktKiAsZWmc8WpOyhnmhQoDf9G2dAZvuBBC+V0LGQhg=' ... ;
116
146
  style-src 'sha256-SLp6LO3rrKDJwsG9uJUxZapb4Wp2Zhj6Bu3l+d9rnAY=' 'sha256-HSGHqlRoKmHAGTAJ2Rq0piXX4CnEbOl1ArNd6ejp2TE=' ...;
147
+ ```
117
148
 
118
149
  ## 3.1.2 Bug fix for regression
119
150
 
data/Gemfile CHANGED
@@ -5,7 +5,8 @@ gemspec
5
5
  group :test do
6
6
  gem "tins", "~> 1.6.0" # 1.7 requires ruby 2.0
7
7
  gem "pry-nav"
8
- gem "rack"
8
+ gem "json", "~> 1"
9
+ gem "rack", "~> 1"
9
10
  gem "rspec"
10
11
  gem "coveralls"
11
12
  end
data/README.md CHANGED
@@ -8,11 +8,12 @@
8
8
  The gem will automatically apply several headers that are related to security. This includes:
9
9
  - Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](http://www.w3.org/TR/CSP2/)
10
10
  - HTTP Strict Transport Security (HSTS) - Ensures the browser never visits the http version of a website. Protects from SSLStrip/Firesheep attacks. [HSTS Specification](https://tools.ietf.org/html/rfc6797)
11
- - X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options draft](https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02)
11
+ - X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options Specification](https://tools.ietf.org/html/rfc7034)
12
12
  - X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](https://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
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
19
  It can also mark all http cookies with the Secure, HttpOnly and SameSite attributes (when configured to do so).
@@ -44,27 +45,27 @@ SecureHeaders::Configuration.default do |config|
44
45
  config.x_xss_protection = "1; mode=block"
45
46
  config.x_download_options = "noopen"
46
47
  config.x_permitted_cross_domain_policies = "none"
48
+ config.referrer_policy = "origin-when-cross-origin"
47
49
  config.csp = {
48
50
  # "meta" values. these will shaped the header, but the values are not included in the header.
49
- report_only: true, # default: false
51
+ report_only: true, # default: false
50
52
  preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
51
53
 
52
54
  # directive values: these values will directly translate into source directives
53
55
  default_src: %w(https: 'self'),
54
- frame_src: %w('self' *.twimg.com itunes.apple.com),
55
- connect_src: %w(wws:),
56
+ base_uri: %w('self'),
57
+ block_all_mixed_content: true, # see http://www.w3.org/TR/mixed-content/
58
+ child_src: %w('self'), # if child-src isn't supported, the value for frame-src will be set.
59
+ connect_src: %w(wss:),
56
60
  font_src: %w('self' data:),
61
+ form_action: %w('self' github.com),
62
+ frame_ancestors: %w('none'),
57
63
  img_src: %w(mycdn.com data:),
58
64
  media_src: %w(utoob.com),
59
65
  object_src: %w('self'),
66
+ plugin_types: %w(application/x-shockwave-flash),
60
67
  script_src: %w('self'),
61
68
  style_src: %w('unsafe-inline'),
62
- base_uri: %w('self'),
63
- child_src: %w('self'),
64
- form_action: %w('self' github.com),
65
- frame_ancestors: %w('none'),
66
- plugin_types: %w(application/x-shockwave-flash),
67
- block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
68
69
  upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
69
70
  report_uri: %w(https://report-uri.io/example-csp)
70
71
  }
@@ -83,7 +84,7 @@ end
83
84
 
84
85
  ### rails 2
85
86
 
86
- For rails 3+ applications, `secure_headers` has a `railtie` that should automatically include the middleware. For rails 2 applications, an explicit statement is required to use the middleware component.
87
+ For rails 3+ applications, `secure_headers` has a `railtie` that should automatically include the middleware. For rails 2 or non-rails applications, an explicit statement is required to use the middleware component.
87
88
 
88
89
  ```ruby
89
90
  use SecureHeaders::Middleware
@@ -135,7 +136,7 @@ class MyController < ApplicationController
135
136
  end
136
137
  ```
137
138
 
138
- By default, a noop configuration is provided. No headers will be set when this default override is used.
139
+ By default, a no-op configuration is provided. No headers will be set when this default override is used.
139
140
 
140
141
  ```ruby
141
142
  class MyController < ApplicationController
@@ -161,12 +162,12 @@ You can override the settings for a given action by producing a temporary overri
161
162
  class MyController < ApplicationController
162
163
  def index
163
164
  # Append value to the source list, override 'none' values
164
- # Produces: default-src 'self'; script-src 'self' s3.amazaonaws.com; object-src 'self' youtube.com
165
- append_content_security_policy_directives(script_src: %w(s3.amazaonaws.com), object_src: %w('self' youtube.com))
165
+ # Produces: default-src 'self'; script-src 'self' s3.amazonaws.com; object-src 'self' www.youtube.com
166
+ append_content_security_policy_directives(script_src: %w(s3.amazonaws.com), object_src: %w('self' www.youtube.com))
166
167
 
167
168
  # Overrides the previously set source list, override 'none' values
168
- # Produces: default-src 'self'; script-src s3.amazaonaws.com; object-src 'self'
169
- override_content_security_policy_directives(script_src: %w(s3.amazaonaws.com), object_src: %w('self'))
169
+ # Produces: default-src 'self'; script-src s3.amazonaws.com; object-src 'self'
170
+ override_content_security_policy_directives(script_src: %w(s3.amazonaws.com), object_src: %w('self'))
170
171
 
171
172
  # Global settings default to "sameorigin"
172
173
  override_x_frame_options("DENY")
@@ -205,7 +206,7 @@ You can use a view helper to automatically add nonces to script tags:
205
206
 
206
207
  ```erb
207
208
  <%= nonced_javascript_tag do %>
208
- console.log("hai");
209
+ console.log("nonced!");
209
210
  <% end %>
210
211
 
211
212
  <%= nonced_style_tag do %>
@@ -322,17 +323,15 @@ Be aware that pinning error reporting is governed by the same rules as everythin
322
323
 
323
324
  ```ruby
324
325
  config.hpkp = {
325
- max_age: 60.days.to_i, # max_age is a required parameter
326
+ max_age: 60.days.to_i, # max_age is a required parameter
326
327
  include_subdomains: true, # whether or not to apply pins to subdomains
327
328
  # Per the spec, SHA256 hashes are the only currently supported format.
328
329
  pins: [
329
330
  {sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
330
331
  {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
331
332
  ],
332
- report_only: true, # defaults to false (report-only mode)
333
- report_uri: 'https://report-uri.io/example-hpkp',
334
- app_name: 'example',
335
- tag_report_uri: true
333
+ report_only: true, # defaults to false (report-only mode)
334
+ report_uri: 'https://report-uri.io/example-hpkp'
336
335
  }
337
336
  ```
338
337
 
@@ -61,6 +61,7 @@ module SecureHeaders
61
61
  config.validate_config!
62
62
  @configurations ||= {}
63
63
  config.send(:cache_headers!)
64
+ config.send(:cache_hpkp_report_host)
64
65
  config.freeze
65
66
  @configurations[name] = config
66
67
  end
@@ -102,11 +103,13 @@ module SecureHeaders
102
103
  end
103
104
  end
104
105
 
106
+ attr_accessor :dynamic_csp
107
+
105
108
  attr_writer :hsts, :x_frame_options, :x_content_type_options,
106
109
  :x_xss_protection, :x_download_options, :x_permitted_cross_domain_policies,
107
- :hpkp, :dynamic_csp, :cookies
110
+ :referrer_policy
108
111
 
109
- attr_reader :cached_headers, :csp, :dynamic_csp, :cookies
112
+ attr_reader :cached_headers, :csp, :cookies, :hpkp, :hpkp_report_host
110
113
 
111
114
  HASH_CONFIG_FILE = ENV["secure_headers_generated_hashes_file"] || "config/secure_headers_generated_hashes.yml"
112
115
  if File.exists?(HASH_CONFIG_FILE)
@@ -117,6 +120,7 @@ module SecureHeaders
117
120
 
118
121
  def initialize(&block)
119
122
  self.hpkp = OPT_OUT
123
+ self.referrer_policy = OPT_OUT
120
124
  self.csp = self.class.send(:deep_copy, CSP::DEFAULT_CONFIG)
121
125
  instance_eval &block if block_given?
122
126
  end
@@ -136,7 +140,9 @@ module SecureHeaders
136
140
  copy.x_xss_protection = @x_xss_protection
137
141
  copy.x_download_options = @x_download_options
138
142
  copy.x_permitted_cross_domain_policies = @x_permitted_cross_domain_policies
143
+ copy.referrer_policy = @referrer_policy
139
144
  copy.hpkp = @hpkp
145
+ copy.hpkp_report_host = @hpkp_report_host
140
146
  copy
141
147
  end
142
148
 
@@ -175,6 +181,7 @@ module SecureHeaders
175
181
  def validate_config!
176
182
  StrictTransportSecurity.validate_config!(@hsts)
177
183
  ContentSecurityPolicy.validate_config!(@csp)
184
+ ReferrerPolicy.validate_config!(@referrer_policy)
178
185
  XFrameOptions.validate_config!(@x_frame_options)
179
186
  XContentTypeOptions.validate_config!(@x_content_type_options)
180
187
  XXssProtection.validate_config!(@x_xss_protection)
@@ -196,15 +203,35 @@ module SecureHeaders
196
203
  raise IllegalPolicyModificationError, "You are attempting to modify CSP settings directly. Use dynamic_csp= instead."
197
204
  end
198
205
 
199
- @csp = new_csp
206
+ @csp = self.class.send(:deep_copy_if_hash, new_csp)
207
+ end
208
+
209
+ def cookies=(cookies)
210
+ @cookies = cookies
200
211
  end
201
212
 
202
213
  def cached_headers=(headers)
203
214
  @cached_headers = headers
204
215
  end
205
216
 
217
+ def hpkp=(hpkp)
218
+ @hpkp = self.class.send(:deep_copy_if_hash, hpkp)
219
+ end
220
+
221
+ def hpkp_report_host=(hpkp_report_host)
222
+ @hpkp_report_host = hpkp_report_host
223
+ end
224
+
206
225
  private
207
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
+
208
235
  # Public: Precompute the header names and values for this configuraiton.
209
236
  # Ensures that headers generated at configure time, not on demand.
210
237
  #
@@ -1,18 +1,22 @@
1
1
  require_relative 'policy_management'
2
+ require 'useragent'
2
3
 
3
4
  module SecureHeaders
4
5
  class ContentSecurityPolicyConfigError < StandardError; end
5
6
  class ContentSecurityPolicy
6
7
  include PolicyManagement
7
8
 
9
+ # constants to be used for version-specific UA sniffing
10
+ VERSION_46 = ::UserAgent::Version.new("46")
11
+
8
12
  def initialize(config = nil, user_agent = OTHER)
9
- config = Configuration.deep_copy(DEFAULT_CONFIG) unless config
10
- @config = config
13
+ @config = Configuration.send(:deep_copy, config || DEFAULT_CONFIG)
11
14
  @parsed_ua = if user_agent.is_a?(UserAgent::Browsers::Base)
12
15
  user_agent
13
16
  else
14
17
  UserAgent.parse(user_agent)
15
18
  end
19
+ normalize_child_frame_src
16
20
  @report_only = @config[:report_only]
17
21
  @preserve_schemes = @config[:preserve_schemes]
18
22
  @script_nonce = @config[:script_nonce]
@@ -42,6 +46,22 @@ module SecureHeaders
42
46
 
43
47
  private
44
48
 
49
+ # frame-src is deprecated, child-src is being implemented. They are
50
+ # very similar and in most cases, the same value can be used for both.
51
+ def normalize_child_frame_src
52
+ if @config[:frame_src] && @config[:child_src] && @config[:frame_src] != @config[:child_src]
53
+ 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.")
54
+ elsif @config[:frame_src]
55
+ Kernel.warn("#{Kernel.caller.first}: [DEPRECATION] :frame_src is deprecated, use :child_src instead. Provided: #{@config[:frame_src]}.")
56
+ end
57
+
58
+ if supported_directives.include?(:child_src)
59
+ @config[:child_src] = @config[:child_src] || @config[:frame_src]
60
+ else
61
+ @config[:frame_src] = @config[:frame_src] || @config[:child_src]
62
+ end
63
+ end
64
+
45
65
  # Private: converts the config object into a string representing a policy.
46
66
  # Places default-src at the first directive and report-uri as the last. All
47
67
  # others are presented in alphabetical order.
@@ -53,7 +73,7 @@ module SecureHeaders
53
73
  directives.map do |directive_name|
54
74
  case DIRECTIVE_VALUE_TYPES[directive_name]
55
75
  when :boolean
56
- symbol_to_hyphen_case(directive_name)
76
+ symbol_to_hyphen_case(directive_name) if @config[directive_name]
57
77
  when :string
58
78
  [symbol_to_hyphen_case(directive_name), @config[directive_name]].join(" ")
59
79
  else
@@ -162,9 +182,19 @@ module SecureHeaders
162
182
 
163
183
  # Private: determine which directives are supported for the given user agent.
164
184
  #
185
+ # Add UA-sniffing special casing here.
186
+ #
165
187
  # Returns an array of symbols representing the directives.
166
188
  def supported_directives
167
- @supported_directives ||= VARIATIONS[@parsed_ua.browser] || VARIATIONS[OTHER]
189
+ @supported_directives ||= if VARIATIONS[@parsed_ua.browser]
190
+ if @parsed_ua.browser == "Firefox" && @parsed_ua.version >= VERSION_46
191
+ VARIATIONS["FirefoxTransitional"]
192
+ else
193
+ VARIATIONS[@parsed_ua.browser]
194
+ end
195
+ else
196
+ VARIATIONS[OTHER]
197
+ end
168
198
  end
169
199
 
170
200
  def nonces_supported?
@@ -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 = [
@@ -99,10 +100,23 @@ module SecureHeaders
99
100
  PLUGIN_TYPES
100
101
  ].freeze
101
102
 
103
+ FIREFOX_46_DEPRECATED_DIRECTIVES = [
104
+ FRAME_SRC
105
+ ].freeze
106
+
107
+ FIREFOX_46_UNSUPPORTED_DIRECTIVES = [
108
+ BLOCK_ALL_MIXED_CONTENT,
109
+ PLUGIN_TYPES
110
+ ].freeze
111
+
102
112
  FIREFOX_DIRECTIVES = (
103
113
  DIRECTIVES_2_0 + DIRECTIVES_DRAFT - FIREFOX_UNSUPPORTED_DIRECTIVES
104
114
  ).freeze
105
115
 
116
+ FIREFOX_46_DIRECTIVES = (
117
+ DIRECTIVES_2_0 + DIRECTIVES_DRAFT - FIREFOX_46_UNSUPPORTED_DIRECTIVES - FIREFOX_46_DEPRECATED_DIRECTIVES
118
+ ).freeze
119
+
106
120
  CHROME_DIRECTIVES = (
107
121
  DIRECTIVES_2_0 + DIRECTIVES_DRAFT
108
122
  ).freeze
@@ -117,7 +131,9 @@ module SecureHeaders
117
131
  "Chrome" => CHROME_DIRECTIVES,
118
132
  "Opera" => CHROME_DIRECTIVES,
119
133
  "Firefox" => FIREFOX_DIRECTIVES,
134
+ "FirefoxTransitional" => FIREFOX_46_DIRECTIVES,
120
135
  "Safari" => SAFARI_DIRECTIVES,
136
+ "Edge" => EDGE_DIRECTIVES,
121
137
  "Other" => CHROME_DIRECTIVES
122
138
  }.freeze
123
139
 
@@ -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
@@ -1,5 +1,7 @@
1
1
  module SecureHeaders
2
2
  class Middleware
3
+ HPKP_SAME_HOST_WARNING = "[WARNING] HPKP report host should not be the same as the request host. See https://github.com/twitter/secureheaders/issues/166"
4
+
3
5
  def initialize(app)
4
6
  @app = app
5
7
  end
@@ -10,6 +12,10 @@ module SecureHeaders
10
12
  status, headers, response = @app.call(env)
11
13
 
12
14
  config = SecureHeaders.config_for(req)
15
+ if config.hpkp_report_host == req.host
16
+ Kernel.warn(HPKP_SAME_HOST_WARNING)
17
+ end
18
+
13
19
  flag_cookies!(headers, override_secure(env, config.cookies)) if config.cookies
14
20
  headers.merge!(SecureHeaders.header_hash_for(req))
15
21
  [status, headers, response]
@@ -7,7 +7,7 @@ if defined?(Rails::Railtie)
7
7
  'X-Permitted-Cross-Domain-Policies', 'X-Download-Options',
8
8
  'X-Content-Type-Options', 'Strict-Transport-Security',
9
9
  'Content-Security-Policy', 'Content-Security-Policy-Report-Only',
10
- 'Public-Key-Pins', 'Public-Key-Pins-Report-Only']
10
+ 'Public-Key-Pins', 'Public-Key-Pins-Report-Only', 'Referrer-Policy']
11
11
 
12
12
  initializer "secure_headers.middleware" do
13
13
  Rails.application.config.middleware.insert_before 0, SecureHeaders::Middleware
@@ -108,8 +108,6 @@ module SecureHeaders
108
108
  end
109
109
  end
110
110
 
111
- module ActionView #:nodoc:
112
- class Base #:nodoc:
113
- include SecureHeaders::ViewHelpers
114
- end
115
- end
111
+ ActiveSupport.on_load :action_view do
112
+ include SecureHeaders::ViewHelpers
113
+ end if defined?(ActiveSupport)
@@ -9,6 +9,7 @@ require "secure_headers/headers/x_xss_protection"
9
9
  require "secure_headers/headers/x_content_type_options"
10
10
  require "secure_headers/headers/x_download_options"
11
11
  require "secure_headers/headers/x_permitted_cross_domain_policies"
12
+ require "secure_headers/headers/referrer_policy"
12
13
  require "secure_headers/middleware"
13
14
  require "secure_headers/railtie"
14
15
  require "secure_headers/view_helper"
@@ -27,6 +28,7 @@ module SecureHeaders
27
28
  ContentSecurityPolicy,
28
29
  StrictTransportSecurity,
29
30
  PublicKeyPins,
31
+ ReferrerPolicy,
30
32
  XContentTypeOptions,
31
33
  XDownloadOptions,
32
34
  XFrameOptions,
data/lib/tasks/tasks.rake CHANGED
@@ -1,7 +1,7 @@
1
- INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx
2
- INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx
3
- INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
4
- INLINE_HASH_STYLE_HELPER_REGEX = /<%=\s?hashed_style_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
1
+ INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx unless defined? INLINE_SCRIPT_REGEX
2
+ INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx unless defined? INLINE_STYLE_REGEX
3
+ INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_SCRIPT_HELPER_REGEX
4
+ INLINE_HASH_STYLE_HELPER_REGEX = /<%=\s?hashed_style_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_STYLE_HELPER_REGEX
5
5
 
6
6
  namespace :secure_headers do
7
7
  include SecureHeaders::HashHelper
@@ -54,6 +54,7 @@ namespace :secure_headers do
54
54
  hashes
55
55
  end
56
56
 
57
+ desc "Generate #{SecureHeaders::Configuration::HASH_CONFIG_FILE}"
57
58
  task :generate_hashes do |t, args|
58
59
  script_hashes = {
59
60
  "scripts" => {},
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |gem|
3
3
  gem.name = "secure_headers"
4
- gem.version = "3.2.0"
4
+ gem.version = "3.4.0"
5
5
  gem.authors = ["Neil Matatall"]
6
6
  gem.email = ["neil.matatall@gmail.com"]
7
7
  gem.description = 'Security related headers all in one gem.'
@@ -24,7 +24,7 @@ module SecureHeaders
24
24
 
25
25
  describe "#value" do
26
26
  it "discards 'none' values if any other source expressions are present" do
27
- csp = ContentSecurityPolicy.new(default_opts.merge(frame_src: %w('self' 'none')))
27
+ csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
28
28
  expect(csp.value).not_to include("'none'")
29
29
  end
30
30
 
@@ -71,42 +71,83 @@ module SecureHeaders
71
71
  expect(csp.value).to eq("default-src example.org")
72
72
  end
73
73
 
74
+ it "does add a boolean directive if the value is true" do
75
+ csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: true)
76
+ expect(csp.value).to eq("default-src example.org; block-all-mixed-content; upgrade-insecure-requests")
77
+ end
78
+
79
+ it "does not add a boolean directive if the value is false" do
80
+ csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: false)
81
+ expect(csp.value).to eq("default-src example.org; block-all-mixed-content")
82
+ end
83
+
74
84
  it "deduplicates any source expressions" do
75
85
  csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
76
86
  expect(csp.value).to eq("default-src example.org")
77
87
  end
78
88
 
89
+ it "emits a warning when using frame-src" do
90
+ expect(Kernel).to receive(:warn).with(/:frame_src is deprecated, use :child_src instead./)
91
+ ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
92
+ end
93
+
94
+ it "emits a warning when child-src and frame-src are supplied but are not equal" do
95
+ expect(Kernel).to receive(:warn).with(/both :child_src and :frame_src supplied and do not match./)
96
+ ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
97
+ end
98
+
99
+ it "will still set inconsistent child/frame-src values to be less surprising" do
100
+ expect(Kernel).to receive(:warn).at_least(:once)
101
+ firefox = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox]).value
102
+ firefox_transitional = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox46]).value
103
+ expect(firefox).not_to eq(firefox_transitional)
104
+ expect(firefox).to match(/frame-src/)
105
+ expect(firefox).not_to match(/child-src/)
106
+ expect(firefox_transitional).to match(/child-src/)
107
+ expect(firefox_transitional).not_to match(/frame-src/)
108
+ end
109
+
79
110
  context "browser sniffing" do
80
111
  let (:complex_opts) do
81
- ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) do |directive, hash|
82
- hash[directive] = %w('self')
112
+ (ContentSecurityPolicy::ALL_DIRECTIVES - [:frame_src]).each_with_object({}) do |directive, hash|
113
+ hash[directive] = ["#{directive.to_s.gsub("_", "-")}.com"]
83
114
  end.merge({
84
115
  block_all_mixed_content: true,
85
116
  upgrade_insecure_requests: true,
86
117
  reflected_xss: "block",
87
- script_src: %w('self'),
118
+ script_src: %w(script-src.com),
88
119
  script_nonce: 123456
89
120
  })
90
121
  end
91
122
 
92
123
  it "does not filter any directives for Chrome" do
93
124
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
94
- expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
125
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
95
126
  end
96
127
 
97
128
  it "does not filter any directives for Opera" do
98
129
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
99
- expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
130
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
100
131
  end
101
132
 
102
133
  it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
103
134
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
104
- expect(policy.value).to eq("default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
135
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
136
+ end
137
+
138
+ it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
139
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
140
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
141
+ end
142
+
143
+ it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
144
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
145
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
105
146
  end
106
147
 
107
- it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
148
+ it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
108
149
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
109
- expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; report-uri 'self'")
150
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
110
151
  end
111
152
  end
112
153
  end
@@ -23,7 +23,8 @@ module SecureHeaders
23
23
  # directive values: these values will directly translate into source directives
24
24
  default_src: %w(https: 'self'),
25
25
  frame_src: %w('self' *.twimg.com itunes.apple.com),
26
- connect_src: %w(wws:),
26
+ child_src: %w('self' *.twimg.com itunes.apple.com),
27
+ connect_src: %w(wss:),
27
28
  font_src: %w('self' data:),
28
29
  img_src: %w(mycdn.com data:),
29
30
  media_src: %w(utoob.com),
@@ -31,7 +32,6 @@ module SecureHeaders
31
32
  script_src: %w('self'),
32
33
  style_src: %w('unsafe-inline'),
33
34
  base_uri: %w('self'),
34
- child_src: %w('self'),
35
35
  form_action: %w('self' github.com),
36
36
  frame_ancestors: %w('none'),
37
37
  plugin_types: %w(application/x-shockwave-flash),
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe ReferrerPolicy do
5
+ specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
6
+ specify { expect(ReferrerPolicy.make_header('no-referrer')).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
7
+
8
+ context "valid configuration values" do
9
+ it "accepts 'no-referrer'" do
10
+ expect do
11
+ ReferrerPolicy.validate_config!("no-referrer")
12
+ end.not_to raise_error
13
+ end
14
+
15
+ it "accepts 'no-referrer-when-downgrade'" do
16
+ expect do
17
+ ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
18
+ end.not_to raise_error
19
+ end
20
+
21
+ it "accepts 'origin'" do
22
+ expect do
23
+ ReferrerPolicy.validate_config!("origin")
24
+ end.not_to raise_error
25
+ end
26
+
27
+ it "accepts 'origin-when-cross-origin'" do
28
+ expect do
29
+ ReferrerPolicy.validate_config!("origin-when-cross-origin")
30
+ end.not_to raise_error
31
+ end
32
+
33
+ it "accepts 'unsafe-url'" do
34
+ expect do
35
+ ReferrerPolicy.validate_config!("unsafe-url")
36
+ end.not_to raise_error
37
+ end
38
+
39
+ it "accepts nil" do
40
+ expect do
41
+ ReferrerPolicy.validate_config!(nil)
42
+ end.not_to raise_error
43
+ end
44
+ end
45
+
46
+ context 'invlaid configuration values' do
47
+ it "doesn't accept invalid values" do
48
+ expect do
49
+ ReferrerPolicy.validate_config!("open")
50
+ end.to raise_error(ReferrerPolicyConfigError)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -13,6 +13,24 @@ module SecureHeaders
13
13
  Configuration.default
14
14
  end
15
15
 
16
+ it "warns if the hpkp report-uri host is the same as the current host" do
17
+ report_host = "report-uri.io"
18
+ Configuration.default do |config|
19
+ config.hpkp = {
20
+ max_age: 10000000,
21
+ pins: [
22
+ {sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
23
+ {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
24
+ ],
25
+ report_uri: "https://#{report_host}/example-hpkp"
26
+ }
27
+ end
28
+
29
+ expect(Kernel).to receive(:warn).with(Middleware::HPKP_SAME_HOST_WARNING)
30
+
31
+ middleware.call(Rack::MockRequest.env_for("https://#{report_host}", {}))
32
+ end
33
+
16
34
  it "sets the headers" do
17
35
  _, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
18
36
  expect_default_values(env)
@@ -20,6 +20,13 @@ module SecureHeaders
20
20
  end.to raise_error(Configuration::NotYetConfiguredError)
21
21
  end
22
22
 
23
+ it "raises and ArgumentError when referencing an override that has not been set" do
24
+ expect do
25
+ Configuration.default
26
+ SecureHeaders.use_secure_headers_override(request, :missing)
27
+ end.to raise_error(ArgumentError)
28
+ end
29
+
23
30
  describe "#header_hash_for" do
24
31
  it "allows you to opt out of individual headers via API" do
25
32
  Configuration.default
@@ -83,8 +90,7 @@ module SecureHeaders
83
90
  config = Configuration.default do |config|
84
91
  config.csp = {
85
92
  default_src: %w('self'),
86
- child_src: %w('self'), #unsupported by firefox
87
- frame_src: %w('self')
93
+ child_src: %w('self')
88
94
  }
89
95
  end
90
96
  firefox_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:firefox]))
@@ -95,6 +101,8 @@ module SecureHeaders
95
101
  SecureHeaders.override_content_security_policy_directives(firefox_request, script_src: %w('self'))
96
102
 
97
103
  hash = SecureHeaders.header_hash_for(firefox_request)
104
+
105
+ # child-src is translated to frame-src
98
106
  expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
99
107
  end
100
108
 
@@ -305,6 +313,14 @@ module SecureHeaders
305
313
  end.to raise_error(XPCDPConfigError)
306
314
  end
307
315
 
316
+ it "validates your referrer_policy config upon configuration" do
317
+ expect do
318
+ Configuration.default do |config|
319
+ config.referrer_policy = "lol"
320
+ end
321
+ end.to raise_error(ReferrerPolicyConfigError)
322
+ end
323
+
308
324
  it "validates your hpkp config upon configuration" do
309
325
  expect do
310
326
  Configuration.default do |config|
@@ -312,6 +328,14 @@ module SecureHeaders
312
328
  end
313
329
  end.to raise_error(PublicKeyPinsConfigError)
314
330
  end
331
+
332
+ it "validates your cookies config upon configuration" do
333
+ expect do
334
+ Configuration.default do |config|
335
+ config.cookies = { secure: "lol" }
336
+ end
337
+ end.to raise_error(CookiesConfigError)
338
+ end
315
339
  end
316
340
  end
317
341
  end
data/spec/spec_helper.rb CHANGED
@@ -11,7 +11,9 @@ require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
11
11
 
12
12
 
13
13
  USER_AGENTS = {
14
- firefox: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
14
+ edge: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
15
+ firefox: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1",
16
+ firefox46: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0",
15
17
  chrome: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
16
18
  ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
17
19
  opera: 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
@@ -30,6 +32,7 @@ def expect_default_values(hash)
30
32
  expect(hash[SecureHeaders::XXssProtection::HEADER_NAME]).to eq(SecureHeaders::XXssProtection::DEFAULT_VALUE)
31
33
  expect(hash[SecureHeaders::XContentTypeOptions::HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::DEFAULT_VALUE)
32
34
  expect(hash[SecureHeaders::XPermittedCrossDomainPolicies::HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::DEFAULT_VALUE)
35
+ expect(hash[SecureHeaders::ReferrerPolicy::HEADER_NAME]).to be_nil
33
36
  end
34
37
 
35
38
  module SecureHeaders
data/upgrading-to-3-0.md CHANGED
@@ -3,16 +3,19 @@
3
3
  Changes
4
4
  ==
5
5
 
6
- | What | < = 2.x | >= 3.0 |
7
- |----------------------------------|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
8
- | Global configuration | `SecureHeaders::Configuration.configure` block | `SecureHeaders::Configuration.default` block |
9
- | All headers besides HPKP and CSP | Accept hashes as config values | Must be strings (validated during configuration) |
10
- | CSP directive values | Accepted space delimited strings OR arrays of strings | Must be arrays of strings |
11
- | CSP Nonce values in views | `@content_security_policy_nonce` | `content_security_policy_script_nonce` or `content_security_policy_style_nonce`
12
- | `self`/`none` source expressions | could be `self` / `none` / `'self'` / `'none'` | Must be `'self'` or `'none'` |
13
- | `inline` / `eval` source expressions | could be `inline`, `eval`, `'unsafe-inline'`, or `'unsafe-eval'` | Must be `'unsafe-eval'` or `'unsafe-inline'` |
14
- | Per-action configuration | override [`def secure_header_options_for(header, options)`](https://github.com/twitter/secureheaders/commit/bb9ebc6c12a677aad29af8e0f08ffd1def56efec#diff-04c6e90faac2675aa89e2176d2eec7d8R111) | Use [named overrides](https://github.com/twitter/secureheaders#named-overrides) or [per-action helpers](https://github.com/twitter/secureheaders#per-action-configuration) |
15
- | CSP/HPKP use `report_only` config that defaults to false | `enforce: false` | `report_only: false` |
6
+ | What | < = 2.x | >= 3.0 |
7
+ | ---------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
8
+ | Global configuration | `SecureHeaders::Configuration.configure` block | `SecureHeaders::Configuration.default` block |
9
+ | All headers besides HPKP and CSP | Accept hashes as config values | Must be strings (validated during configuration) |
10
+ | CSP directive values | Accepted space delimited strings OR arrays of strings | Must be arrays of strings |
11
+ | CSP Nonce values in views | `@content_security_policy_nonce` | `content_security_policy_nonce(:script)` or `content_security_policy_nonce(:style)` |
12
+ | nonce is no longer a source expression | `config.csp = "'self' 'nonce'"` | Remove `'nonce'` from source expression and use [nonce helpers](https://github.com/twitter/secureheaders#nonce). |
13
+ | `self`/`none` source expressions | Could be `self` / `none` / `'self'` / `'none'` | Must be `'self'` or `'none'` |
14
+ | `inline` / `eval` source expressions | Could be `inline`, `eval`, `'unsafe-inline'`, or `'unsafe-eval'` | Must be `'unsafe-eval'` or `'unsafe-inline'` |
15
+ | Per-action configuration | Override [`def secure_header_options_for(header, options)`](https://github.com/twitter/secureheaders/commit/bb9ebc6c12a677aad29af8e0f08ffd1def56efec#diff-04c6e90faac2675aa89e2176d2eec7d8R111) | Use [named overrides](https://github.com/twitter/secureheaders#named-overrides) or [per-action helpers](https://github.com/twitter/secureheaders#per-action-configuration) |
16
+ | CSP/HPKP use `report_only` config that defaults to false | `enforce: false` | `report_only: false` |
17
+ | Schemes in source expressions | Schemes were not stripped | Schemes are stripped by default to discourage mixed content. Setting `preserve_schemes: true` will revert to previous behavior |
18
+ | Opting out of default configuration | `skip_before_filter :set_x_download_options_header` or `config.x_download_options = false` | Within default block: `config.x_download_options = SecureHeaders::OPT_OUT` |
16
19
 
17
20
  Migrating to 3.x from <= 2.x
18
21
  ==
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secure_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-18 00:00:00.000000000 Z
11
+ date: 2016-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -45,6 +45,8 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".github/ISSUE_TEMPLATE.md"
49
+ - ".github/PULL_REQUEST_TEMPLATE.md"
48
50
  - ".gitignore"
49
51
  - ".rspec"
50
52
  - ".ruby-gemset"
@@ -63,6 +65,7 @@ files:
63
65
  - lib/secure_headers/headers/cookie.rb
64
66
  - lib/secure_headers/headers/policy_management.rb
65
67
  - lib/secure_headers/headers/public_key_pins.rb
68
+ - lib/secure_headers/headers/referrer_policy.rb
66
69
  - lib/secure_headers/headers/strict_transport_security.rb
67
70
  - lib/secure_headers/headers/x_content_type_options.rb
68
71
  - lib/secure_headers/headers/x_download_options.rb
@@ -80,6 +83,7 @@ files:
80
83
  - spec/lib/secure_headers/headers/cookie_spec.rb
81
84
  - spec/lib/secure_headers/headers/policy_management_spec.rb
82
85
  - spec/lib/secure_headers/headers/public_key_pins_spec.rb
86
+ - spec/lib/secure_headers/headers/referrer_policy_spec.rb
83
87
  - spec/lib/secure_headers/headers/strict_transport_security_spec.rb
84
88
  - spec/lib/secure_headers/headers/x_content_type_options_spec.rb
85
89
  - spec/lib/secure_headers/headers/x_download_options_spec.rb
@@ -122,6 +126,7 @@ test_files:
122
126
  - spec/lib/secure_headers/headers/cookie_spec.rb
123
127
  - spec/lib/secure_headers/headers/policy_management_spec.rb
124
128
  - spec/lib/secure_headers/headers/public_key_pins_spec.rb
129
+ - spec/lib/secure_headers/headers/referrer_policy_spec.rb
125
130
  - spec/lib/secure_headers/headers/strict_transport_security_spec.rb
126
131
  - spec/lib/secure_headers/headers/x_content_type_options_spec.rb
127
132
  - spec/lib/secure_headers/headers/x_download_options_spec.rb