secure_headers 2.2.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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +3 -1
- data/CHANGELOG.md +205 -0
- data/Gemfile +9 -10
- data/Guardfile +12 -0
- data/README.md +175 -276
- data/Rakefile +2 -36
- data/lib/secure_headers/configuration.rb +189 -0
- data/lib/secure_headers/headers/content_security_policy.rb +367 -188
- data/lib/secure_headers/headers/public_key_pins.rb +43 -57
- data/lib/secure_headers/headers/strict_transport_security.rb +21 -47
- data/lib/secure_headers/headers/x_content_type_options.rb +19 -32
- data/lib/secure_headers/headers/x_download_options.rb +18 -31
- data/lib/secure_headers/headers/x_frame_options.rb +24 -32
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -32
- data/lib/secure_headers/headers/x_xss_protection.rb +17 -46
- 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 -44
- data/lib/secure_headers.rb +244 -145
- data/secure_headers.gemspec +7 -11
- data/spec/lib/secure_headers/configuration_spec.rb +80 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +176 -213
- 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 +13 -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 +22 -30
- data/spec/lib/secure_headers/middleware_spec.rb +40 -0
- data/spec/lib/secure_headers_spec.rb +204 -278
- data/spec/spec_helper.rb +32 -25
- data/upgrading-to-3-0.md +37 -0
- metadata +28 -95
- data/fixtures/rails_3_2_12/.rspec +0 -1
- data/fixtures/rails_3_2_12/Gemfile +0 -6
- data/fixtures/rails_3_2_12/README.rdoc +0 -261
- data/fixtures/rails_3_2_12/Rakefile +0 -7
- data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_3_2_12/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_3_2_12/config/application.rb +0 -14
- data/fixtures/rails_3_2_12/config/boot.rb +0 -6
- data/fixtures/rails_3_2_12/config/environment.rb +0 -5
- data/fixtures/rails_3_2_12/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +0 -17
- data/fixtures/rails_3_2_12/config/routes.rb +0 -4
- data/fixtures/rails_3_2_12/config/script_hashes.yml +0 -5
- data/fixtures/rails_3_2_12/config.ru +0 -7
- data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +0 -15
- data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/.rspec +0 -1
- data/fixtures/rails_3_2_12_no_init/Gemfile +0 -5
- data/fixtures/rails_3_2_12_no_init/README.rdoc +0 -261
- data/fixtures/rails_3_2_12_no_init/Rakefile +0 -7
- data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -6
- data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -12
- data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +0 -1
- data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -0
- data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -17
- data/fixtures/rails_3_2_12_no_init/config/boot.rb +0 -6
- data/fixtures/rails_3_2_12_no_init/config/environment.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -4
- data/fixtures/rails_3_2_12_no_init/config.ru +0 -4
- data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +0 -50
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_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 -17
- 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/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 -47
data/README.md
CHANGED
|
@@ -1,211 +1,202 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Secure Headers [](http://travis-ci.org/twitter/secureheaders) [](https://codeclimate.com/github/twitter/secureheaders) [](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](
|
|
8
|
-
- X-Content-Type-Options - [Prevent content type sniffing](
|
|
9
|
-
- X-Download-Options - [Prevent file downloads opening](
|
|
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
|
|
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`
|
|
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)
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
`secure_headers` is a library with a global config, per request overrides, and rack middleware that enables you customize your application settings.
|
|
22
19
|
|
|
23
|
-
|
|
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`
|
|
20
|
+
## Use
|
|
31
21
|
|
|
32
|
-
|
|
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.
|
|
22
|
+
`gem install secure_headers`
|
|
37
23
|
|
|
38
24
|
## Configuration
|
|
39
25
|
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
config.hsts = {
|
|
45
|
-
config.x_frame_options =
|
|
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 =
|
|
48
|
-
config.x_download_options =
|
|
49
|
-
config.x_permitted_cross_domain_policies =
|
|
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
|
-
|
|
52
|
-
:
|
|
53
|
-
:
|
|
54
|
-
|
|
55
|
-
:
|
|
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
|
-
:
|
|
59
|
-
:
|
|
60
|
-
:
|
|
61
|
-
:
|
|
62
|
-
|
|
63
|
-
{:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
82
|
+
## Default values
|
|
85
83
|
|
|
86
|
-
|
|
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).
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
is provided, that value is inserted verbatim. If a hash is supplied, a
|
|
90
|
-
header will be constructed using the supplied options.
|
|
86
|
+
## Named overrides
|
|
91
87
|
|
|
92
|
-
|
|
88
|
+
Named overrides serve two purposes:
|
|
93
89
|
|
|
94
|
-
|
|
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.
|
|
95
92
|
|
|
96
|
-
|
|
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'}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Content Security Policy (CSP)
|
|
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.
|
|
106
94
|
|
|
107
95
|
```ruby
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Example CSP header config
|
|
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
|
|
140
103
|
|
|
104
|
+
# override default configuration
|
|
105
|
+
SecureHeaders::Configuration.override(:script_from_otherdomain_com) do |config|
|
|
106
|
+
config.csp[:script_src] << "otherdomain.com"
|
|
107
|
+
end
|
|
141
108
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
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
|
|
148
114
|
|
|
149
|
-
|
|
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
|
|
150
120
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
126
|
+
```
|
|
156
127
|
|
|
157
|
-
|
|
128
|
+
By default, a noop configuration is provided. No headers will be set when this default override is used.
|
|
158
129
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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;"
|
|
130
|
+
```ruby
|
|
131
|
+
class MyController < ApplicationController
|
|
132
|
+
def index
|
|
133
|
+
SecureHeaders::opt_out_of_all_protection(request)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
168
136
|
```
|
|
169
137
|
|
|
170
|
-
|
|
138
|
+
## Per-action configuration
|
|
171
139
|
|
|
172
|
-
|
|
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.
|
|
173
141
|
|
|
174
142
|
```ruby
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
|
|
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
|
+
|
|
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'))
|
|
160
|
+
|
|
161
|
+
# Global settings default to "sameorigin"
|
|
162
|
+
override_x_frame_options("DENY")
|
|
163
|
+
end
|
|
186
164
|
```
|
|
187
165
|
|
|
188
|
-
|
|
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`
|
|
189
170
|
|
|
190
|
-
|
|
171
|
+
## Appending / overriding Content Security Policy
|
|
191
172
|
|
|
192
|
-
|
|
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:)}`.
|
|
193
174
|
|
|
194
|
-
|
|
175
|
+
#### Append to the policy with a directive other than `default_src`
|
|
195
176
|
|
|
196
|
-
|
|
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:
|
|
197
178
|
|
|
198
179
|
```ruby
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
180
|
+
::SecureHeaders::Configuration.default do |config|
|
|
181
|
+
config.csp = {
|
|
182
|
+
default_src: %w('self')
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
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`
|
|
204
191
|
|
|
205
|
-
|
|
192
|
+
#### Nonce
|
|
193
|
+
|
|
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
|
+
|
|
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.
|
|
206
197
|
|
|
207
198
|
```erb
|
|
208
|
-
<script 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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", :
|
|
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,42 +317,22 @@ and in `config/boot.rb`:
|
|
|
398
317
|
|
|
399
318
|
```ruby
|
|
400
319
|
def before_load
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
}
|
|
320
|
+
SecureHeaders::Configuration.default do |config|
|
|
321
|
+
...
|
|
414
322
|
end
|
|
415
323
|
end
|
|
416
324
|
```
|
|
417
325
|
|
|
418
326
|
## Similar libraries
|
|
419
327
|
|
|
420
|
-
*
|
|
421
|
-
*
|
|
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)
|
|
330
|
+
* Node.js (hapi) [blankie](https://github.com/nlf/blankie)
|
|
331
|
+
* J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
|
|
422
332
|
* ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
|
|
423
|
-
* Python - [django-csp](https://github.com/mozilla/django-csp
|
|
333
|
+
* Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
|
|
424
334
|
* Go - [secureheader](https://github.com/kr/secureheader)
|
|
425
|
-
|
|
426
|
-
## Authors
|
|
427
|
-
|
|
428
|
-
* Neil Matatall [@ndm](https://twitter.com/ndm) - primary author.
|
|
429
|
-
* Nicholas Green [@nickgreen](https://twitter.com/nickgreen) - code contributions, main reviewer.
|
|
430
|
-
|
|
431
|
-
## Acknowledgements
|
|
432
|
-
|
|
433
|
-
* Justin Collins [@presidentbeef](https://twitter.com/presidentbeef) & Jim O'Leary [@jimio](https://twitter.com/jimio) for reviews.
|
|
434
|
-
* 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.
|
|
435
|
-
* Sumit Shah [@omnidactyl](https://twitter.com/omnidactyl) - For being an eager guinea pig.
|
|
436
|
-
* 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)
|
|
437
336
|
|
|
438
337
|
## License
|
|
439
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
|
-
|
|
10
|
+
t.rspec_opts = "--format progress"
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
task :
|
|
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'
|