secure_headers 2.3.0 → 3.0.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +3 -0
  5. data/CHANGELOG.md +205 -0
  6. data/Gemfile +9 -10
  7. data/Guardfile +12 -0
  8. data/README.md +169 -301
  9. data/Rakefile +2 -36
  10. data/lib/secure_headers/configuration.rb +189 -0
  11. data/lib/secure_headers/headers/content_security_policy.rb +362 -213
  12. data/lib/secure_headers/headers/public_key_pins.rb +43 -58
  13. data/lib/secure_headers/headers/strict_transport_security.rb +21 -48
  14. data/lib/secure_headers/headers/x_content_type_options.rb +18 -32
  15. data/lib/secure_headers/headers/x_download_options.rb +18 -32
  16. data/lib/secure_headers/headers/x_frame_options.rb +24 -33
  17. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -33
  18. data/lib/secure_headers/headers/x_xss_protection.rb +17 -47
  19. data/lib/secure_headers/middleware.rb +15 -0
  20. data/lib/secure_headers/padrino.rb +1 -2
  21. data/lib/secure_headers/railtie.rb +9 -6
  22. data/lib/secure_headers/view_helper.rb +27 -44
  23. data/lib/secure_headers.rb +229 -162
  24. data/secure_headers.gemspec +7 -11
  25. data/spec/lib/secure_headers/configuration_spec.rb +80 -0
  26. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +175 -262
  27. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
  28. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
  29. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +13 -18
  30. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
  31. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
  32. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
  33. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +22 -30
  34. data/spec/lib/secure_headers/middleware_spec.rb +40 -0
  35. data/spec/lib/secure_headers_spec.rb +202 -326
  36. data/spec/spec_helper.rb +32 -28
  37. data/upgrading-to-3-0.md +37 -0
  38. metadata +14 -94
  39. data/fixtures/rails_3_2_12/.rspec +0 -1
  40. data/fixtures/rails_3_2_12/Gemfile +0 -6
  41. data/fixtures/rails_3_2_12/README.rdoc +0 -261
  42. data/fixtures/rails_3_2_12/Rakefile +0 -7
  43. data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +0 -4
  44. data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +0 -5
  45. data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -5
  46. data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
  47. data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +0 -11
  48. data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +0 -2
  49. data/fixtures/rails_3_2_12/app/views/things/index.html.erb +0 -1
  50. data/fixtures/rails_3_2_12/config/application.rb +0 -14
  51. data/fixtures/rails_3_2_12/config/boot.rb +0 -6
  52. data/fixtures/rails_3_2_12/config/environment.rb +0 -5
  53. data/fixtures/rails_3_2_12/config/environments/test.rb +0 -37
  54. data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +0 -17
  55. data/fixtures/rails_3_2_12/config/routes.rb +0 -4
  56. data/fixtures/rails_3_2_12/config/script_hashes.yml +0 -5
  57. data/fixtures/rails_3_2_12/config.ru +0 -7
  58. data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
  59. data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
  60. data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
  61. data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +0 -83
  62. data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +0 -54
  63. data/fixtures/rails_3_2_12/spec/spec_helper.rb +0 -15
  64. data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
  65. data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
  66. data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
  67. data/fixtures/rails_3_2_12_no_init/.rspec +0 -1
  68. data/fixtures/rails_3_2_12_no_init/Gemfile +0 -5
  69. data/fixtures/rails_3_2_12_no_init/README.rdoc +0 -261
  70. data/fixtures/rails_3_2_12_no_init/Rakefile +0 -7
  71. data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +0 -4
  72. data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -20
  73. data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +0 -5
  74. data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
  75. data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -12
  76. data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +0 -1
  77. data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -0
  78. data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -17
  79. data/fixtures/rails_3_2_12_no_init/config/boot.rb +0 -6
  80. data/fixtures/rails_3_2_12_no_init/config/environment.rb +0 -5
  81. data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +0 -37
  82. data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -4
  83. data/fixtures/rails_3_2_12_no_init/config.ru +0 -4
  84. data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
  85. data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
  86. data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
  87. data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
  88. data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +0 -54
  89. data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +0 -5
  90. data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
  91. data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
  92. data/fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep +0 -0
  93. data/fixtures/rails_4_1_8/Gemfile +0 -5
  94. data/fixtures/rails_4_1_8/README.rdoc +0 -28
  95. data/fixtures/rails_4_1_8/Rakefile +0 -6
  96. data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
  97. data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
  98. data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
  99. data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
  100. data/fixtures/rails_4_1_8/app/models/.keep +0 -0
  101. data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
  102. data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
  103. data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
  104. data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
  105. data/fixtures/rails_4_1_8/config/application.rb +0 -15
  106. data/fixtures/rails_4_1_8/config/boot.rb +0 -4
  107. data/fixtures/rails_4_1_8/config/environment.rb +0 -5
  108. data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
  109. data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -17
  110. data/fixtures/rails_4_1_8/config/routes.rb +0 -4
  111. data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
  112. data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
  113. data/fixtures/rails_4_1_8/config.ru +0 -4
  114. data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
  115. data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
  116. data/fixtures/rails_4_1_8/log/.keep +0 -0
  117. data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
  118. data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
  119. data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
  120. data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
  121. data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
  122. data/lib/secure_headers/hash_helper.rb +0 -7
  123. data/lib/secure_headers/header.rb +0 -5
  124. data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
  125. data/lib/secure_headers/version.rb +0 -3
  126. data/lib/tasks/tasks.rake +0 -48
  127. data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -47
data/README.md CHANGED
@@ -1,211 +1,202 @@
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)
1
+ # Secure Headers [![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
+
3
+
4
+ **The 3.x branch was recently merged**. See the [upgrading to 3.x doc](upgrading-to-3-0.md) for instructions on how to upgrade including the differences and benefits of using the 3.x branch.
5
+
6
+ **The [2.x branch](https://github.com/twitter/secureheaders/tree/2.x) will be maintained**. The documentation below only applies to the 3.x branch. See the 2.x [README](https://github.com/twitter/secureheaders/blob/2.x/README.md) for the old way of doing things.
2
7
 
3
8
  The gem will automatically apply several headers that are related to security. This includes:
4
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/)
5
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)
6
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)
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
- - 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)
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
+ - X-Content-Type-Options - [Prevent content type sniffing](https://msdn.microsoft.com/library/gg622941\(v=vs.85\).aspx)
14
+ - X-Download-Options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
10
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)
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)
12
-
13
- ## Usage
14
-
15
- - `ensure_security_headers` in a controller will set security-related headers automatically based on the configuration below.
16
-
17
- ### Disabling
18
-
19
- Use the standard `skip_before_filter :filter_name, options` mechanism. e.g. `skip_before_filter :set_csp_header, :only => :tinymce_page`
20
-
21
- The following methods are going to be called, unless they are provided in a `skip_before_filter` block.
16
+ - 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)
22
17
 
23
- * `:set_csp_header`
24
- * `:set_hsts_header`
25
- * `:set_hpkp_header`
26
- * `:set_x_frame_options_header`
27
- * `:set_x_xss_protection_header`
28
- * `:set_x_content_type_options_header`
29
- * `:set_x_download_options_header`
30
- * `:set_x_permitted_cross_domain_policies_header`
18
+ `secure_headers` is a library with a global config, per request overrides, and rack middleware that enables you customize your application settings.
31
19
 
32
- ### Bonus Features
20
+ ## Use
33
21
 
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.
22
+ `gem install secure_headers`
37
23
 
38
24
  ## Configuration
39
25
 
40
- **Place the following in an initializer (recommended):**
26
+ If you do not supply a `default` configuration, exceptions will be raised. If you would like to use a default configuration (which is fairly locked down), just call `SecureHeaders::Configuration.default` without any arguments or block.
27
+
28
+ All `nil` values will fallback to their default values. `SecureHeaders::OPT_OUT` will disable the header entirely.
41
29
 
42
30
  ```ruby
43
- ::SecureHeaders::Configuration.configure do |config|
44
- config.hsts = {:max_age => 20.years.to_i, :include_subdomains => true}
45
- config.x_frame_options = 'DENY'
31
+ SecureHeaders::Configuration.default do |config|
32
+ config.hsts = "max-age=#{20.years.to_i}"
33
+ config.x_frame_options = "DENY"
46
34
  config.x_content_type_options = "nosniff"
47
- config.x_xss_protection = {:value => 1, :mode => 'block'}
48
- config.x_download_options = 'noopen'
49
- config.x_permitted_cross_domain_policies = 'none'
35
+ config.x_xss_protection = "1; mode=block"
36
+ config.x_download_options = "noopen"
37
+ config.x_permitted_cross_domain_policies = "none"
50
38
  config.csp = {
51
- :default_src => "https: self",
52
- :enforce => proc {|controller| controller.current_user.enforce_csp? },
53
- :frame_src => "https: http:.twimg.com http://itunes.apple.com",
54
- :img_src => "https:",
55
- :report_uri => '//example.com/uri-directive'
39
+ # "meta" values. these will shaped the header, but the values are not included in the header.
40
+ report_only: true, # default: false
41
+ preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
42
+
43
+ # directive values: these values will directly translate into source directives
44
+ default_src: %w(https: 'self'),
45
+ frame_src: %w('self' *.twimg.com itunes.apple.com),
46
+ connect_src: %w(wws:),
47
+ font_src: %w('self' data:),
48
+ img_src: %w(mycdn.com data:),
49
+ media_src: %w(utoob.com),
50
+ object_src: %w('self'),
51
+ script_src: %w('self'),
52
+ style_src: %w('unsafe-inline'),
53
+ base_uri: %w('self'),
54
+ child_src: %w('self'),
55
+ form_action: %w('self' github.com),
56
+ frame_ancestors: %w('none'),
57
+ plugin_types: %w(application/x-shockwave-flash),
58
+ block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
59
+ report_uri: %w(https://example.com/uri-directive)
56
60
  }
57
61
  config.hpkp = {
58
- :max_age => 60.days.to_i,
59
- :include_subdomains => true,
60
- :report_uri => '//example.com/uri-directive',
61
- :pins => [
62
- {:sha256 => 'abc'},
63
- {:sha256 => '123'}
62
+ report_only: false,
63
+ max_age: 60.days.to_i,
64
+ include_subdomains: true,
65
+ report_uri: "https://example.com/uri-directive",
66
+ pins: [
67
+ {sha256: "abc"},
68
+ {sha256: "123"}
64
69
  ]
65
70
  }
66
71
  end
67
-
68
- # and then include this in application_controller.rb
69
- class ApplicationController < ActionController::Base
70
- ensure_security_headers
71
- end
72
72
  ```
73
73
 
74
- Or do the config as a parameter to `ensure_security_headers`
74
+ ### rails 2
75
+
76
+ 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.
75
77
 
76
78
  ```ruby
77
- ensure_security_headers(
78
- :hsts => {:include_subdomains => true, :max_age => 20.years.to_i},
79
- :x_frame_options => 'DENY',
80
- :csp => false
81
- )
79
+ use SecureHeaders::Middleware
82
80
  ```
83
81
 
84
- ## Options for ensure\_security\_headers
82
+ ## Default values
83
+
84
+ All headers except for PublicKeyPins have a default value. See the [corresponding classes for their defaults](https://github.com/twitter/secureheaders/tree/master/lib/secure_headers/headers).
85
85
 
86
- **To disable any of these headers, supply a value of false (e.g. :hsts => false), supplying nil will set the default value**
86
+ ## Named overrides
87
87
 
88
- Each header configuration can take a hash, or a string, or both. If a string
89
- is provided, that value is inserted verbatim. If a hash is supplied, a
90
- header will be constructed using the supplied options.
88
+ Named overrides serve two purposes:
91
89
 
92
- ### The Easy Headers
90
+ * To be able to refer to a configuration by simple name.
91
+ * By precomputing the headers for a named configuration, the headers generated once and reused over every request.
93
92
 
94
- This configuration will likely work for most applications without modification.
93
+ To use a named override, drop a `SecureHeaders::Configuration.override` block **outside** of method definitions and then declare which named override you'd like to use. You can even override an override.
95
94
 
96
95
  ```ruby
97
- :hsts => {:max_age => 631138519, :include_subdomains => false}
98
- :x_frame_options => {:value => 'SAMEORIGIN'}
99
- :x_xss_protection => {:value => 1, :mode => 'block'} # set the :mode option to false to use "warning only" mode
100
- :x_content_type_options => {:value => 'nosniff'}
101
- :x_download_options => {:value => 'noopen'}
102
- :x_permitted_cross_domain_policies => {:value => 'none'}
96
+ class ApplicationController < ActionController::Base
97
+ SecureHeaders::Configuration.default do |config|
98
+ config.csp = {
99
+ default_src: %w('self'),
100
+ script_src: %w(example.org)
101
+ }
102
+ end
103
+
104
+ # override default configuration
105
+ SecureHeaders::Configuration.override(:script_from_otherdomain_com) do |config|
106
+ config.csp[:script_src] << "otherdomain.com"
107
+ end
108
+
109
+ # overrides the :script_from_otherdomain_com configuration
110
+ SecureHeaders::Configuration.override(:another_config, :script_from_otherdomain_com) do |config|
111
+ config.csp[:script_src] << "evenanotherdomain.com"
112
+ end
113
+ end
114
+
115
+ class MyController < ApplicationController
116
+ def index
117
+ # Produces default-src 'self'; script-src example.org otherdomain.org
118
+ use_secure_headers_override(:script_from_otherdomain_com)
119
+ end
120
+
121
+ def show
122
+ # Produces default-src 'self'; script-src example.org otherdomain.org evenanotherdomain.com
123
+ use_secure_headers_override(:another_config)
124
+ end
125
+ end
103
126
  ```
104
127
 
105
- ### Content Security Policy (CSP)
128
+ By default, a noop configuration is provided. No headers will be set when this default override is used.
106
129
 
107
130
  ```ruby
108
- :csp => {
109
- :enforce => false, # sets header to report-only, by default
110
- # default_src is required!
111
- :default_src => nil, # sets the default-src/allow+options directives
112
-
113
- # 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
114
- :report_uri => '//mysite.example.com',
115
-
116
- # these directives all take 'none', 'self', or a globbed pattern
117
- :img_src => nil,
118
- :frame_src => nil,
119
- :connect_src => nil,
120
- :font_src => nil,
121
- :media_src => nil,
122
- :object_src => nil,
123
- :style_src => nil,
124
- :script_src => nil,
125
-
126
- # http additions will be appended to the various directives when
127
- # over http, relaxing the policy
128
- # e.g.
129
- # :csp => {
130
- # :img_src => 'https:',
131
- # :http_additions => {:img_src => 'http'}
132
- # }
133
- # would produce the directive: "img-src https: http:;"
134
- # when over http, ignored for https requests
135
- :http_additions => {}
136
- }
131
+ class MyController < ApplicationController
132
+ def index
133
+ SecureHeaders::opt_out_of_all_protection(request)
134
+ end
135
+ end
137
136
  ```
138
137
 
139
- ### Example CSP header config
138
+ ## Per-action configuration
140
139
 
140
+ You can override the settings for a given action by producing a temporary override. Be aware that because of the dynamic nature of the value, the header values will be computed per request.
141
141
 
142
142
  ```ruby
143
- # most basic example
144
- :csp => {
145
- :default_src => "https: inline eval",
146
- :report_uri => '/uri-directive'
147
- }
148
-
149
- > "default-src 'unsafe-inline' 'unsafe-eval' https:; report-uri /uri-directive;"
143
+ # Given a config of:
144
+ ::SecureHeaders::Configuration.default do |config|
145
+ config.csp = {
146
+ default_src: %w('self'),
147
+ script_src: %w('self')
148
+ }
149
+ end
150
150
 
151
- # turn off inline scripting/eval
152
- :csp => {
153
- :default_src => 'https:',
154
- :report_uri => '/uri-directive'
155
- }
151
+ class MyController < ApplicationController
152
+ def index
153
+ # Append value to the source list, override 'none' values
154
+ # Produces: default-src 'self'; script-src 'self' s3.amazaonaws.com; object-src 'self' youtube.com
155
+ append_content_security_policy_directives(script_src: %w(s3.amazaonaws.com), object_src: %w('self' youtube.com))
156
156
 
157
- > "default-src https:; report-uri /uri-directive;"
157
+ # Overrides the previously set source list, override 'none' values
158
+ # Produces: default-src 'self'; script-src s3.amazaonaws.com; object-src 'self'
159
+ override_content_security_policy_directives(script_src: %w(s3.amazaonaws.com), object_src: %w('self'))
158
160
 
159
- # 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
- :csp => {
161
- :default_src => 'self',
162
- :img_src => '*',
163
- :object_src => ['media1.com', 'media2.com', '*.cdn.com'],
164
- # alternatively (NOT csv) :object_src => 'media1.com media2.com *.cdn.com'
165
- :script_src => 'trustedscripts.example.com'
166
- }
167
- "default-src 'self'; img-src *; object-src media1.com media2.com *.cdn.com; script-src trustedscripts.example.com;"
161
+ # Global settings default to "sameorigin"
162
+ override_x_frame_options("DENY")
163
+ end
168
164
  ```
169
165
 
170
- ### Tagging Requests
166
+ The following methods are available as controller instance methods. They are also available as class methods, but require you to pass in the `request` object.
167
+ * `append_content_security_policy_directives(hash)`: appends each value to the corresponding CSP app-wide configuration.
168
+ * `override_content_security_policy_directives(hash)`: merges the hash into the app-wide configuration, overwriting any previous config
169
+ * `override_x_frame_options(value)`: sets the `X-Frame-Options header` to `value`
171
170
 
172
- It's often valuable to send extra information in the report uri that is not available in the reports themselves. Namely, "was the policy enforced" and "where did the report come from"
171
+ ## Appending / overriding Content Security Policy
173
172
 
174
- ```ruby
175
- {
176
- :tag_report_uri => true,
177
- :enforce => true,
178
- :app_name => 'twitter',
179
- :report_uri => 'csp_reports'
180
- }
181
- ```
173
+ When manipulating content security policy, there are a few things to consider. The default header value is `default-src https:` which corresponds to a default configuration of `{ default_src: %w(https:)}`.
182
174
 
183
- Results in
184
- ```
185
- report-uri csp_reports?enforce=true&app_name=twitter
186
- ```
175
+ #### Append to the policy with a directive other than `default_src`
187
176
 
188
- ### CSP Level 2 features
177
+ The value of `default_src` is joined with the addition. Note the `https:` is carried over from the `default-src` config. If you do not want this, use `override_content_security_policy_directives` instead. To illustrate:
178
+
179
+ ```ruby
180
+ ::SecureHeaders::Configuration.default do |config|
181
+ config.csp = {
182
+ default_src: %w('self')
183
+ }
184
+ end
185
+ ```
189
186
 
190
- *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.*
187
+ Code | Result
188
+ ------------- | -------------
189
+ `append_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src 'self'; script-src 'self' mycdn.com`
190
+ `override_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src 'self'; script-src mycdn.com`
191
191
 
192
192
  #### Nonce
193
193
 
194
- 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.
194
+ script/style-nonce can be used to whitelist inline content. To do this, call the SecureHeaders::content_security_policy_nonce then set the nonce attributes on the various tags.
195
195
 
196
196
  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.
197
197
 
198
- ```ruby
199
- :csp => {
200
- :default_src => 'self',
201
- :script_src => 'self nonce'
202
- }
203
- ```
204
-
205
- > content-security-policy: default-src 'self'; script-src 'self' 'nonce-abc123' 'unsafe-inline'
206
-
207
198
  ```erb
208
- <script nonce="<%= @content_security_policy_nonce %>">
199
+ <script nonce="<%= content_security_policy_nonce %>">
209
200
  console.log("whitelisted, will execute")
210
201
  </script>
211
202
 
@@ -217,12 +208,19 @@ Setting a nonce will also set 'unsafe-inline' for browsers that don't support no
217
208
  console.log("won't execute, not whitelisted")
218
209
  </script>
219
210
  ```
211
+
220
212
  You can use a view helper to automatically add nonces to script tags:
213
+
221
214
  ```erb
222
215
  <%= nonced_javascript_tag do %>
223
- console.log("nonced!")
216
+ console.log("hai");
217
+ <% end %>
218
+
219
+ <%= nonced_style_tag do %>
220
+ body {
221
+ background-color: black;
222
+ }
224
223
  <% end %>
225
- <%= nonced_javascript_tag("nonced without a block!") %>
226
224
  ```
227
225
 
228
226
  becomes:
@@ -231,90 +229,22 @@ becomes:
231
229
  <script nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
232
230
  console.log("nonced!")
233
231
  </script>
232
+ <style nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
233
+ body {
234
+ background-color: black;
235
+ }
236
+ </style>
234
237
  ```
235
238
 
236
239
  #### Hash
237
240
 
238
- 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.
239
-
240
- 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.
241
-
242
- If you only have a few hashes, you can hardcode them for the entire app:
243
-
244
- ```ruby
245
- config.csp = {
246
- :default_src => "https:",
247
- :script_src => 'self'
248
- :script_hashes => ['sha1-abc', 'sha1-qwe']
249
- }
250
- ```
251
-
252
- The following will work as well, but may not be as clear:
253
-
254
- ```ruby
255
- config.csp = {
256
- :default_src => "https:",
257
- :script_src => "self 'sha1-qwe'"
258
- }
259
- ```
260
-
261
- 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:
262
-
263
- ```ruby
264
- # config.ru
265
- require 'secure_headers/headers/content_security_policy/script_hash_middleware'
266
- use ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware
267
- ```
268
-
269
- ```ruby
270
- config.csp = {
271
- :default_src => "https:",
272
- :script_src => 'self',
273
- :script_hash_middleware => true
274
- }
275
- ```
276
-
277
- 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:
278
-
279
- ```$ bundle exec rake secure_headers:generate_hashes```
280
-
281
- Which will generate something like:
282
-
283
- ```yaml
284
- # config/script_hashes.yml
285
- app/views/layouts/application.html.erb:
286
- - sha256-l8OLjZqYRnKilpdE0VosRMvhdYArjXT4NZaK2p7QVvs=
287
- app/templates/articles/edit.html.erb:
288
- - sha256-+7mij1/uCwtCQRWrof2NmOln5qX+5WdVwTLMpi8nuoA=
289
- - sha256-Ny4TRIhhFpnYnSeKC274P6bfAz4TOkezLabavIAU4dA=
290
- - sha256-I5e58Gqbu4WpO9dck18QxO7aYOHKrELIi70it4jIPi0=
291
- - sha256-Po4LMynwnAJHxiTp3DQaQ3YDBj3paN/xrDoKl4OyxY4=
292
- ```
293
-
294
- In this example, if we visit /articles/edit/[id], the above hashes will automatically be added to the CSP header's
295
- script-src value!
296
-
297
- You can use plain "script" tags or you can use a built-in helper:
298
-
299
- ```erb
300
- <%= hashed_javascript_tag do %>
301
- console.log("hashed automatically!")
302
- <% end %>
303
- ```
304
-
305
- 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:
306
-
307
- ```erb
308
- <%= hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
309
- console.log("will raise an exception if not in script_hashes.yml!")
310
- <% end %>
311
- ```
241
+ The hash feature has been removed, for now.
312
242
 
313
243
  ### Public Key Pins
314
244
 
315
245
  Be aware that pinning error reporting is governed by the same rules as everything else. If you have a pinning failure that tries to report back to the same origin, by definition this will not work.
316
246
 
317
- ```
247
+ ```ruby
318
248
  config.hpkp = {
319
249
  max_age: 60.days.to_i, # max_age is a required parameter
320
250
  include_subdomains: true, # whether or not to apply pins to subdomains
@@ -323,7 +253,7 @@ config.hpkp = {
323
253
  {sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
324
254
  {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
325
255
  ],
326
- enforce: true, # defaults to false (report-only mode)
256
+ report_only: true, # defaults to false (report-only mode)
327
257
  report_uri: '//example.com/uri-directive',
328
258
  app_name: 'example',
329
259
  tag_report_uri: true
@@ -340,28 +270,17 @@ require 'sinatra'
340
270
  require 'haml'
341
271
  require 'secure_headers'
342
272
 
343
- ::SecureHeaders::Configuration.configure do |config|
344
- config.hsts = {:max_age => 99, :include_subdomains => true}
345
- config.x_frame_options = 'DENY'
346
- config.x_content_type_options = "nosniff"
347
- config.x_xss_protection = {:value => 1, :mode => false}
348
- config.x_download_options = 'noopen'
349
- config.x_permitted_cross_domain_policies = 'none'
350
- config.csp = {
351
- :default_src => "https: inline eval",
352
- :report_uri => '//example.com/uri-directive',
353
- :img_src => "https: data:",
354
- :frame_src => "https: http:.twimg.com http://itunes.apple.com"
355
- }
356
- config.hpkp = false
273
+ use SecureHeaders::Middleware
274
+
275
+ SecureHeaders::Configuration.default do |config|
276
+ ...
357
277
  end
358
278
 
359
279
  class Donkey < Sinatra::Application
360
- include SecureHeaders
361
280
  set :root, APP_ROOT
362
281
 
363
282
  get '/' do
364
- set_csp_header
283
+ SecureHeaders.override_x_frame_options(SecureHeaders::OPT_OUT)
365
284
  haml :index
366
285
  end
367
286
  end
@@ -374,12 +293,13 @@ You can use SecureHeaders for Padrino applications as well:
374
293
  In your `Gemfile`:
375
294
 
376
295
  ```ruby
377
- gem "secure_headers", :require => 'secure_headers'
296
+ gem "secure_headers", require: 'secure_headers'
378
297
  ```
379
298
 
380
299
  then in your `app.rb` file you can:
381
300
 
382
301
  ```ruby
302
+ Padrino.use(SecureHeaders::Middleware)
383
303
  require 'secure_headers/padrino'
384
304
 
385
305
  module Web
@@ -387,7 +307,6 @@ module Web
387
307
  register SecureHeaders::Padrino
388
308
 
389
309
  get '/' do
390
- set_csp_header
391
310
  render 'index'
392
311
  end
393
312
  end
@@ -398,73 +317,22 @@ and in `config/boot.rb`:
398
317
 
399
318
  ```ruby
400
319
  def before_load
401
- ::SecureHeaders::Configuration.configure do |config|
402
- config.hsts = {:max_age => 99, :include_subdomains => true}
403
- config.x_frame_options = 'DENY'
404
- config.x_content_type_options = "nosniff"
405
- config.x_xss_protection = {:value => '1', :mode => false}
406
- config.x_download_options = 'noopen'
407
- config.x_permitted_cross_domain_policies = 'none'
408
- config.csp = {
409
- :default_src => "https: inline eval",
410
- :report_uri => '//example.com/uri-directive',
411
- :img_src => "https: data:",
412
- :frame_src => "https: http:.twimg.com http://itunes.apple.com"
413
- }
414
- end
415
- end
416
- ```
417
-
418
- ### Using in rack middleware
419
-
420
- The `SecureHeaders::header_hash` generates a hash of all header values, which is useful for merging with rack middleware values.
421
-
422
- ```ruby
423
- class MySecureHeaders
424
- include SecureHeaders
425
- def initialize(app)
426
- @app = app
427
- end
428
-
429
- def call(env)
430
- status, headers, response = @app.call(env)
431
- security_headers = if override?
432
- SecureHeaders::header_hash(:csp => false) # uses global config, but overrides CSP config
433
- else
434
- SecureHeaders::header_hash # uses global config
435
- end
436
- [status, headers.merge(security_headers), [response.body]]
437
- end
438
- end
439
-
440
- module Testapp
441
- class Application < Rails::Application
442
- config.middleware.use MySecureHeaders
320
+ SecureHeaders::Configuration.default do |config|
321
+ ...
443
322
  end
444
323
  end
445
324
  ```
446
325
 
447
326
  ## Similar libraries
448
327
 
449
- * Rack [rack-secure_headers](https://github.com/harmoni/rack-secure_headers)
450
- * Node.js (express) [helmet](https://github.com/evilpacket/helmet) and [hood](https://github.com/seanmonstar/hood)
328
+ * Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
329
+ * Node.js (express) [helmet](https://github.com/helmetjs/helmet) and [hood](https://github.com/seanmonstar/hood)
451
330
  * Node.js (hapi) [blankie](https://github.com/nlf/blankie)
452
331
  * J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
453
332
  * ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
454
333
  * Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
455
334
  * Go - [secureheader](https://github.com/kr/secureheader)
456
-
457
- ## Authors
458
-
459
- * Neil Matatall [@ndm](https://twitter.com/ndm) - primary author.
460
- * Nicholas Green [@nickgreen](https://twitter.com/nickgreen) - code contributions, main reviewer.
461
-
462
- ## Acknowledgements
463
-
464
- * Justin Collins [@presidentbeef](https://twitter.com/presidentbeef) & Jim O'Leary [@jimio](https://twitter.com/jimio) for reviews.
465
- * Ian Melven [@imelven](https://twitter.com/imelven) - Discussions/info about CSP in general, made us aware of the [userCSP](https://addons.mozilla.org/en-US/firefox/addon/newusercspdesign/) Mozilla extension.
466
- * Sumit Shah [@omnidactyl](https://twitter.com/omnidactyl) - For being an eager guinea pig.
467
- * Chris Aniszczyk [@cra](https://twitter.com/cra) - For running an awesome open source program at Twitter.
335
+ * Elixir [secure_headers](https://github.com/anotherhale/secure_headers)
468
336
 
469
337
  ## License
470
338
 
data/Rakefile CHANGED
@@ -7,44 +7,10 @@ require 'net/https'
7
7
  desc "Run RSpec"
8
8
  RSpec::Core::RakeTask.new do |t|
9
9
  t.verbose = false
10
- t.rspec_opts = "--format progress"
10
+ t.rspec_opts = "--format progress"
11
11
  end
12
12
 
13
- task :default => :all_spec
14
-
15
- desc "Run all specs, and test fixture apps"
16
- task :all_spec => :spec do
17
- pwd = Dir.pwd
18
- Dir.chdir 'fixtures/rails_3_2_12'
19
- puts Dir.pwd
20
- str = `bundle install >> /dev/null; bundle exec rspec spec`
21
- puts str
22
- unless $? == 0
23
- Dir.chdir pwd
24
- fail "Header tests with app not using initializer failed exit code: #{$?}"
25
- end
26
-
27
- Dir.chdir pwd
28
- Dir.chdir 'fixtures/rails_3_2_12_no_init'
29
- puts Dir.pwd
30
- puts `bundle install >> /dev/null; bundle exec rspec spec`
31
-
32
- unless $? == 0
33
- fail "Header tests with app not using initializer failed"
34
- Dir.chdir pwd
35
- end
36
-
37
- Dir.chdir pwd
38
- Dir.chdir 'fixtures/rails_4_1_8'
39
- puts Dir.pwd
40
- puts `bundle install >> /dev/null; bundle exec rspec spec`
41
-
42
- unless $? == 0
43
- fail "Header tests with Rails 4 failed"
44
- Dir.chdir pwd
45
- end
46
-
47
- end
13
+ task default: :spec
48
14
 
49
15
  begin
50
16
  require 'rdoc/task'