secure_headers 1.4.1 → 2.0.0.pre2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/.gitignore +4 -8
  2. data/Gemfile +2 -2
  3. data/Guardfile +8 -0
  4. data/README.md +114 -48
  5. data/Rakefile +0 -116
  6. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +1 -1
  7. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +2 -1
  8. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +2 -1
  9. data/fixtures/rails_3_2_12/config/script_hashes.yml +5 -0
  10. data/fixtures/rails_3_2_12/config.ru +3 -0
  11. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +55 -18
  12. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +6 -1
  13. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +1 -2
  14. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +5 -0
  15. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +5 -0
  16. data/lib/secure_headers/hash_helper.rb +7 -0
  17. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +22 -0
  18. data/lib/secure_headers/headers/content_security_policy.rb +141 -137
  19. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +40 -0
  20. data/lib/secure_headers/railtie.rb +0 -22
  21. data/lib/secure_headers/version.rb +1 -1
  22. data/lib/secure_headers/view_helper.rb +68 -0
  23. data/lib/secure_headers.rb +59 -17
  24. data/lib/tasks/tasks.rake +48 -0
  25. data/secure_headers.gemspec +4 -4
  26. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +47 -0
  27. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +83 -208
  28. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +1 -1
  29. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +64 -0
  30. data/spec/lib/secure_headers_spec.rb +36 -61
  31. data/spec/spec_helper.rb +26 -1
  32. metadata +19 -14
  33. data/HISTORY.md +0 -162
  34. data/app/controllers/content_security_policy_controller.rb +0 -76
  35. data/config/curl-ca-bundle.crt +0 -5420
  36. data/config/routes.rb +0 -3
  37. data/spec/controllers/content_security_policy_controller_spec.rb +0 -90
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)
@@ -6,6 +6,8 @@ The gem will automatically apply several headers that are related to security.
6
6
  - 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)
7
7
  - X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](http://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
8
8
  - X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
9
+ - X-Download-Options - [Prevent file downloads opening](http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx)
10
+ - 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)
9
11
 
10
12
  This gem has integration with Rails, but works for any Ruby code. See the sinatra example section.
11
13
 
@@ -48,14 +50,14 @@ The following methods are going to be called, unless they are provided in a `ski
48
50
  * `:set_x_frame_options_header`
49
51
  * `:set_x_xss_protection_header`
50
52
  * `:set_x_content_type_options_header`
53
+ * `:set_x_download_options_header`
54
+ * `:set_x_permitted_cross_domain_policies_header`
51
55
 
52
56
  ### Bonus Features
53
57
 
54
58
  This gem makes a few assumptions about how you will use some features. For example:
55
59
 
56
60
  * 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
61
 
60
62
  ## Configuration
61
63
 
@@ -67,10 +69,12 @@ This gem makes a few assumptions about how you will use some features. For exam
67
69
  config.x_frame_options = 'DENY'
68
70
  config.x_content_type_options = "nosniff"
69
71
  config.x_xss_protection = {:value => 1, :mode => 'block'}
72
+ config.x_download_options = 'noopen'
73
+ config.x_permitted_cross_domain_policies = 'none'
70
74
  config.csp = {
71
- :default_src => "https://* self",
72
- :frame_src => "https://* http://*.twimg.com http://itunes.apple.com",
73
- :img_src => "https://*",
75
+ :default_src => "https: self",
76
+ :frame_src => "https: http:.twimg.com http://itunes.apple.com",
77
+ :img_src => "https:",
74
78
  :report_uri => '//example.com/uri-directive'
75
79
  }
76
80
  end
@@ -108,6 +112,8 @@ This configuration will likely work for most applications without modification.
108
112
  :x_frame_options => {:value => 'SAMEORIGIN'}
109
113
  :x_xss_protection => {:value => 1, :mode => 'block'} # set the :mode option to false to use "warning only" mode
110
114
  :x_content_type_options => {:value => 'nosniff'}
115
+ :x_download_options => {:value => 'noopen'}
116
+ :x_permitted_cross_domain_policies => {:value => 'none'}
111
117
  ```
112
118
 
113
119
  ### Content Security Policy (CSP)
@@ -125,11 +131,6 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
125
131
  # 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
132
  :report_uri => '//mysite.example.com',
127
133
 
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
134
  # these directives all take 'none', 'self', or a globbed pattern
134
135
  :img_src => nil,
135
136
  :frame_src => nil,
@@ -144,25 +145,12 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
144
145
  # over http, relaxing the policy
145
146
  # e.g.
146
147
  # :csp => {
147
- # :img_src => 'https://*',
148
- # :http_additions => {:img_src => 'http//*'}
148
+ # :img_src => 'https:',
149
+ # :http_additions => {:img_src => 'http'}
149
150
  # }
150
- # would produce the directive: "img-src https://* http://*;"
151
+ # would produce the directive: "img-src https: http:;"
151
152
  # when over http, ignored for https requests
152
153
  :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
154
  }
167
155
  ```
168
156
 
@@ -172,19 +160,19 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
172
160
  ```ruby
173
161
  # most basic example
174
162
  :csp => {
175
- :default_src => "https://* inline eval",
163
+ :default_src => "https: inline eval",
176
164
  :report_uri => '/uri-directive'
177
165
  }
178
166
 
179
- > "default-src 'unsafe-inline' 'unsafe-eval' https://*; report-uri /uri-directive;"
167
+ > "default-src 'unsafe-inline' 'unsafe-eval' https:; report-uri /uri-directive;"
180
168
 
181
169
  # turn off inline scripting/eval
182
170
  :csp => {
183
- :default_src => 'https://*',
171
+ :default_src => 'https:',
184
172
  :report_uri => '/uri-directive'
185
173
  }
186
174
 
187
- > "default-src https://*; report-uri /uri-directive;"
175
+ > "default-src https:; report-uri /uri-directive;"
188
176
 
189
177
  # 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
178
  :csp => {
@@ -217,9 +205,13 @@ report-uri csp_reports?enforce=true&app_name=twitter
217
205
 
218
206
  ### CSP Level 2 features
219
207
 
208
+ *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.*
209
+
210
+ #### Nonce
211
+
220
212
  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
213
 
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.
214
+ 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
215
 
224
216
  ```ruby
225
217
  :csp => {
@@ -243,29 +235,99 @@ script/style-nonce can be used to whitelist inline content. To do this, add "non
243
235
  console.log("won't execute, not whitelisted")
244
236
  </script>
245
237
  ```
238
+ You can use a view helper to automatically add nonces to script tags:
239
+ ```erb
240
+ <%= nonced_javascript_tag do %>
241
+ console.log("nonced!")
242
+ <% end %>
243
+ <%= nonced_javascript_tag("nonced without a block!") %>
244
+ ```
246
245
 
247
- ## Note on Firefox handling of CSP
246
+ becomes:
248
247
 
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`
248
+ ```html
249
+ <script nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
250
+ console.log("nonced!")
251
+ </script>
252
+ ```
253
+
254
+ #### Hash
250
255
 
251
- ### Adding the Firefox report forwarding endpoint
256
+ 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
257
 
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!!!**
258
+ 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
259
 
256
- #### Rails 2
260
+ If you only have a few hashes, you can hardcode them for the entire app:
257
261
 
258
262
  ```ruby
259
- map.csp_endpoint
263
+ config.csp = {
264
+ :default_src => "https:",
265
+ :script_src => 'self'
266
+ :script_hashes => ['sha1-abc', 'sha1-qwe']
267
+ }
260
268
  ```
261
269
 
262
- #### Rails 3
270
+ The following will work as well, but may not be as clear:
271
+
272
+ ```ruby
273
+ config.csp = {
274
+ :default_src => "https:",
275
+ :script_src => "self 'sha1-qwe'"
276
+ }
277
+ ```
263
278
 
264
- If the csp reporting endpoint is clobbered by another route, add:
279
+ 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
280
 
266
281
  ```ruby
267
- post SecureHeaders::ContentSecurityPolicy::FF_CSP_ENDPOINT => "content_security_policy#scribe"
282
+ # config.ru
283
+ require 'secure_headers/headers/content_security_policy/script_hash_middleware'
284
+ use ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware
268
285
  ```
286
+
287
+ ```ruby
288
+ config.csp = {
289
+ :default_src => "https:",
290
+ :script_src => 'self',
291
+ :script_hash_middleware => true
292
+ }
293
+ ```
294
+
295
+ 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:
296
+
297
+ ```$ bundle exec rake secure_headers:generate_hashes```
298
+
299
+ Which will generate something like:
300
+
301
+ ```yaml
302
+ # config/script_hashes.yml
303
+ app/views/layouts/application.html.erb:
304
+ - sha256-l8OLjZqYRnKilpdE0VosRMvhdYArjXT4NZaK2p7QVvs=
305
+ app/templates/articles/edit.html.erb:
306
+ - sha256-+7mij1/uCwtCQRWrof2NmOln5qX+5WdVwTLMpi8nuoA=
307
+ - sha256-Ny4TRIhhFpnYnSeKC274P6bfAz4TOkezLabavIAU4dA=
308
+ - sha256-I5e58Gqbu4WpO9dck18QxO7aYOHKrELIi70it4jIPi0=
309
+ - sha256-Po4LMynwnAJHxiTp3DQaQ3YDBj3paN/xrDoKl4OyxY4=
310
+ ```
311
+
312
+ In this example, if we visit /articles/edit/[id], the above hashes will automatically be added to the CSP header's
313
+ script-src value!
314
+
315
+ You can use plain "script" tags or you can use a built-in helper:
316
+
317
+ ```erb
318
+ <%= hashed_javascript_tag do %>
319
+ console.log("hashed automatically!")
320
+ <% end %>
321
+ ```
322
+
323
+ 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:
324
+
325
+ ```erb
326
+ <%= hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
327
+ console.log("will raise an exception if not in script_hashes.yml!")
328
+ <% end %>
329
+ ```
330
+
269
331
  ### Using with Sinatra
270
332
 
271
333
  Here's an example using SecureHeaders for Sinatra applications:
@@ -281,11 +343,13 @@ require 'secure_headers'
281
343
  config.x_frame_options = 'DENY'
282
344
  config.x_content_type_options = "nosniff"
283
345
  config.x_xss_protection = {:value => 1, :mode => false}
346
+ config.x_download_options = 'noopen'
347
+ config.x_permitted_cross_domain_policies = 'none'
284
348
  config.csp = {
285
- :default_src => "https://* inline eval",
349
+ :default_src => "https: inline eval",
286
350
  :report_uri => '//example.com/uri-directive',
287
- :img_src => "https://* data:",
288
- :frame_src => "https://* http://*.twimg.com http://itunes.apple.com"
351
+ :img_src => "https: data:",
352
+ :frame_src => "https: http:.twimg.com http://itunes.apple.com"
289
353
  }
290
354
  end
291
355
 
@@ -336,11 +400,13 @@ def before_load
336
400
  config.x_frame_options = 'DENY'
337
401
  config.x_content_type_options = "nosniff"
338
402
  config.x_xss_protection = {:value => '1', :mode => false}
403
+ config.x_download_options = 'noopen'
404
+ config.x_permitted_cross_domain_policies = 'none'
339
405
  config.csp = {
340
- :default_src => "https://* inline eval",
406
+ :default_src => "https: inline eval",
341
407
  :report_uri => '//example.com/uri-directive',
342
- :img_src => "https://* data:",
343
- :frame_src => "https://* http://*.twimg.com http://itunes.apple.com"
408
+ :img_src => "https: data:",
409
+ :frame_src => "https: http:.twimg.com http://itunes.apple.com"
344
410
  }
345
411
  end
346
412
  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>
@@ -3,12 +3,13 @@
3
3
  config.x_frame_options = 'SAMEORIGIN'
4
4
  config.x_content_type_options = "nosniff"
5
5
  config.x_xss_protection = {:value => 1, :mode => 'block'}
6
+ config.x_permitted_cross_domain_policies = 'none'
6
7
  csp = {
7
8
  :default_src => "self",
8
9
  :script_src => "self nonce",
9
- :disable_chrome_extension => true,
10
10
  :disable_fill_missing => true,
11
11
  :report_uri => 'somewhere',
12
+ :script_hash_middleware => true,
12
13
  :enforce => false # false means warnings only
13
14
  }
14
15
 
@@ -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,82 @@
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")
68
+ end
69
+
70
+ it "sets the X-Permitted-Cross-Domain-Policies" do
71
+ get '/'
72
+ expect(@env['X-Permitted-Cross-Domain-Policies']).to eq("none")
36
73
  end
37
74
 
38
75
  context "using IE" do
39
76
  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")
77
+ @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"
78
+ get '/'
79
+ expect(@env['X-Content-Type-Options']).to eq("nosniff")
43
80
  end
44
81
  end
45
82
  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
@@ -38,6 +38,11 @@ describe ThingsController, :type => :controller do
38
38
  expect(response.headers['X-Content-Type-Options']).to eq("nosniff")
39
39
  end
40
40
 
41
+ it "sets the X-Permitted-Cross-Domain-Policies" do
42
+ get :index
43
+ expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
44
+ end
45
+
41
46
  context "using IE" do
42
47
  it "sets the X-Content-Type-Options header" do
43
48
  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"
@@ -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
@@ -34,6 +34,11 @@ describe OtherThingsController, :type => :controller do
34
34
  expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
35
35
  end
36
36
 
37
+ it "sets the X-Permitted-Cross-Domain-Policies" do
38
+ get :index
39
+ expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
40
+ end
41
+
37
42
  context "using IE" do
38
43
  it "sets the X-Content-Type-Options header" do
39
44
  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"
@@ -38,6 +38,11 @@ describe ThingsController, :type => :controller do
38
38
  expect(response.headers['X-Content-Type-Options']).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
39
39
  end
40
40
 
41
+ it "sets the X-Permitted-Cross-Domain-Policies" do
42
+ get :index
43
+ expect(response.headers['X-Permitted-Cross-Domain-Policies']).to eq("none")
44
+ end
45
+
41
46
  context "using IE" do
42
47
  it "sets the X-Content-Type-Options header" do
43
48
  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"
@@ -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