secure_headers 1.4.1 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -8
  3. data/Gemfile +2 -2
  4. data/Guardfile +8 -0
  5. data/README.md +102 -48
  6. data/Rakefile +0 -116
  7. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +1 -1
  8. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +2 -1
  9. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +1 -1
  10. data/fixtures/rails_3_2_12/config/script_hashes.yml +5 -0
  11. data/fixtures/rails_3_2_12/config.ru +3 -0
  12. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +50 -18
  13. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +1 -1
  14. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +1 -2
  15. data/lib/secure_headers/hash_helper.rb +7 -0
  16. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +22 -0
  17. data/lib/secure_headers/headers/content_security_policy.rb +141 -137
  18. data/lib/secure_headers/railtie.rb +0 -22
  19. data/lib/secure_headers/version.rb +1 -1
  20. data/lib/secure_headers/view_helper.rb +68 -0
  21. data/lib/secure_headers.rb +51 -17
  22. data/lib/tasks/tasks.rake +48 -0
  23. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +47 -0
  24. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +83 -208
  25. data/spec/lib/secure_headers_spec.rb +16 -62
  26. data/spec/spec_helper.rb +25 -1
  27. metadata +22 -24
  28. data/HISTORY.md +0 -162
  29. data/app/controllers/content_security_policy_controller.rb +0 -76
  30. data/config/curl-ca-bundle.crt +0 -5420
  31. data/config/routes.rb +0 -3
  32. data/spec/controllers/content_security_policy_controller_spec.rb +0 -90
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4d41b253fa38de884f864558563330b9e707e2dc
4
+ data.tar.gz: 69b19dbc4ab124481e6f47dad73cd7a6497aa844
5
+ SHA512:
6
+ metadata.gz: fa7e778e7cb305a3273d4b3aa94bf8597972910da752b218ebf0779330c4c025c2906cec3974a410193db600fc899465a2da8bc7a8ca5720eb30ba95335e7a5d
7
+ data.tar.gz: 21934fb364794d3fc08e7dde201e04cb1253686e362e46ec370b06021007cb897fc3182de9a1ac646fa2448f4ce7a9f45b33da31f4d2a8690b76c42527bb9001
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
+ *.DS_STORE
2
3
  *.rbc
3
4
  .bundle
4
5
  .config
@@ -15,12 +16,7 @@ rdoc
15
16
  spec/reports
16
17
  test/tmp
17
18
  test/version_tmp
18
- tmp
19
-
20
- /fixtures/rails_3_2_12/log/test.log
21
- /fixtures/rails_3_2_12_no_init/log/test.log
19
+ *tmp
22
20
  *.sqlite3
23
- test.sqlite3
24
- *test.sqlite3
25
- /fixtures/rails_3_2_12_no_init/db/test.sqlite3
26
- /fixtures/rails_3_2_12/db/test.sqlite3
21
+ fixtures/rails_3_2_12_no_init/log
22
+ fixtures/rails_3_2_12/log
data/Gemfile CHANGED
@@ -8,8 +8,8 @@ group :test do
8
8
  gem 'jdbc-sqlite3', :platform => :jruby
9
9
  gem 'rspec-rails', '>= 3.1'
10
10
  gem 'rspec', '>= 3.1'
11
+ gem 'guard-rspec', :platform => [:ruby_19, :ruby_20, :ruby_21]
11
12
  gem 'growl'
12
13
  gem 'rb-fsevent'
13
- gem 'debugger', :platform => :ruby_19
14
- gem 'ruby-debug', :platform => :ruby_18
14
+ gem 'coveralls', :platform => :ruby_19
15
15
  end
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ notification :growl
2
+
3
+ guard 'rspec', cmd: 'rspec' do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
6
+ watch(%r{^app/controllers/(.+)\.rb$}) { |m| "spec/controllers/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SecureHeaders [![Build Status](https://travis-ci.org/twitter/secureheaders.png?branch=master)](http://travis-ci.org/twitter/secureheaders) [![Code Climate](https://codeclimate.com/github/twitter/secureheaders.png)](https://codeclimate.com/github/twitter/secureheaders)
1
+ # SecureHeaders [![Build Status](https://travis-ci.org/twitter/secureheaders.png?branch=master)](http://travis-ci.org/twitter/secureheaders) [![Code Climate](https://codeclimate.com/github/twitter/secureheaders.png)](https://codeclimate.com/github/twitter/secureheaders) [![Coverage Status](https://coveralls.io/repos/twitter/secureheaders/badge.png)](https://coveralls.io/r/twitter/secureheaders)
2
2
 
3
3
  The gem will automatically apply several headers that are related to security. This includes:
4
4
  - Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 1.1 Specification](https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html)
@@ -54,8 +54,6 @@ The following methods are going to be called, unless they are provided in a `ski
54
54
  This gem makes a few assumptions about how you will use some features. For example:
55
55
 
56
56
  * It fills any blank directives with the value in `:default_src` Getting a default\-src report is pretty useless. This way, you will always know what type of violation occurred. You can disable this feature by supplying `:disable_fill_missing => true`. This is referred to as the "effective-directive" in the spec, but is not well supported as of Nov 5, 2013.
57
- * Firefox does not support cross\-origin CSP reports. If we are using Firefox, AND the value for `:report_uri` does not satisfy the same\-origin requirements, we will instead forward to an internal endpoint (`FF_CSP_ENDPOINT`). This is also the case if `:report_uri` only contains a path, which we assume will be cross host. This endpoint will in turn forward the request to the value in `:forward_endpoint` without restriction. More information can be found in the "Note on Firefox handling of CSP" section.
58
-
59
57
 
60
58
  ## Configuration
61
59
 
@@ -68,9 +66,9 @@ This gem makes a few assumptions about how you will use some features. For exam
68
66
  config.x_content_type_options = "nosniff"
69
67
  config.x_xss_protection = {:value => 1, :mode => 'block'}
70
68
  config.csp = {
71
- :default_src => "https://* self",
72
- :frame_src => "https://* http://*.twimg.com http://itunes.apple.com",
73
- :img_src => "https://*",
69
+ :default_src => "https: self",
70
+ :frame_src => "https: http:.twimg.com http://itunes.apple.com",
71
+ :img_src => "https:",
74
72
  :report_uri => '//example.com/uri-directive'
75
73
  }
76
74
  end
@@ -125,11 +123,6 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
125
123
  # Where reports are sent. Use protocol relative URLs if you are posting to the same domain (TLD+1). Use paths if you are posting to the application serving the header
126
124
  :report_uri => '//mysite.example.com',
127
125
 
128
- # Send reports that cannot be sent across host here. These requests are sent
129
- # the server, not the browser. If no value is supplied, it will default to
130
- # the value in report_uri. Use this if you cannot use relative protocols mentioned above due to host mismatches.
131
- :forward_endpoint => 'https://internal.mylogaggregator.example.com'
132
-
133
126
  # these directives all take 'none', 'self', or a globbed pattern
134
127
  :img_src => nil,
135
128
  :frame_src => nil,
@@ -144,25 +137,12 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
144
137
  # over http, relaxing the policy
145
138
  # e.g.
146
139
  # :csp => {
147
- # :img_src => 'https://*',
148
- # :http_additions => {:img_src => 'http//*'}
140
+ # :img_src => 'https:',
141
+ # :http_additions => {:img_src => 'http'}
149
142
  # }
150
- # would produce the directive: "img-src https://* http://*;"
143
+ # would produce the directive: "img-src https: http:;"
151
144
  # when over http, ignored for https requests
152
145
  :http_additions => {}
153
-
154
- # If you have enforce => true, you can use the `experiments` block to
155
- # also produce a report-only header. Values in this block override the
156
- # parent config for the report-only, and leave the enforcing header
157
- # unaltered. http_additions work the same way described above, but
158
- # are added to your report-only header as expected.
159
- :experimental => {
160
- :script_src => 'self',
161
- :img_src => 'https://mycdn.example.com',
162
- :http_additions {
163
- :img_src => 'http://mycdn.example.com'
164
- }
165
- }
166
146
  }
167
147
  ```
168
148
 
@@ -172,19 +152,19 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
172
152
  ```ruby
173
153
  # most basic example
174
154
  :csp => {
175
- :default_src => "https://* inline eval",
155
+ :default_src => "https: inline eval",
176
156
  :report_uri => '/uri-directive'
177
157
  }
178
158
 
179
- > "default-src 'unsafe-inline' 'unsafe-eval' https://*; report-uri /uri-directive;"
159
+ > "default-src 'unsafe-inline' 'unsafe-eval' https:; report-uri /uri-directive;"
180
160
 
181
161
  # turn off inline scripting/eval
182
162
  :csp => {
183
- :default_src => 'https://*',
163
+ :default_src => 'https:',
184
164
  :report_uri => '/uri-directive'
185
165
  }
186
166
 
187
- > "default-src https://*; report-uri /uri-directive;"
167
+ > "default-src https:; report-uri /uri-directive;"
188
168
 
189
169
  # Auction site wants to allow images from anywhere, plugin content from a list of trusted media providers (including a content distribution network), and scripts only from its server hosting sanitized JavaScript
190
170
  :csp => {
@@ -217,9 +197,13 @@ report-uri csp_reports?enforce=true&app_name=twitter
217
197
 
218
198
  ### CSP Level 2 features
219
199
 
200
+ *NOTE: Currently, only erb is supported. Mustache support isn't far off. Hash sources are valid for inline style blocks but are not yet supported by secure_headers.*
201
+
202
+ #### Nonce
203
+
220
204
  script/style-nonce can be used to whitelist inline content. To do this, add "nonce" to your script/style-src configuration, then set the nonce attributes on the various tags.
221
205
 
222
- *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.
206
+ 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.
223
207
 
224
208
  ```ruby
225
209
  :csp => {
@@ -243,29 +227,99 @@ script/style-nonce can be used to whitelist inline content. To do this, add "non
243
227
  console.log("won't execute, not whitelisted")
244
228
  </script>
245
229
  ```
230
+ You can use a view helper to automatically add nonces to script tags:
231
+ ```erb
232
+ <%= nonced_javascript_tag do %>
233
+ console.log("nonced!")
234
+ <% end %>
235
+ <%= nonced_javascript_tag("nonced without a block!") %>
236
+ ```
246
237
 
247
- ## Note on Firefox handling of CSP
238
+ becomes:
248
239
 
249
- * CSP reports will not POST cross\-origin. This sets up an internal endpoint in the application that will forward the request. Set the `forward_endpoint` value in the CSP section if you need to post cross origin for firefox. The internal endpoint that receives the initial request will forward the request to `forward_endpoint`
240
+ ```html
241
+ <script nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
242
+ console.log("nonced!")
243
+ </script>
244
+ ```
245
+
246
+ #### Hash
250
247
 
251
- ### Adding the Firefox report forwarding endpoint
248
+ setting hash source values will also set 'unsafe-inline' for browsers that don't support hash sources for backwards compatibility. 'unsafe-inline' is ignored if a hash is present in a directive in compliant browsers.
252
249
 
253
- **You need to add the following line to the TOP of confib/routes.rb**
254
- **This is an unauthenticated, unauthorized endpoint. Only do this if your report\-uri is not on the same origin as your application!!!**
250
+ Hash source support works by taking the hash value of the contents of an inline script block and adding the hash "fingerprint" to the CSP header.
255
251
 
256
- #### Rails 2
252
+ If you only have a few hashes, you can hardcode them for the entire app:
257
253
 
258
254
  ```ruby
259
- map.csp_endpoint
255
+ config.csp = {
256
+ :default_src => "https:",
257
+ :script_src => 'self'
258
+ :script_hashes => ['sha1-abc', 'sha1-qwe']
259
+ }
260
260
  ```
261
261
 
262
- #### Rails 3
262
+ The following will work as well, but may not be as clear:
263
+
264
+ ```ruby
265
+ config.csp = {
266
+ :default_src => "https:",
267
+ :script_src => "self 'sha1-qwe'"
268
+ }
269
+ ```
263
270
 
264
- If the csp reporting endpoint is clobbered by another route, add:
271
+ If you find you have many hashes or the content of the script tags change frequently, you can apply these hashes in a more intelligent way. This method expects config/script_hashes.yml to contain a map of templates => [hashes]. When the individual templates, layouts, or partials are rendered the hash values for the script tags in those templates will be automatically added to the header. *Currently, only erb layouts are supported.* This requires the use of middleware:
265
272
 
266
273
  ```ruby
267
- post SecureHeaders::ContentSecurityPolicy::FF_CSP_ENDPOINT => "content_security_policy#scribe"
274
+ # config.ru
275
+ require 'secure_headers/headers/content_security_policy/script_hash_middleware'
276
+ use ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware
268
277
  ```
278
+
279
+ ```ruby
280
+ config.csp = {
281
+ :default_src => "https:",
282
+ :script_src => 'self',
283
+ :script_hash_middleware => true
284
+ }
285
+ ```
286
+
287
+ Hashes are stored in a yaml file with a mapping of Filename => [list of hashes] in config/script_hashes.yml. You can automatically populate this file by running the following rake task:
288
+
289
+ ```$ bundle exec rake secure_headers:generate_hashes```
290
+
291
+ Which will generate something like:
292
+
293
+ ```yaml
294
+ # config/script_hashes.yml
295
+ app/views/layouts/application.html.erb:
296
+ - sha256-l8OLjZqYRnKilpdE0VosRMvhdYArjXT4NZaK2p7QVvs=
297
+ app/templates/articles/edit.html.erb:
298
+ - sha256-+7mij1/uCwtCQRWrof2NmOln5qX+5WdVwTLMpi8nuoA=
299
+ - sha256-Ny4TRIhhFpnYnSeKC274P6bfAz4TOkezLabavIAU4dA=
300
+ - sha256-I5e58Gqbu4WpO9dck18QxO7aYOHKrELIi70it4jIPi0=
301
+ - sha256-Po4LMynwnAJHxiTp3DQaQ3YDBj3paN/xrDoKl4OyxY4=
302
+ ```
303
+
304
+ In this example, if we visit /articles/edit/[id], the above hashes will automatically be added to the CSP header's
305
+ script-src value!
306
+
307
+ You can use plain "script" tags or you can use a built-in helper:
308
+
309
+ ```erb
310
+ <%= hashed_javascript_tag do %>
311
+ console.log("hashed automatically!")
312
+ <% end %>
313
+ ```
314
+
315
+ By using the helper, hash values will be computed dynamically in development/test environments. If a dynamically computed hash value does not match what is expected to be found in config/script_hashes.yml a warning message will be printed to the console. If you want to raise exceptions instead, use:
316
+
317
+ ```erb
318
+ <%= hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
319
+ console.log("will raise an exception if not in script_hashes.yml!")
320
+ <% end %>
321
+ ```
322
+
269
323
  ### Using with Sinatra
270
324
 
271
325
  Here's an example using SecureHeaders for Sinatra applications:
@@ -282,10 +336,10 @@ require 'secure_headers'
282
336
  config.x_content_type_options = "nosniff"
283
337
  config.x_xss_protection = {:value => 1, :mode => false}
284
338
  config.csp = {
285
- :default_src => "https://* inline eval",
339
+ :default_src => "https: inline eval",
286
340
  :report_uri => '//example.com/uri-directive',
287
- :img_src => "https://* data:",
288
- :frame_src => "https://* http://*.twimg.com http://itunes.apple.com"
341
+ :img_src => "https: data:",
342
+ :frame_src => "https: http:.twimg.com http://itunes.apple.com"
289
343
  }
290
344
  end
291
345
 
@@ -337,10 +391,10 @@ def before_load
337
391
  config.x_content_type_options = "nosniff"
338
392
  config.x_xss_protection = {:value => '1', :mode => false}
339
393
  config.csp = {
340
- :default_src => "https://* inline eval",
394
+ :default_src => "https: inline eval",
341
395
  :report_uri => '//example.com/uri-directive',
342
- :img_src => "https://* data:",
343
- :frame_src => "https://* http://*.twimg.com http://itunes.apple.com"
396
+ :img_src => "https: data:",
397
+ :frame_src => "https: http:.twimg.com http://itunes.apple.com"
344
398
  }
345
399
  end
346
400
  end
data/Rakefile CHANGED
@@ -49,119 +49,3 @@ RDoc::Task.new(:rdoc) do |rdoc|
49
49
  rdoc.options << '--line-numbers'
50
50
  rdoc.rdoc_files.include('lib/**/*.rb')
51
51
  end
52
-
53
- UPDATE_URI = 'https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1'
54
- CA_FILE = File.expand_path(File.join('..', 'config', 'curl-ca-bundle.crt'), __FILE__)
55
- task :fetch_ca_bundle do
56
- begin
57
- FileUtils.cp CA_FILE, CA_FILE + ".bak"
58
- uri = URI.parse(UPDATE_URI)
59
- http = Net::HTTP.new(uri.host, uri.port)
60
- http.use_ssl = true
61
- http.ca_file = CA_FILE
62
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
63
- request = Net::HTTP::Get.new(uri.request_uri)
64
-
65
- ca_file = StringIO.new(http.request(request).body)
66
- File.open(CA_FILE, 'w') do |f|
67
- f.puts mozilla_license
68
- end
69
-
70
- while line = ca_file.gets
71
- next if line =~ /^#/
72
- next if line =~ /^\s*$/
73
- line.chomp!
74
-
75
- if line =~ /CKA_LABEL/
76
- label,type,cert_name = line.split(' ',3)
77
- cert_name.sub!(/^"/, "")
78
- cert_name.sub!(/"$/, "")
79
- next
80
- end
81
- if line =~ /CKA_VALUE MULTILINE_OCTAL/
82
- puts "reading cert for #{cert_name}"
83
- data=''
84
- while line = ca_file.gets
85
- break if line =~ /^END/
86
- line.chomp!
87
- line.gsub(/\\([0-3][0-7][0-7])/) { data += $1.oct.chr }
88
- end
89
-
90
- open(CA_FILE, "a") do |fp|
91
- puts "Appending"
92
- fp.puts cert_name
93
- fp.puts "================"
94
- fp.puts "-----BEGIN CERTIFICATE-----"
95
- fp.puts [data].pack("m*")
96
- fp.puts "-----END CERTIFICATE-----"
97
- fp.puts
98
- end
99
- puts "Parsing: " + cert_name
100
- end
101
- end
102
-
103
- FileUtils.rm CA_FILE + ".bak"
104
- rescue => e
105
- puts "ERRROR #{e}"
106
- puts e.backtrace
107
- FileUtils.mv CA_FILE + '.bak', CA_FILE
108
- end
109
- end
110
-
111
-
112
- def mozilla_license
113
- <<-EOM
114
- ## generated using a modified version of http://curl.haxx.se/mail/lib-2004-07/att-0134/parse-certs.sh
115
- ##
116
- ## lib/ca-bundle.crt -- Bundle of CA Root Certificates
117
- ##
118
- ## Certificate data from Mozilla as of: Tue Mar 27 20:21:58 2012
119
- ##
120
- ## This is a bundle of X.509 certificates of public Certificate Authorities
121
- ## (CA). These were automatically extracted from Mozilla's root certificates
122
- ## file (certdata.txt). This file can be found in the mozilla source tree:
123
- ## http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1
124
- ##
125
- ## It contains the certificates in PEM format and therefore
126
- ## can be directly used with curl / libcurl / php_curl, or with
127
- ## an Apache+mod_ssl webserver for SSL client authentication.
128
- ## Just configure this file as the SSLCACertificateFile.
129
- ##
130
-
131
- # ***** BEGIN LICENSE BLOCK *****
132
- # Version: MPL 1.1/GPL 2.0/LGPL 2.1
133
- #
134
- # The contents of this file are subject to the Mozilla Public License Version
135
- # 1.1 (the "License"); you may not use this file except in compliance with
136
- # the License. You may obtain a copy of the License at
137
- # http://www.mozilla.org/MPL/
138
- #
139
- # Software distributed under the License is distributed on an "AS IS" basis,
140
- # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
141
- # for the specific language governing rights and limitations under the
142
- # License.
143
- #
144
- # The Original Code is the Netscape security libraries.
145
- #
146
- # The Initial Developer of the Original Code is
147
- # Netscape Communications Corporation.
148
- # Portions created by the Initial Developer are Copyright (C) 1994-2000
149
- # the Initial Developer. All Rights Reserved.
150
- #
151
- # Contributor(s):
152
- #
153
- # Alternatively, the contents of this file may be used under the terms of
154
- # either the GNU General Public License Version 2 or later (the "GPL"), or
155
- # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
156
- # in which case the provisions of the GPL or the LGPL are applicable instead
157
- # of those above. If you wish to allow use of your version of this file only
158
- # under the terms of either the GPL or the LGPL, and not to allow others to
159
- # use your version of this file under the terms of the MPL, indicate your
160
- # decision by deleting the provisions above and replace them with the notice
161
- # and other provisions required by the GPL or the LGPL. If you do not delete
162
- # the provisions above, a recipient may use your version of this file under
163
- # the terms of any one of the MPL, the GPL or the LGPL.
164
- #
165
- # ***** END LICENSE BLOCK *****
166
- EOM
167
- end
@@ -6,6 +6,6 @@
6
6
  <body>
7
7
 
8
8
  <%= yield %>
9
-
9
+ <script>console.log("oh hell nah")</script>
10
10
  </body>
11
11
  </html>
@@ -1 +1,2 @@
1
- index
1
+ index
2
+ <script>console.log("oh what")</script>
@@ -6,9 +6,9 @@
6
6
  csp = {
7
7
  :default_src => "self",
8
8
  :script_src => "self nonce",
9
- :disable_chrome_extension => true,
10
9
  :disable_fill_missing => true,
11
10
  :report_uri => 'somewhere',
11
+ :script_hash_middleware => true,
12
12
  :enforce => false # false means warnings only
13
13
  }
14
14
 
@@ -0,0 +1,5 @@
1
+ ---
2
+ app/views/layouts/application.html.erb:
3
+ - sha256-VjDxT7saxd2FgaUQQTWw/jsTnvonaoCP/ACWDBTpyhU=
4
+ app/views/other_things/index.html.erb:
5
+ - sha256-ZXAcP8a0y1pPMTJW8pUr43c+XBkgYQBwHOPvXk9mq5A=
@@ -2,3 +2,6 @@
2
2
 
3
3
  require ::File.expand_path('../config/environment', __FILE__)
4
4
  run Rails3212::Application
5
+
6
+ require 'secure_headers/headers/content_security_policy/script_hash_middleware'
7
+ use ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware
@@ -1,45 +1,77 @@
1
1
  require 'spec_helper'
2
2
 
3
+ require 'secure_headers/headers/content_security_policy/script_hash_middleware'
4
+
3
5
  describe OtherThingsController, :type => :controller do
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ OtherThingsController.action(:index)
10
+ end
11
+
12
+ def request(opts = {})
13
+ options = opts.merge(
14
+ {
15
+ 'HTTPS' => 'on',
16
+ 'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
17
+ }
18
+ )
19
+
20
+
21
+ Rack::MockRequest.env_for('/', options)
22
+ end
23
+
24
+
4
25
  describe "headers" do
26
+ before(:each) do
27
+ _, @env = app.call(request)
28
+ end
29
+
5
30
  it "sets the X-XSS-Protection header" do
6
- get :index
7
- expect(response.headers['X-XSS-Protection']).to eq('1; mode=block')
31
+ get '/'
32
+ expect(@env['X-XSS-Protection']).to eq('1; mode=block')
8
33
  end
9
34
 
10
35
  it "sets the X-Frame-Options header" do
11
- get :index
12
- expect(response.headers['X-Frame-Options']).to eq('SAMEORIGIN')
36
+ get '/'
37
+ expect(@env['X-Frame-Options']).to eq('SAMEORIGIN')
13
38
  end
14
39
 
15
40
  it "sets the CSP header with a local reference to a nonce" do
16
- get :index
17
- nonce = controller.instance_exec { @content_security_policy_nonce }
18
- expect(nonce).to match /[a-zA-Z0-9\+\/=]{44}/
19
- expect(response.headers['Content-Security-Policy-Report-Only']).to match(/default-src 'self'; img-src 'self' data:; script-src 'self' 'nonce-[a-zA-Z0-9\+\/=]{44}' 'unsafe-inline'; report-uri somewhere;/)
41
+ middleware = ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware.new(app)
42
+ _, env = middleware.call(request(@env))
43
+ expect(env['Content-Security-Policy-Report-Only']).to match(/script-src[^;]*'nonce-[a-zA-Z0-9\+\/=]{44}'/)
44
+ end
45
+
46
+ it "sets the required hashes to whitelist inline script" do
47
+ middleware = ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware.new(app)
48
+ _, env = middleware.call(request(@env))
49
+ hashes = ['sha256-VjDxT7saxd2FgaUQQTWw/jsTnvonaoCP/ACWDBTpyhU=', 'sha256-ZXAcP8a0y1pPMTJW8pUr43c+XBkgYQBwHOPvXk9mq5A=']
50
+ hashes.each do |hash|
51
+ expect(env['Content-Security-Policy-Report-Only']).to include(hash)
52
+ end
20
53
  end
21
54
 
22
55
  it "sets the Strict-Transport-Security header" do
23
- request.env['HTTPS'] = 'on'
24
- get :index
25
- expect(response.headers['Strict-Transport-Security']).to eq("max-age=315576000")
56
+ get '/'
57
+ expect(@env['Strict-Transport-Security']).to eq("max-age=315576000")
26
58
  end
27
59
 
28
60
  it "sets the X-Download-Options header" do
29
- get :index
30
- expect(response.headers['X-Download-Options']).to eq('noopen')
61
+ get '/'
62
+ expect(@env['X-Download-Options']).to eq('noopen')
31
63
  end
32
64
 
33
65
  it "sets the X-Content-Type-Options header" do
34
- get :index
35
- expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
66
+ get '/'
67
+ expect(@env['X-Content-Type-Options']).to eq("nosniff")
36
68
  end
37
69
 
38
70
  context "using IE" do
39
71
  it "sets the X-Content-Type-Options header" do
40
- request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
41
- get :index
42
- expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
72
+ @env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
73
+ get '/'
74
+ expect(@env['X-Content-Type-Options']).to eq("nosniff")
43
75
  end
44
76
  end
45
77
  end
@@ -16,7 +16,7 @@ describe ThingsController, :type => :controller do
16
16
  expect(response.headers['X-Frame-Options']).to eq('SAMEORIGIN')
17
17
  end
18
18
 
19
- it "sets the X-WebKit-CSP header" do
19
+ it "does not set CSP header" do
20
20
  get :index
21
21
  expect(response.headers['Content-Security-Policy-Report-Only']).to eq(nil)
22
22
  end
@@ -1,6 +1,5 @@
1
1
  class OtherThingsController < ApplicationController
2
- ensure_security_headers :csp => {:default_src => 'self', :disable_chrome_extension => true,
3
- :disable_fill_missing => true}
2
+ ensure_security_headers :csp => {:default_src => 'self', :disable_fill_missing => true}
4
3
  def index
5
4
 
6
5
  end
@@ -0,0 +1,7 @@
1
+ module SecureHeaders
2
+ module HashHelper
3
+ def hash_source(inline_script, digest = :SHA256)
4
+ [digest.to_s.downcase, "-", [[Digest.const_get(digest).hexdigest(inline_script)].pack("H*")].pack("m").chomp].join
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module SecureHeaders
2
+ class ContentSecurityPolicy
3
+ class ScriptHashMiddleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ status, headers, response = @app.call(env)
10
+ metadata = env[ContentSecurityPolicy::ENV_KEY]
11
+ if !metadata.nil?
12
+ config, options = metadata.values_at(:config, :options)
13
+ config.merge!(:script_hashes => env[HASHES_ENV_KEY])
14
+ csp_header = ContentSecurityPolicy.new(config, options)
15
+ headers[csp_header.name] = csp_header.value
16
+ end
17
+
18
+ [status, headers, response]
19
+ end
20
+ end
21
+ end
22
+ end