secure_headers 2.2.0 → 2.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -1
  4. data/Gemfile +3 -2
  5. data/README.md +89 -18
  6. data/Rakefile +2 -2
  7. data/fixtures/{rails_3_2_12_no_init → rails_3_2_22}/Gemfile +2 -1
  8. data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/initializers/secure_headers.rb +2 -3
  9. data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/controllers/other_things_controller_spec.rb +1 -1
  10. data/fixtures/{rails_3_2_12 → rails_3_2_22_no_init}/Gemfile +2 -2
  11. data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +20 -0
  12. data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/controllers/other_things_controller_spec.rb +7 -1
  13. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +1 -1
  14. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +2 -3
  15. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +1 -1
  16. data/lib/secure_headers/headers/content_security_policy.rb +41 -41
  17. data/lib/secure_headers/headers/public_key_pins.rb +1 -0
  18. data/lib/secure_headers/headers/strict_transport_security.rb +1 -0
  19. data/lib/secure_headers/headers/x_content_type_options.rb +2 -1
  20. data/lib/secure_headers/headers/x_download_options.rb +1 -0
  21. data/lib/secure_headers/headers/x_frame_options.rb +1 -0
  22. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -0
  23. data/lib/secure_headers/headers/x_xss_protection.rb +1 -0
  24. data/lib/secure_headers/version.rb +1 -1
  25. data/lib/secure_headers.rb +59 -27
  26. data/secure_headers.gemspec +1 -1
  27. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +2 -2
  28. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +64 -36
  29. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +1 -1
  30. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +1 -1
  31. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +1 -1
  32. data/spec/lib/secure_headers_spec.rb +51 -5
  33. data/spec/spec_helper.rb +8 -1
  34. metadata +72 -59
  35. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -6
  36. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/.rspec +0 -0
  37. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/README.rdoc +0 -0
  38. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/Rakefile +0 -0
  39. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/application_controller.rb +0 -0
  40. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/other_things_controller.rb +0 -0
  41. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/things_controller.rb +0 -0
  42. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/models/.gitkeep +0 -0
  43. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/layouts/application.html.erb +0 -0
  44. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/other_things/index.html.erb +0 -0
  45. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/things/index.html.erb +0 -0
  46. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/application.rb +0 -0
  47. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/boot.rb +0 -0
  48. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/environment.rb +0 -0
  49. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/environments/test.rb +0 -0
  50. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/routes.rb +0 -0
  51. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/script_hashes.yml +0 -0
  52. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config.ru +0 -0
  53. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/lib/assets/.gitkeep +0 -0
  54. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/lib/tasks/.gitkeep +0 -0
  55. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/log/.gitkeep +0 -0
  56. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/controllers/things_controller_spec.rb +0 -0
  57. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/spec_helper.rb +0 -0
  58. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/assets/javascripts/.gitkeep +0 -0
  59. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/assets/stylesheets/.gitkeep +0 -0
  60. /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/plugins/.gitkeep +0 -0
  61. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/.rspec +0 -0
  62. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/README.rdoc +0 -0
  63. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/Rakefile +0 -0
  64. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/controllers/application_controller.rb +0 -0
  65. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/controllers/things_controller.rb +0 -0
  66. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/models/.gitkeep +0 -0
  67. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/layouts/application.html.erb +0 -0
  68. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/other_things/index.html.erb +0 -0
  69. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/things/index.html.erb +0 -0
  70. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/application.rb +0 -0
  71. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/boot.rb +0 -0
  72. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/environment.rb +0 -0
  73. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/environments/test.rb +0 -0
  74. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/routes.rb +0 -0
  75. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config.ru +0 -0
  76. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/lib/assets/.gitkeep +0 -0
  77. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/lib/tasks/.gitkeep +0 -0
  78. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/log/.gitkeep +0 -0
  79. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/controllers/things_controller_spec.rb +0 -0
  80. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/spec_helper.rb +0 -0
  81. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/assets/javascripts/.gitkeep +0 -0
  82. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/assets/stylesheets/.gitkeep +0 -0
  83. /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/plugins/.gitkeep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e454769ab715d2e94e5451190e7ef8679810ec0
4
- data.tar.gz: 9f0fd48b03bd3e8f5a60b85894492a4a9aee08a0
3
+ metadata.gz: bdc7e4cc47367102f2318244051b544e4bc5d611
4
+ data.tar.gz: f4bb9651462b15c056ab9720a0c079283e9c2462
5
5
  SHA512:
6
- metadata.gz: 9ace533baba91512c2d8b15f12ed188d93feda9b20f7e682caaa740d413b1428e9c39deb2c2fbba4aec6462093f54126de9ca8cc5268d5b8d98851c82876fa6d
7
- data.tar.gz: 9b5e12226b456e4983eb31cd75f48ccadf34d80d125ff0fb67ee84afe56f3ca3a682890213e20e2c24e2c715073c0284b4ab8fc3fc86ad722c7ae6d08c0bb207
6
+ metadata.gz: 888729b84ba1eb266c25c3bbc218c270f9e262bcbf490363a2daad6202803f4ba71a21f59c1a36e816a06c01ab8d28126ba81e05ae4f37ac36a53ee670af8420
7
+ data.tar.gz: 26640f0a917396faf083eaff557316d4a376e6956ae95915cfb5c815de3269a6ec5cb9a9f6a7684d871d8ff634dc586bb6a6a20dd79ece6cee034d5ec8f20648
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.1.6
1
+ 2.2.3
data/.travis.yml CHANGED
@@ -1,10 +1,10 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
+ - "2.2"
4
5
  - "2.1"
5
6
  - "2.0.0"
6
7
  - "1.9.3"
7
- - "1.8.7"
8
8
  - "jruby-19mode"
9
9
 
10
10
  sudo: false
data/Gemfile CHANGED
@@ -3,13 +3,14 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'rails', '3.2.12'
6
+ gem 'test-unit', '~> 3.0'
7
+ gem 'rails', '3.2.22'
7
8
  gem 'sqlite3', :platforms => [:ruby, :mswin, :mingw]
8
9
  gem 'jdbc-sqlite3', :platforms => [:jruby]
9
10
  gem 'rspec-rails', '>= 3.1'
10
11
  gem 'rspec', '>= 3.1'
11
12
  gem 'growl'
12
13
  gem 'rb-fsevent'
13
- gem 'coveralls', :platforms => [:ruby_19]
14
+ gem 'coveralls', :platforms => [:ruby_19, :ruby_20, :ruby_21]
14
15
  gem 'i18n', '< 0.7.0', :platforms => [:ruby_18]
15
16
  end
data/README.md CHANGED
@@ -8,7 +8,7 @@ The gem will automatically apply several headers that are related to security.
8
8
  - X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
9
9
  - X-Download-Options - [Prevent file downloads opening](http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx)
10
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)
11
- - Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorites. [Public Key Pinnning Specification](https://tools.ietf.org/html/rfc7469)
11
+ - Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key Pinnning Specification](https://tools.ietf.org/html/rfc7469)
12
12
 
13
13
  ## Usage
14
14
 
@@ -29,16 +29,12 @@ The following methods are going to be called, unless they are provided in a `ski
29
29
  * `:set_x_download_options_header`
30
30
  * `:set_x_permitted_cross_domain_policies_header`
31
31
 
32
- ### Bonus Features
33
-
34
- This gem makes a few assumptions about how you will use some features. For example:
35
-
36
- * 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.
37
-
38
32
  ## Configuration
39
33
 
40
34
  **Place the following in an initializer (recommended):**
41
35
 
36
+ **NOTE: All CSP config values accept procs for one way of dynamically setting values**
37
+
42
38
  ```ruby
43
39
  ::SecureHeaders::Configuration.configure do |config|
44
40
  config.hsts = {:max_age => 20.years.to_i, :include_subdomains => true}
@@ -48,10 +44,23 @@ This gem makes a few assumptions about how you will use some features. For exam
48
44
  config.x_download_options = 'noopen'
49
45
  config.x_permitted_cross_domain_policies = 'none'
50
46
  config.csp = {
51
- :default_src => "https: self",
52
- :enforce => proc {|controller| contoller.current_user.enforce_csp? }
47
+ :default_src => "https: 'self'",
48
+ :enforce => proc {|controller| controller.my_feature_flag_api.enabled? },
53
49
  :frame_src => "https: http:.twimg.com http://itunes.apple.com",
54
50
  :img_src => "https:",
51
+ :connect_src => "wws:"
52
+ :font_src => "'self' data:",
53
+ :frame_src => "'self'",
54
+ :img_src => "mycdn.com data:",
55
+ :media_src => "utoob.com",
56
+ :object_src => "'self'",
57
+ :script_src => "'self'",
58
+ :style_src => "'unsafe-inline'",
59
+ :base_uri => "'self'",
60
+ :child_src => "'self'",
61
+ :form_action => "'self' github.com",
62
+ :frame_ancestors => "'none'",
63
+ :plugin_types => 'application/x-shockwave-flash',
55
64
  :report_uri => '//example.com/uri-directive'
56
65
  }
57
66
  config.hpkp = {
@@ -81,6 +90,37 @@ ensure_security_headers(
81
90
  )
82
91
  ```
83
92
 
93
+ ## Per-action configuration
94
+
95
+ Sometimes you need to override your content security policy for a given endpoint. Rather than applying the exception globally, you have a few options:
96
+
97
+ 1. Use procs as config values as mentioned above.
98
+ 1. Specifying `ensure_security_headers csp: ::SecureHeaders::Configuration.csp.merge(script_src: shadyhost.com)` in a descendent controller will override the settings for that controller only.
99
+ 1. Override the `secure_header_options_for` class instance method. e.g.
100
+
101
+ ```ruby
102
+ class SomethingController < ApplicationController
103
+ def wumbus
104
+ # gets style-src override
105
+ end
106
+
107
+ def diffendoofer
108
+ # does not get style-src override
109
+ end
110
+
111
+ def secure_header_options_for(header, options)
112
+ options = super
113
+ if params[:action] == "wumbus"
114
+ if header == :csp
115
+ options.merge(style_src: "'self'")
116
+ end
117
+ else
118
+ options
119
+ end
120
+ end
121
+ end
122
+ ```
123
+
84
124
  ## Options for ensure\_security\_headers
85
125
 
86
126
  **To disable any of these headers, supply a value of false (e.g. :hsts => false), supplying nil will set the default value**
@@ -142,7 +182,7 @@ This configuration will likely work for most applications without modification.
142
182
  ```ruby
143
183
  # most basic example
144
184
  :csp => {
145
- :default_src => "https: inline eval",
185
+ :default_src => "https: 'unsafe-inline' 'unsafe-eval'",
146
186
  :report_uri => '/uri-directive'
147
187
  }
148
188
 
@@ -158,7 +198,7 @@ This configuration will likely work for most applications without modification.
158
198
 
159
199
  # 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
160
200
  :csp => {
161
- :default_src => 'self',
201
+ :default_src => "'self'",
162
202
  :img_src => '*',
163
203
  :object_src => ['media1.com', 'media2.com', '*.cdn.com'],
164
204
  # alternatively (NOT csv) :object_src => 'media1.com media2.com *.cdn.com'
@@ -197,8 +237,8 @@ Setting a nonce will also set 'unsafe-inline' for browsers that don't support no
197
237
 
198
238
  ```ruby
199
239
  :csp => {
200
- :default_src => 'self',
201
- :script_src => 'self nonce'
240
+ :default_src => "'self'",
241
+ :script_src => "'self' nonce"
202
242
  }
203
243
  ```
204
244
 
@@ -244,7 +284,7 @@ If you only have a few hashes, you can hardcode them for the entire app:
244
284
  ```ruby
245
285
  config.csp = {
246
286
  :default_src => "https:",
247
- :script_src => 'self'
287
+ :script_src => "'self'"
248
288
  :script_hashes => ['sha1-abc', 'sha1-qwe']
249
289
  }
250
290
  ```
@@ -254,7 +294,7 @@ The following will work as well, but may not be as clear:
254
294
  ```ruby
255
295
  config.csp = {
256
296
  :default_src => "https:",
257
- :script_src => "self 'sha1-qwe'"
297
+ :script_src => "'self' 'sha1-qwe'"
258
298
  }
259
299
  ```
260
300
 
@@ -269,7 +309,7 @@ use ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware
269
309
  ```ruby
270
310
  config.csp = {
271
311
  :default_src => "https:",
272
- :script_src => 'self',
312
+ :script_src => "'self'",
273
313
  :script_hash_middleware => true
274
314
  }
275
315
  ```
@@ -415,12 +455,43 @@ def before_load
415
455
  end
416
456
  ```
417
457
 
458
+ ### Using in rack middleware
459
+
460
+ The `SecureHeaders::header_hash` generates a hash of all header values, which is useful for merging with rack middleware values.
461
+
462
+ ```ruby
463
+ class MySecureHeaders
464
+ include SecureHeaders
465
+ def initialize(app)
466
+ @app = app
467
+ end
468
+
469
+ def call(env)
470
+ status, headers, response = @app.call(env)
471
+ security_headers = if override?
472
+ SecureHeaders::header_hash(:csp => false) # uses global config, but overrides CSP config
473
+ else
474
+ SecureHeaders::header_hash # uses global config
475
+ end
476
+ [status, headers.merge(security_headers), [response.body]]
477
+ end
478
+ end
479
+
480
+ module Testapp
481
+ class Application < Rails::Application
482
+ config.middleware.use MySecureHeaders
483
+ end
484
+ end
485
+ ```
486
+
418
487
  ## Similar libraries
419
488
 
489
+ * Rack [rack-secure_headers](https://github.com/harmoni/rack-secure_headers)
420
490
  * Node.js (express) [helmet](https://github.com/evilpacket/helmet) and [hood](https://github.com/seanmonstar/hood)
421
- * J2EE Servlet >= 3.0 [highlines](https://github.com/sourceclear/headlines)
491
+ * Node.js (hapi) [blankie](https://github.com/nlf/blankie)
492
+ * J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
422
493
  * ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
423
- * Python - [django-csp](https://github.com/mozilla/django-csp/) + [commonware](https://github.com/jsocol/commonware/)
494
+ * Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
424
495
  * Go - [secureheader](https://github.com/kr/secureheader)
425
496
 
426
497
  ## Authors
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ task :default => :all_spec
15
15
  desc "Run all specs, and test fixture apps"
16
16
  task :all_spec => :spec do
17
17
  pwd = Dir.pwd
18
- Dir.chdir 'fixtures/rails_3_2_12'
18
+ Dir.chdir 'fixtures/rails_3_2_22'
19
19
  puts Dir.pwd
20
20
  str = `bundle install >> /dev/null; bundle exec rspec spec`
21
21
  puts str
@@ -25,7 +25,7 @@ task :all_spec => :spec do
25
25
  end
26
26
 
27
27
  Dir.chdir pwd
28
- Dir.chdir 'fixtures/rails_3_2_12_no_init'
28
+ Dir.chdir 'fixtures/rails_3_2_22_no_init'
29
29
  puts Dir.pwd
30
30
  puts `bundle install >> /dev/null; bundle exec rspec spec`
31
31
 
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'rails', '3.2.12'
3
+ gem 'test-unit', '~> 3.0'
4
+ gem 'rails', '3.2.22'
4
5
  gem 'rspec-rails', '>= 2.0.0'
5
6
  gem 'secure_headers', :path => '../..'
@@ -5,9 +5,8 @@
5
5
  config.x_xss_protection = {:value => 1, :mode => 'block'}
6
6
  config.x_permitted_cross_domain_policies = 'none'
7
7
  csp = {
8
- :default_src => "self",
9
- :script_src => "self nonce",
10
- :disable_fill_missing => true,
8
+ :default_src => "'self'",
9
+ :script_src => "'self' nonce",
11
10
  :report_uri => 'somewhere',
12
11
  :script_hash_middleware => true,
13
12
  :enforce => false # false means warnings only
@@ -13,7 +13,7 @@ describe OtherThingsController, :type => :controller do
13
13
  options = opts.merge(
14
14
  {
15
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"
16
+ 'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
17
17
  }
18
18
  )
19
19
 
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'rails', '3.2.12'
3
+ gem 'test-unit'
4
+ gem 'rails', '3.2.22'
4
5
  gem 'rspec-rails', '>= 2.0.0'
5
6
  gem 'secure_headers', :path => '../..'
6
-
@@ -0,0 +1,20 @@
1
+ class OtherThingsController < ApplicationController
2
+ ensure_security_headers :csp => {:default_src => "'self'"}
3
+ def index
4
+
5
+ end
6
+
7
+ def other_action
8
+ render :text => 'yooooo'
9
+ end
10
+
11
+ def secure_header_options_for(header, options)
12
+ if params[:action] == "other_action"
13
+ if header == :csp
14
+ options.merge(:style_src => "'self'")
15
+ end
16
+ else
17
+ options
18
+ end
19
+ end
20
+ end
@@ -12,11 +12,17 @@ describe OtherThingsController, :type => :controller do
12
12
  expect(response.headers['X-Frame-Options']).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
13
13
  end
14
14
 
15
- it "sets the X-WebKit-CSP header" do
15
+ it "sets the CSP header" do
16
16
  get :index
17
17
  expect(response.headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; img-src 'self' data:;")
18
18
  end
19
19
 
20
+ it "sets per-action values based on secure_header_options_for" do
21
+ # munges :style_src => self into policy
22
+ get :other_action
23
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; img-src 'self' data:; style-src 'self';")
24
+ end
25
+
20
26
  #mock ssl
21
27
  it "sets the Strict-Transport-Security header" do
22
28
  request.env['HTTPS'] = 'on'
@@ -2,4 +2,4 @@ class OtherThingsController < ApplicationController
2
2
  def index
3
3
 
4
4
  end
5
- end
5
+ end
@@ -5,9 +5,8 @@
5
5
  config.x_xss_protection = {:value => 0}
6
6
  config.x_permitted_cross_domain_policies = 'none'
7
7
  csp = {
8
- :default_src => "self",
9
- :script_src => "self nonce",
10
- :disable_fill_missing => true,
8
+ :default_src => "'self'",
9
+ :script_src => "'self' nonce",
11
10
  :report_uri => 'somewhere',
12
11
  :script_hash_middleware => true,
13
12
  :enforce => false # false means warnings only
@@ -13,7 +13,7 @@ describe OtherThingsController, :type => :controller do
13
13
  options = opts.merge(
14
14
  {
15
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"
16
+ 'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
17
17
  }
18
18
  )
19
19
 
@@ -1,6 +1,8 @@
1
1
  require 'uri'
2
2
  require 'base64'
3
3
  require 'securerandom'
4
+ require 'user_agent_parser'
5
+ require 'json'
4
6
 
5
7
  module SecureHeaders
6
8
  class ContentSecurityPolicyBuildError < StandardError; end
@@ -18,31 +20,25 @@ module SecureHeaders
18
20
  :media_src,
19
21
  :object_src,
20
22
  :script_src,
21
- :style_src
22
- ]
23
-
24
- NON_DEFAULT_SOURCES = [
23
+ :style_src,
25
24
  :base_uri,
26
25
  :child_src,
27
26
  :form_action,
28
27
  :frame_ancestors,
29
- :plugin_types,
30
- :referrer,
31
- :reflected_xss
28
+ :plugin_types
32
29
  ]
33
30
 
34
31
  OTHER = [
35
32
  :report_uri
36
33
  ]
37
34
 
38
- SOURCE_DIRECTIVES = DIRECTIVES + NON_DEFAULT_SOURCES
39
-
40
- ALL_DIRECTIVES = DIRECTIVES + NON_DEFAULT_SOURCES + OTHER
35
+ ALL_DIRECTIVES = DIRECTIVES + OTHER
36
+ CONFIG_KEY = :csp
41
37
  end
38
+
42
39
  include Constants
43
40
 
44
- attr_reader :disable_fill_missing, :ssl_request
45
- alias :disable_fill_missing? :disable_fill_missing
41
+ attr_reader :ssl_request
46
42
  alias :ssl_request? :ssl_request
47
43
 
48
44
  class << self
@@ -108,7 +104,7 @@ module SecureHeaders
108
104
  @config = config.inject({}) do |hash, (key, value)|
109
105
  config_val = value.respond_to?(:call) ? value.call(@controller) : value
110
106
 
111
- if SOURCE_DIRECTIVES.include?(key) # directives need to be normalized to arrays of strings
107
+ if DIRECTIVES.include?(key) # directives need to be normalized to arrays of strings
112
108
  config_val = config_val.split if config_val.is_a? String
113
109
  if config_val.is_a?(Array)
114
110
  config_val = config_val.map do |val|
@@ -124,14 +120,12 @@ module SecureHeaders
124
120
  @http_additions = @config.delete(:http_additions)
125
121
  @app_name = @config.delete(:app_name)
126
122
  @report_uri = @config.delete(:report_uri)
127
-
128
- @disable_fill_missing = !!@config.delete(:disable_fill_missing)
129
123
  @enforce = !!@config.delete(:enforce)
124
+ @disable_img_src_data_uri = !!@config.delete(:disable_img_src_data_uri)
130
125
  @tag_report_uri = !!@config.delete(:tag_report_uri)
131
126
  @script_hashes = @config.delete(:script_hashes) || []
132
127
 
133
128
  add_script_hashes if @script_hashes.any?
134
- fill_directives unless disable_fill_missing?
135
129
  end
136
130
 
137
131
  ##
@@ -164,6 +158,21 @@ module SecureHeaders
164
158
  end
165
159
  end
166
160
 
161
+ def to_json
162
+ build_value
163
+ @config.to_json.gsub(/(\w+)_src/, "\\1-src")
164
+ end
165
+
166
+ def self.from_json(*json_configs)
167
+ json_configs.inject({}) do |combined_config, one_config|
168
+ one_config = one_config.gsub(/(\w+)-src/, "\\1_src")
169
+ config = JSON.parse(one_config, :symbolize_names => true)
170
+ combined_config.merge(config) do |_, lhs, rhs|
171
+ lhs | rhs
172
+ end
173
+ end
174
+ end
175
+
167
176
  private
168
177
 
169
178
  def add_script_hashes
@@ -175,21 +184,10 @@ module SecureHeaders
175
184
  append_http_additions unless ssl_request?
176
185
  header_value = [
177
186
  generic_directives,
178
- non_default_directives,
179
187
  report_uri_directive
180
188
  ].join.strip
181
189
  end
182
190
 
183
- def fill_directives
184
- if default = @config[:default_src]
185
- DIRECTIVES.each do |directive|
186
- unless @config[directive]
187
- @config[directive] = default
188
- end
189
- end
190
- end
191
- end
192
-
193
191
  def append_http_additions
194
192
  return unless @http_additions
195
193
  @http_additions.each do |k, v|
@@ -200,13 +198,18 @@ module SecureHeaders
200
198
 
201
199
  def translate_dir_value val
202
200
  if %w{inline eval}.include?(val)
201
+ warn "[DEPRECATION] using inline/eval may not be supported in the future. Instead use 'unsafe-inline'/'unsafe-eval' instead."
203
202
  val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
204
- # self/none are special sources/src-dir-values and need to be quoted
205
203
  elsif %{self none}.include?(val)
204
+ warn "[DEPRECATION] using self/none may not be supported in the future. Instead use 'self'/'none' instead."
206
205
  "'#{val}'"
207
206
  elsif val == 'nonce'
208
- self.class.set_nonce(@controller, nonce)
209
- ["'nonce-#{nonce}'", "'unsafe-inline'"]
207
+ if supports_nonces?(@ua)
208
+ self.class.set_nonce(@controller, nonce)
209
+ ["'nonce-#{nonce}'", "'unsafe-inline'"]
210
+ else
211
+ "'unsafe-inline'"
212
+ end
210
213
  else
211
214
  val
212
215
  end
@@ -233,10 +236,11 @@ module SecureHeaders
233
236
 
234
237
  def generic_directives
235
238
  header_value = ''
239
+ data_uri = @disable_img_src_data_uri ? [] : ["data:"]
236
240
  if @config[:img_src]
237
- @config[:img_src] = @config[:img_src] + ['data:'] unless @config[:img_src].include?('data:')
241
+ @config[:img_src] = @config[:img_src] + data_uri unless @config[:img_src].include?('data:')
238
242
  else
239
- @config[:img_src] = @config[:default_src] + ['data:']
243
+ @config[:img_src] = @config[:default_src] + data_uri
240
244
  end
241
245
 
242
246
  DIRECTIVES.each do |directive_name|
@@ -246,17 +250,13 @@ module SecureHeaders
246
250
  header_value
247
251
  end
248
252
 
249
- def non_default_directives
250
- header_value = ''
251
- NON_DEFAULT_SOURCES.each do |directive_name|
252
- header_value += build_directive(directive_name) if @config[directive_name]
253
- end
254
-
255
- header_value
256
- end
257
-
258
253
  def build_directive(key)
259
254
  "#{self.class.symbol_to_hyphen_case(key)} #{@config[key].join(" ")}; "
260
255
  end
256
+
257
+ def supports_nonces?(user_agent)
258
+ parsed_ua = UserAgentParser.parse(user_agent)
259
+ ["Chrome", "Opera", "Firefox"].include?(parsed_ua.family)
260
+ end
261
261
  end
262
262
  end
@@ -6,6 +6,7 @@ module SecureHeaders
6
6
  ENV_KEY = 'secure_headers.public_key_pins'
7
7
  HASH_ALGORITHMS = [:sha256]
8
8
  DIRECTIVES = [:max_age]
9
+ CONFIG_KEY = :hpkp
9
10
  end
10
11
  class << self
11
12
  def symbol_to_hyphen_case sym
@@ -8,6 +8,7 @@ module SecureHeaders
8
8
  DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
9
9
  VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
10
10
  MESSAGE = "The config value supplied for the HSTS header was invalid."
11
+ CONFIG_KEY = :hsts
11
12
  end
12
13
  include Constants
13
14
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  module Constants
6
6
  X_CONTENT_TYPE_OPTIONS_HEADER_NAME = "X-Content-Type-Options"
7
7
  DEFAULT_VALUE = "nosniff"
8
+ CONFIG_KEY = :x_content_type_options
8
9
  end
9
10
  include Constants
10
11
 
@@ -37,4 +38,4 @@ module SecureHeaders
37
38
  end
38
39
  end
39
40
  end
40
- end
41
+ end
@@ -4,6 +4,7 @@ module SecureHeaders
4
4
  module Constants
5
5
  XDO_HEADER_NAME = "X-Download-Options"
6
6
  DEFAULT_VALUE = 'noopen'
7
+ CONFIG_KEY = :x_download_options
7
8
  end
8
9
  include Constants
9
10
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  XFO_HEADER_NAME = "X-Frame-Options"
6
6
  DEFAULT_VALUE = 'SAMEORIGIN'
7
7
  VALID_XFO_HEADER = /\A(SAMEORIGIN\z|DENY\z|ALLOW-FROM[:\s])/i
8
+ CONFIG_KEY = :x_frame_options
8
9
  end
9
10
  include Constants
10
11
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  XPCDP_HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
6
6
  DEFAULT_VALUE = 'none'
7
7
  VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
8
+ CONFIG_KEY = :x_permitted_cross_domain_policies
8
9
  end
9
10
  include Constants
10
11
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  X_XSS_PROTECTION_HEADER_NAME = 'X-XSS-Protection'
6
6
  DEFAULT_VALUE = "1"
7
7
  VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
8
+ CONFIG_KEY = :x_xss_protection
8
9
  end
9
10
  include Constants
10
11
 
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "2.2.0"
2
+ VERSION = "2.4.0"
3
3
  end