secure_headers 2.5.3 → 3.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +2 -1
- data/Gemfile +9 -16
- data/README.md +154 -331
- data/Rakefile +2 -36
- data/lib/secure_headers/configuration.rb +189 -0
- data/lib/secure_headers/headers/content_security_policy.rb +341 -254
- data/lib/secure_headers/headers/public_key_pins.rb +43 -58
- data/lib/secure_headers/headers/strict_transport_security.rb +21 -49
- data/lib/secure_headers/headers/x_content_type_options.rb +18 -33
- data/lib/secure_headers/headers/x_download_options.rb +18 -33
- data/lib/secure_headers/headers/x_frame_options.rb +24 -34
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -34
- data/lib/secure_headers/headers/x_xss_protection.rb +17 -48
- data/lib/secure_headers/middleware.rb +15 -0
- data/lib/secure_headers/padrino.rb +1 -2
- data/lib/secure_headers/railtie.rb +9 -6
- data/lib/secure_headers/view_helper.rb +27 -43
- data/lib/secure_headers.rb +254 -61
- data/secure_headers.gemspec +7 -12
- data/spec/lib/secure_headers/configuration_spec.rb +80 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +111 -276
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +11 -18
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -30
- data/spec/lib/secure_headers/middleware_spec.rb +40 -0
- data/spec/lib/secure_headers_spec.rb +201 -339
- data/spec/spec_helper.rb +30 -30
- data/upgrading-to-3-0.md +35 -0
- metadata +14 -100
- data/fixtures/rails_3_2_22/.rspec +0 -1
- data/fixtures/rails_3_2_22/Gemfile +0 -6
- data/fixtures/rails_3_2_22/README.rdoc +0 -261
- data/fixtures/rails_3_2_22/Rakefile +0 -7
- data/fixtures/rails_3_2_22/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_22/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_3_2_22/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_22/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_3_2_22/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_3_2_22/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_3_2_22/config/application.rb +0 -14
- data/fixtures/rails_3_2_22/config/boot.rb +0 -6
- data/fixtures/rails_3_2_22/config/environment.rb +0 -5
- data/fixtures/rails_3_2_22/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_22/config/initializers/secure_headers.rb +0 -16
- data/fixtures/rails_3_2_22/config/routes.rb +0 -4
- data/fixtures/rails_3_2_22/config/script_hashes.yml +0 -5
- data/fixtures/rails_3_2_22/config.ru +0 -7
- data/fixtures/rails_3_2_22/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_22/spec/spec_helper.rb +0 -15
- data/fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/.rspec +0 -1
- data/fixtures/rails_3_2_22_no_init/Gemfile +0 -6
- data/fixtures/rails_3_2_22_no_init/README.rdoc +0 -261
- data/fixtures/rails_3_2_22_no_init/Rakefile +0 -7
- data/fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +0 -20
- data/fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb +0 -12
- data/fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb +0 -1
- data/fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb +0 -0
- data/fixtures/rails_3_2_22_no_init/config/application.rb +0 -17
- data/fixtures/rails_3_2_22_no_init/config/boot.rb +0 -6
- data/fixtures/rails_3_2_22_no_init/config/environment.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_22_no_init/config/routes.rb +0 -4
- data/fixtures/rails_3_2_22_no_init/config.ru +0 -4
- data/fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
- data/fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_22_no_init/spec/spec_helper.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_4_1_8/Gemfile +0 -5
- data/fixtures/rails_4_1_8/README.rdoc +0 -28
- data/fixtures/rails_4_1_8/Rakefile +0 -6
- data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_4_1_8/app/models/.keep +0 -0
- data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_4_1_8/config/application.rb +0 -15
- data/fixtures/rails_4_1_8/config/boot.rb +0 -4
- data/fixtures/rails_4_1_8/config/environment.rb +0 -5
- data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
- data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -16
- data/fixtures/rails_4_1_8/config/routes.rb +0 -4
- data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
- data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
- data/fixtures/rails_4_1_8/config.ru +0 -4
- data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
- data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
- data/fixtures/rails_4_1_8/log/.keep +0 -0
- data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
- data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
- data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
- data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
- data/lib/secure_headers/controller_extension.rb +0 -158
- data/lib/secure_headers/hash_helper.rb +0 -7
- data/lib/secure_headers/header.rb +0 -5
- data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
- data/lib/secure_headers/version.rb +0 -3
- data/lib/tasks/tasks.rake +0 -48
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -46
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
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
|
+
**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.
|
4
|
+
|
5
|
+
**The [2.x branch](https://github.com/twitter/secureheaders/tree/2.x) will be maintained**. The documentation below only applies to the 2.x branch. See the 2.x [README](https://github.com/twitter/secureheaders/blob/2.x/README.md) for the old way of doing things.
|
6
|
+
|
3
7
|
The gem will automatically apply several headers that are related to security. This includes:
|
4
8
|
- 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
9
|
- 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)
|
@@ -8,245 +12,188 @@ The gem will automatically apply several headers that are related to security.
|
|
8
12
|
- X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
|
9
13
|
- X-Download-Options - [Prevent file downloads opening](http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx)
|
10
14
|
- 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 Authorities. [Public Key Pinning
|
12
|
-
|
13
|
-
## Usage
|
14
|
-
|
15
|
-
- `ensure_security_headers` in a controller will set security-related headers automatically based on the configuration below.
|
15
|
+
- 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)
|
16
16
|
|
17
|
-
|
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.
|
22
|
-
|
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`
|
17
|
+
`secure_headers` is a library with a global config, per request overrides, and rack milddleware that enables you customize your application settings.
|
31
18
|
|
32
19
|
## Configuration
|
33
20
|
|
34
|
-
|
21
|
+
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.
|
35
22
|
|
36
|
-
|
23
|
+
All `nil` values will fallback to their default value. `SecureHeaders::OPT_OUT` will disable the header entirely.
|
37
24
|
|
38
25
|
```ruby
|
39
|
-
|
40
|
-
config.hsts =
|
41
|
-
config.x_frame_options =
|
26
|
+
SecureHeaders::Configuration.default do |config|
|
27
|
+
config.hsts = 20.years.to_i.to_s
|
28
|
+
config.x_frame_options = "DENY"
|
42
29
|
config.x_content_type_options = "nosniff"
|
43
|
-
config.x_xss_protection =
|
44
|
-
config.x_download_options =
|
45
|
-
config.x_permitted_cross_domain_policies =
|
30
|
+
config.x_xss_protection = "1; mode=block"
|
31
|
+
config.x_download_options = "noopen"
|
32
|
+
config.x_permitted_cross_domain_policies = "none"
|
46
33
|
config.csp = {
|
47
|
-
:
|
48
|
-
:
|
49
|
-
|
50
|
-
:
|
51
|
-
:
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
56
|
-
:
|
57
|
-
:
|
58
|
-
:
|
59
|
-
:
|
60
|
-
:
|
61
|
-
:
|
62
|
-
:
|
63
|
-
:
|
64
|
-
:
|
65
|
-
:report_uri => '//example.com/uri-directive'
|
34
|
+
default_src: %w(https: 'self'),
|
35
|
+
report_only: false,
|
36
|
+
frame_src: %w(*.twimg.com itunes.apple.com),
|
37
|
+
connect_src: %w(wws:),
|
38
|
+
font_src: %w('self' data:),
|
39
|
+
frame_src: %w('self'),
|
40
|
+
img_src: %w(mycdn.com data:),
|
41
|
+
media_src: %w(utoob.com),
|
42
|
+
object_src: %w('self'),
|
43
|
+
script_src: %w('self'),
|
44
|
+
style_src: %w('unsafe-inline'),
|
45
|
+
base_uri: %w('self'),
|
46
|
+
child_src: %w('self'),
|
47
|
+
form_action: %w('self' github.com),
|
48
|
+
frame_ancestors: %w('none'),
|
49
|
+
plugin_types: %w(application/x-shockwave-flash),
|
50
|
+
block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
|
51
|
+
report_uri: %w(https://example.com/uri-directive)
|
66
52
|
}
|
67
53
|
config.hpkp = {
|
68
|
-
:
|
69
|
-
:
|
70
|
-
:
|
71
|
-
:
|
72
|
-
|
73
|
-
{:
|
54
|
+
report_only: false,
|
55
|
+
max_age: 60.days.to_i,
|
56
|
+
include_subdomains: true,
|
57
|
+
report_uri: "https://example.com/uri-directive",
|
58
|
+
pins: [
|
59
|
+
{sha256: "abc"},
|
60
|
+
{sha256: "123"}
|
74
61
|
]
|
75
62
|
}
|
76
63
|
end
|
77
|
-
|
78
|
-
# and then include this in application_controller.rb
|
79
|
-
class ApplicationController < ActionController::Base
|
80
|
-
ensure_security_headers
|
81
|
-
end
|
82
64
|
```
|
83
65
|
|
84
|
-
|
66
|
+
### rails 2
|
67
|
+
|
68
|
+
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.
|
85
69
|
|
86
70
|
```ruby
|
87
|
-
|
88
|
-
:hsts => {:include_subdomains => true, :max_age => 20.years.to_i},
|
89
|
-
:x_frame_options => 'DENY',
|
90
|
-
:csp => false
|
91
|
-
)
|
71
|
+
use SecureHeaders::Middleware
|
92
72
|
```
|
93
73
|
|
94
|
-
##
|
74
|
+
## Default values
|
75
|
+
|
76
|
+
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).
|
77
|
+
|
78
|
+
## Named overrides
|
79
|
+
|
80
|
+
Named overrides serve two purposes:
|
95
81
|
|
96
|
-
|
82
|
+
* To be able to refer to a configuration by simple name.
|
83
|
+
* By precomputing the headers for a named configuration, the headers generated once and reused over every request.
|
97
84
|
|
98
|
-
|
99
|
-
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.
|
100
|
-
1. Override the `secure_header_options_for` class instance method. e.g.
|
85
|
+
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.
|
101
86
|
|
102
87
|
```ruby
|
103
|
-
class
|
104
|
-
|
105
|
-
|
88
|
+
class ApplicationController < ActionController::Base
|
89
|
+
SecureHeaders::Configuration.default do |config|
|
90
|
+
config.csp = {
|
91
|
+
default_src: %w('self'),
|
92
|
+
script_src: %w(example.org)
|
93
|
+
}
|
106
94
|
end
|
107
95
|
|
108
|
-
|
109
|
-
|
96
|
+
# override default configuration
|
97
|
+
SecureHeaders::Configuration.override(:script_from_otherdomain_com) do |config|
|
98
|
+
config.csp[:script_src] << "otherdomain.com"
|
110
99
|
end
|
111
100
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
if header == :csp
|
116
|
-
options.merge(style_src: "'self'")
|
117
|
-
end
|
118
|
-
else
|
119
|
-
options
|
120
|
-
end
|
101
|
+
# overrides the :script_from_otherdomain_com configuration
|
102
|
+
SecureHeaders::Configuration.override(:another_config, :script_from_otherdomain_com) do |config|
|
103
|
+
config.csp[:script_src] << "evenanotherdomain.com"
|
121
104
|
end
|
122
105
|
end
|
123
|
-
```
|
124
|
-
|
125
|
-
## Options for ensure\_security\_headers
|
126
|
-
|
127
|
-
**To disable any of these headers, supply a value of false (e.g. :hsts => false), supplying nil will set the default value**
|
128
|
-
|
129
|
-
Each header configuration can take a hash, or a string, or both. If a string
|
130
|
-
is provided, that value is inserted verbatim. If a hash is supplied, a
|
131
|
-
header will be constructed using the supplied options.
|
132
|
-
|
133
|
-
### The Easy Headers
|
134
106
|
|
135
|
-
|
107
|
+
class MyController < ApplicationController
|
108
|
+
def index
|
109
|
+
# Produces default-src 'self'; script-src example.org otherdomain.org
|
110
|
+
use_secure_headers_override(:script_from_otherdomain_com)
|
111
|
+
end
|
136
112
|
|
137
|
-
|
138
|
-
|
139
|
-
:
|
140
|
-
|
141
|
-
|
142
|
-
:x_download_options => {:value => 'noopen'}
|
143
|
-
:x_permitted_cross_domain_policies => {:value => 'none'}
|
113
|
+
def show
|
114
|
+
# Produces default-src 'self'; script-src example.org otherdomain.org evenanotherdomain.com
|
115
|
+
use_secure_headers_override(:another_config)
|
116
|
+
end
|
117
|
+
end
|
144
118
|
```
|
145
119
|
|
146
|
-
|
120
|
+
By default, a noop configuration is provided. No headers will be set when this default override is used.
|
147
121
|
|
148
122
|
```ruby
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
# 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
|
155
|
-
:report_uri => '//mysite.example.com',
|
156
|
-
|
157
|
-
# these directives all take 'none', 'self', or a globbed pattern
|
158
|
-
:img_src => nil,
|
159
|
-
:frame_src => nil,
|
160
|
-
:connect_src => nil,
|
161
|
-
:font_src => nil,
|
162
|
-
:media_src => nil,
|
163
|
-
:object_src => nil,
|
164
|
-
:style_src => nil,
|
165
|
-
:script_src => nil,
|
166
|
-
|
167
|
-
# http additions will be appended to the various directives when
|
168
|
-
# over http, relaxing the policy
|
169
|
-
# e.g.
|
170
|
-
# :csp => {
|
171
|
-
# :img_src => 'https:',
|
172
|
-
# :http_additions => {:img_src => 'http'}
|
173
|
-
# }
|
174
|
-
# would produce the directive: "img-src https: http:;"
|
175
|
-
# when over http, ignored for https requests
|
176
|
-
:http_additions => {}
|
177
|
-
}
|
123
|
+
class MyController < ApplicationController
|
124
|
+
def index
|
125
|
+
SecureHeaders::opt_out_of_all_protection(request)
|
126
|
+
end
|
127
|
+
end
|
178
128
|
```
|
179
129
|
|
180
|
-
|
130
|
+
## Per-action configuration
|
181
131
|
|
132
|
+
You can override the settings for a given action by producing a temporary override. This approach is not recommended because the header values will be computed per request.
|
182
133
|
|
183
134
|
```ruby
|
184
|
-
#
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
135
|
+
# Given a config of:
|
136
|
+
::SecureHeaders::Configuration.default do |config|
|
137
|
+
config.csp = {
|
138
|
+
default_src: %w('self'),
|
139
|
+
script_src: %w('self')
|
140
|
+
}
|
141
|
+
end
|
191
142
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
143
|
+
class MyController < ApplicationController
|
144
|
+
def index
|
145
|
+
# Append value to the source list, override 'none' values
|
146
|
+
# Produces: default-src 'self'; script-src 'self' s3.amazaonaws.com; object-src 'self' youtube.com
|
147
|
+
append_content_security_policy_directives(script_src: %w(s3.amazaonaws.com), object_src: %w('self' youtube.com))
|
197
148
|
|
198
|
-
|
149
|
+
# Overrides the previously set source list, override 'none' values
|
150
|
+
# Produces: default-src 'self'; script-src s3.amazaonaws.com; object-src 'self'
|
151
|
+
override_content_security_policy_directive(script_src: %w(s3.amazaonaws.com), object_src: %w('self'))
|
199
152
|
|
200
|
-
#
|
201
|
-
|
202
|
-
|
203
|
-
:img_src => '*',
|
204
|
-
:object_src => ['media1.com', 'media2.com', '*.cdn.com'],
|
205
|
-
# alternatively (NOT csv) :object_src => 'media1.com media2.com *.cdn.com'
|
206
|
-
:script_src => 'trustedscripts.example.com'
|
207
|
-
}
|
208
|
-
"default-src 'self'; img-src *; object-src media1.com media2.com *.cdn.com; script-src trustedscripts.example.com;"
|
153
|
+
# Global settings default to "sameorigin"
|
154
|
+
override_x_frame_options("DENY")
|
155
|
+
end
|
209
156
|
```
|
210
157
|
|
211
|
-
|
158
|
+
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.
|
159
|
+
* `append_content_security_policy_directives(hash)`: appends each value to the corresponding CSP app-wide configuration.
|
160
|
+
* `override_content_security_policy_directive(hash)`: merges the hash into the app-wide configuration, overwriting any previous config
|
161
|
+
* `override_x_frame_options(value)`: sets the `X-Frame-Options header` to `value`
|
212
162
|
|
213
|
-
|
163
|
+
## Appending / overriding Content Security Policy
|
214
164
|
|
215
|
-
|
216
|
-
{
|
217
|
-
:tag_report_uri => true,
|
218
|
-
:enforce => true,
|
219
|
-
:app_name => 'twitter',
|
220
|
-
:report_uri => 'csp_reports'
|
221
|
-
}
|
222
|
-
```
|
165
|
+
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:)}`.
|
223
166
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
167
|
+
#### Append to the policy with a directive other than `default_src`
|
168
|
+
|
169
|
+
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:
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
::SecureHeaders::Configuration.configure do |config|
|
173
|
+
config.csp = {
|
174
|
+
default_src: %w('self')
|
175
|
+
}
|
176
|
+
end
|
177
|
+
```
|
228
178
|
|
229
|
-
|
179
|
+
Code | Result
|
180
|
+
------------- | -------------
|
181
|
+
`append_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src 'self'; script-src 'self' mycdn.com`
|
182
|
+
`override_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src 'self'; script-src mycdn.com`
|
230
183
|
|
231
|
-
|
184
|
+
Code | Result
|
185
|
+
------------- | -------------
|
186
|
+
`append_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src https:; script-src https: mycdn.com`
|
187
|
+
`override_content_security_policy_directives(script_src: %w(mycdn.com))` | `default-src https:; script-src mycdn.com`
|
232
188
|
|
233
189
|
#### Nonce
|
234
190
|
|
235
|
-
script/style-nonce can be used to whitelist inline content. To do this,
|
191
|
+
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.
|
236
192
|
|
237
193
|
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.
|
238
194
|
|
239
|
-
```ruby
|
240
|
-
:csp => {
|
241
|
-
:default_src => "'self'",
|
242
|
-
:script_src => "'self' nonce"
|
243
|
-
}
|
244
|
-
```
|
245
|
-
|
246
|
-
> content-security-policy: default-src 'self'; script-src 'self' 'nonce-abc123' 'unsafe-inline'
|
247
|
-
|
248
195
|
```erb
|
249
|
-
<script nonce="<%=
|
196
|
+
<script nonce="<%= content_security_policy_nonce %>">
|
250
197
|
console.log("whitelisted, will execute")
|
251
198
|
</script>
|
252
199
|
|
@@ -258,12 +205,19 @@ Setting a nonce will also set 'unsafe-inline' for browsers that don't support no
|
|
258
205
|
console.log("won't execute, not whitelisted")
|
259
206
|
</script>
|
260
207
|
```
|
208
|
+
|
261
209
|
You can use a view helper to automatically add nonces to script tags:
|
210
|
+
|
262
211
|
```erb
|
263
212
|
<%= nonced_javascript_tag do %>
|
264
|
-
|
213
|
+
console.log("hai");
|
214
|
+
<% end %>
|
215
|
+
|
216
|
+
<%= nonced_style_tag do %>
|
217
|
+
body {
|
218
|
+
background-color: black;
|
219
|
+
}
|
265
220
|
<% end %>
|
266
|
-
<%= nonced_javascript_tag("nonced without a block!") %>
|
267
221
|
```
|
268
222
|
|
269
223
|
becomes:
|
@@ -272,90 +226,22 @@ becomes:
|
|
272
226
|
<script nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
|
273
227
|
console.log("nonced!")
|
274
228
|
</script>
|
229
|
+
<style nonce="/jRAxuLJsDXAxqhNBB7gg7h55KETtDQBXe4ZL+xIXwI=">
|
230
|
+
body {
|
231
|
+
background-color: black;
|
232
|
+
}
|
233
|
+
</style>
|
275
234
|
```
|
276
235
|
|
277
236
|
#### Hash
|
278
237
|
|
279
|
-
|
280
|
-
|
281
|
-
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.
|
282
|
-
|
283
|
-
If you only have a few hashes, you can hardcode them for the entire app:
|
284
|
-
|
285
|
-
```ruby
|
286
|
-
config.csp = {
|
287
|
-
:default_src => "https:",
|
288
|
-
:script_src => "'self'"
|
289
|
-
:script_hashes => ['sha1-abc', 'sha1-qwe']
|
290
|
-
}
|
291
|
-
```
|
292
|
-
|
293
|
-
The following will work as well, but may not be as clear:
|
294
|
-
|
295
|
-
```ruby
|
296
|
-
config.csp = {
|
297
|
-
:default_src => "https:",
|
298
|
-
:script_src => "'self' 'sha1-qwe'"
|
299
|
-
}
|
300
|
-
```
|
301
|
-
|
302
|
-
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:
|
303
|
-
|
304
|
-
```ruby
|
305
|
-
# config.ru
|
306
|
-
require 'secure_headers/headers/content_security_policy/script_hash_middleware'
|
307
|
-
use ::SecureHeaders::ContentSecurityPolicy::ScriptHashMiddleware
|
308
|
-
```
|
309
|
-
|
310
|
-
```ruby
|
311
|
-
config.csp = {
|
312
|
-
:default_src => "https:",
|
313
|
-
:script_src => "'self'",
|
314
|
-
:script_hash_middleware => true
|
315
|
-
}
|
316
|
-
```
|
317
|
-
|
318
|
-
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:
|
319
|
-
|
320
|
-
```$ bundle exec rake secure_headers:generate_hashes```
|
321
|
-
|
322
|
-
Which will generate something like:
|
323
|
-
|
324
|
-
```yaml
|
325
|
-
# config/script_hashes.yml
|
326
|
-
app/views/layouts/application.html.erb:
|
327
|
-
- sha256-l8OLjZqYRnKilpdE0VosRMvhdYArjXT4NZaK2p7QVvs=
|
328
|
-
app/templates/articles/edit.html.erb:
|
329
|
-
- sha256-+7mij1/uCwtCQRWrof2NmOln5qX+5WdVwTLMpi8nuoA=
|
330
|
-
- sha256-Ny4TRIhhFpnYnSeKC274P6bfAz4TOkezLabavIAU4dA=
|
331
|
-
- sha256-I5e58Gqbu4WpO9dck18QxO7aYOHKrELIi70it4jIPi0=
|
332
|
-
- sha256-Po4LMynwnAJHxiTp3DQaQ3YDBj3paN/xrDoKl4OyxY4=
|
333
|
-
```
|
334
|
-
|
335
|
-
In this example, if we visit /articles/edit/[id], the above hashes will automatically be added to the CSP header's
|
336
|
-
script-src value!
|
337
|
-
|
338
|
-
You can use plain "script" tags or you can use a built-in helper:
|
339
|
-
|
340
|
-
```erb
|
341
|
-
<%= hashed_javascript_tag do %>
|
342
|
-
console.log("hashed automatically!")
|
343
|
-
<% end %>
|
344
|
-
```
|
345
|
-
|
346
|
-
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:
|
347
|
-
|
348
|
-
```erb
|
349
|
-
<%= hashed_javascript_tag(raise_error_on_unrecognized_hash = true) do %>
|
350
|
-
console.log("will raise an exception if not in script_hashes.yml!")
|
351
|
-
<% end %>
|
352
|
-
```
|
238
|
+
The hash feature has been removed, for now.
|
353
239
|
|
354
240
|
### Public Key Pins
|
355
241
|
|
356
242
|
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.
|
357
243
|
|
358
|
-
```
|
244
|
+
```ruby
|
359
245
|
config.hpkp = {
|
360
246
|
max_age: 60.days.to_i, # max_age is a required parameter
|
361
247
|
include_subdomains: true, # whether or not to apply pins to subdomains
|
@@ -364,7 +250,7 @@ config.hpkp = {
|
|
364
250
|
{sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
|
365
251
|
{sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
|
366
252
|
],
|
367
|
-
|
253
|
+
report_only: true, # defaults to false (report-only mode)
|
368
254
|
report_uri: '//example.com/uri-directive',
|
369
255
|
app_name: 'example',
|
370
256
|
tag_report_uri: true
|
@@ -381,28 +267,17 @@ require 'sinatra'
|
|
381
267
|
require 'haml'
|
382
268
|
require 'secure_headers'
|
383
269
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
config.x_xss_protection = {:value => 1, :mode => false}
|
389
|
-
config.x_download_options = 'noopen'
|
390
|
-
config.x_permitted_cross_domain_policies = 'none'
|
391
|
-
config.csp = {
|
392
|
-
:default_src => "https: inline eval",
|
393
|
-
:report_uri => '//example.com/uri-directive',
|
394
|
-
:img_src => "https: data:",
|
395
|
-
:frame_src => "https: http:.twimg.com http://itunes.apple.com"
|
396
|
-
}
|
397
|
-
config.hpkp = false
|
270
|
+
use SecureHeaders::Middleware
|
271
|
+
|
272
|
+
SecureHeaders::Configuration.configure do |config|
|
273
|
+
...
|
398
274
|
end
|
399
275
|
|
400
276
|
class Donkey < Sinatra::Application
|
401
|
-
include SecureHeaders
|
402
277
|
set :root, APP_ROOT
|
403
278
|
|
404
279
|
get '/' do
|
405
|
-
|
280
|
+
SecureHeaders.override_x_frame_options(SecureHeaders::OPT_OUT)
|
406
281
|
haml :index
|
407
282
|
end
|
408
283
|
end
|
@@ -415,12 +290,13 @@ You can use SecureHeaders for Padrino applications as well:
|
|
415
290
|
In your `Gemfile`:
|
416
291
|
|
417
292
|
```ruby
|
418
|
-
gem "secure_headers", :
|
293
|
+
gem "secure_headers", require: 'secure_headers'
|
419
294
|
```
|
420
295
|
|
421
296
|
then in your `app.rb` file you can:
|
422
297
|
|
423
298
|
```ruby
|
299
|
+
Padrino.use(SecureHeaders::Middleware)
|
424
300
|
require 'secure_headers/padrino'
|
425
301
|
|
426
302
|
module Web
|
@@ -428,7 +304,6 @@ module Web
|
|
428
304
|
register SecureHeaders::Padrino
|
429
305
|
|
430
306
|
get '/' do
|
431
|
-
set_csp_header
|
432
307
|
render 'index'
|
433
308
|
end
|
434
309
|
end
|
@@ -439,48 +314,8 @@ and in `config/boot.rb`:
|
|
439
314
|
|
440
315
|
```ruby
|
441
316
|
def before_load
|
442
|
-
|
443
|
-
|
444
|
-
config.x_frame_options = 'DENY'
|
445
|
-
config.x_content_type_options = "nosniff"
|
446
|
-
config.x_xss_protection = {:value => '1', :mode => false}
|
447
|
-
config.x_download_options = 'noopen'
|
448
|
-
config.x_permitted_cross_domain_policies = 'none'
|
449
|
-
config.csp = {
|
450
|
-
:default_src => "https: inline eval",
|
451
|
-
:report_uri => '//example.com/uri-directive',
|
452
|
-
:img_src => "https: data:",
|
453
|
-
:frame_src => "https: http:.twimg.com http://itunes.apple.com"
|
454
|
-
}
|
455
|
-
end
|
456
|
-
end
|
457
|
-
```
|
458
|
-
|
459
|
-
### Using in rack middleware
|
460
|
-
|
461
|
-
The `SecureHeaders::header_hash` generates a hash of all header values, which is useful for merging with rack middleware values.
|
462
|
-
|
463
|
-
```ruby
|
464
|
-
class MySecureHeaders
|
465
|
-
include SecureHeaders
|
466
|
-
def initialize(app)
|
467
|
-
@app = app
|
468
|
-
end
|
469
|
-
|
470
|
-
def call(env)
|
471
|
-
status, headers, response = @app.call(env)
|
472
|
-
security_headers = if override?
|
473
|
-
SecureHeaders::header_hash(:csp => false) # uses global config, but overrides CSP config
|
474
|
-
else
|
475
|
-
SecureHeaders::header_hash # uses global config
|
476
|
-
end
|
477
|
-
[status, headers.merge(security_headers), [response.body]]
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
module Testapp
|
482
|
-
class Application < Rails::Application
|
483
|
-
config.middleware.use MySecureHeaders
|
317
|
+
SecureHeaders::Configuration.configure do |config|
|
318
|
+
...
|
484
319
|
end
|
485
320
|
end
|
486
321
|
```
|
@@ -495,18 +330,6 @@ end
|
|
495
330
|
* Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
|
496
331
|
* Go - [secureheader](https://github.com/kr/secureheader)
|
497
332
|
|
498
|
-
## Authors
|
499
|
-
|
500
|
-
* Neil Matatall [@ndm](https://twitter.com/ndm) - primary author.
|
501
|
-
* Nicholas Green [@nickgreen](https://twitter.com/nickgreen) - code contributions, main reviewer.
|
502
|
-
|
503
|
-
## Acknowledgements
|
504
|
-
|
505
|
-
* Justin Collins [@presidentbeef](https://twitter.com/presidentbeef) & Jim O'Leary [@jimio](https://twitter.com/jimio) for reviews.
|
506
|
-
* 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.
|
507
|
-
* Sumit Shah [@omnidactyl](https://twitter.com/omnidactyl) - For being an eager guinea pig.
|
508
|
-
* Chris Aniszczyk [@cra](https://twitter.com/cra) - For running an awesome open source program at Twitter.
|
509
|
-
|
510
333
|
## License
|
511
334
|
|
512
335
|
Copyright 2013-2014 Twitter, Inc and other contributors.
|