secure_headers 2.5.3 → 3.0.0.pre
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/.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 [](http://travis-ci.org/twitter/secureheaders) [](https://codeclimate.com/github/twitter/secureheaders) [](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.
|