secure_headers 3.6.4 → 4.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 +2 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +8 -6
- data/CHANGELOG.md +21 -1
- data/CONTRIBUTING.md +3 -5
- data/Gemfile +7 -4
- data/Guardfile +1 -0
- data/README.md +15 -19
- data/Rakefile +22 -18
- data/docs/cookies.md +16 -2
- data/docs/hashes.md +1 -1
- data/docs/per_action_configuration.md +28 -0
- data/lib/secure_headers/configuration.rb +29 -16
- data/lib/secure_headers/hash_helper.rb +2 -1
- data/lib/secure_headers/headers/clear_site_data.rb +16 -12
- data/lib/secure_headers/headers/content_security_policy.rb +52 -20
- data/lib/secure_headers/headers/content_security_policy_config.rb +25 -0
- data/lib/secure_headers/headers/cookie.rb +21 -3
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
- data/lib/secure_headers/headers/policy_management.rb +115 -68
- data/lib/secure_headers/headers/public_key_pins.rb +5 -4
- data/lib/secure_headers/headers/referrer_policy.rb +1 -0
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
- data/lib/secure_headers/headers/x_download_options.rb +2 -1
- data/lib/secure_headers/headers/x_frame_options.rb +1 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -1
- data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
- data/lib/secure_headers/middleware.rb +10 -9
- data/lib/secure_headers/railtie.rb +7 -6
- data/lib/secure_headers/utils/cookies_config.rb +13 -12
- data/lib/secure_headers/view_helper.rb +4 -3
- data/lib/secure_headers.rb +6 -2
- data/lib/tasks/tasks.rake +2 -1
- data/secure_headers.gemspec +13 -3
- data/spec/lib/secure_headers/configuration_spec.rb +9 -8
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +11 -27
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +42 -26
- data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
- data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +57 -10
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
- data/spec/lib/secure_headers/middleware_spec.rb +29 -21
- data/spec/lib/secure_headers/view_helpers_spec.rb +5 -5
- data/spec/lib/secure_headers_spec.rb +96 -124
- data/spec/spec_helper.rb +10 -22
- data/upgrading-to-4-0.md +55 -0
- metadata +16 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de4e0f18558ac4a65a9983f56e4dd65ca95e5f82
|
|
4
|
+
data.tar.gz: 3c16a5bdeec36aab812bcbfbbdba544b969c3a33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14f3bc42066b1b4bd6044b75a78818a6ce6edc60760e84af03d13623758a4d05b9397d0c4b4baa92c71d105e0334fa39ec56124c3014e8a63096236f9c6e6d51
|
|
7
|
+
data.tar.gz: f07cfc4d11b24a7d7ec1ad50b2fb756fd3a69c8558b922eb79daf35bbf5dc721c24ca27dfa8b5ca13865dc3345cb0162161f3c41fa04ffbff8c190908d8a7b32
|
data/.rspec
CHANGED
data/.rubocop.yml
ADDED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.4.1
|
data/.travis.yml
CHANGED
|
@@ -2,15 +2,17 @@ language: ruby
|
|
|
2
2
|
|
|
3
3
|
rvm:
|
|
4
4
|
- ruby-head
|
|
5
|
-
- 2.4.
|
|
6
|
-
- 2.3.
|
|
5
|
+
- 2.4.1
|
|
6
|
+
- 2.3.4
|
|
7
7
|
- 2.2
|
|
8
|
-
- 2.1
|
|
9
|
-
- 2.0.0
|
|
10
|
-
- 1.9.3
|
|
11
|
-
- jruby-19mode
|
|
12
8
|
- jruby-head
|
|
13
9
|
|
|
10
|
+
env:
|
|
11
|
+
- SUITE=rspec spec
|
|
12
|
+
- SUITE=rubocop
|
|
13
|
+
|
|
14
|
+
script: bundle exec $SUITE
|
|
15
|
+
|
|
14
16
|
matrix:
|
|
15
17
|
allow_failures:
|
|
16
18
|
- rvm: jruby-head
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 4.x
|
|
2
|
+
|
|
3
|
+
- See the [upgrading to 4.0](upgrading-to-4-0.md) guide. Lots of breaking changes.
|
|
4
|
+
|
|
5
|
+
## 3.7.1
|
|
6
|
+
|
|
7
|
+
Fix support for the sandbox attribute of CSP. `true` and `[]` represent the maximally restricted policy (`sandbox;`) and validate other values.
|
|
8
|
+
|
|
9
|
+
## 3.7.0
|
|
10
|
+
|
|
11
|
+
Adds support for the `Expect-CT` header (@jacobbednarz: https://github.com/twitter/secureheaders/pull/322)
|
|
12
|
+
|
|
13
|
+
## 3.6.7
|
|
14
|
+
|
|
15
|
+
Actually set manifest-src when configured. https://github.com/twitter/secureheaders/pull/339 Thanks @carlosantoniodasilva!
|
|
16
|
+
|
|
17
|
+
## 3.6.5
|
|
18
|
+
|
|
19
|
+
Update clear-site-data header to use current format specified by the specification.
|
|
20
|
+
|
|
1
21
|
## 3.6.4
|
|
2
22
|
|
|
3
23
|
Fix case where mixing frame-src/child-src dynamically would behave in unexpected ways: https://github.com/twitter/secureheaders/pull/325
|
|
@@ -196,7 +216,7 @@ You can add hash sources directly to your policy :
|
|
|
196
216
|
rake secure_headers:generate_hashes
|
|
197
217
|
```
|
|
198
218
|
|
|
199
|
-
This will generate a file (`config/
|
|
219
|
+
This will generate a file (`config/secure_headers_generated_hashes.yml` by default, you can override by setting `ENV["secure_headers_generated_hashes_file"]`) containing a mapping of file names with the array of hash values found on that page. When ActionView renders a given file, we check if there are any known hashes for that given file. If so, they are added as values to the header.
|
|
200
220
|
|
|
201
221
|
```yaml
|
|
202
222
|
---
|
data/CONTRIBUTING.md
CHANGED
|
@@ -15,7 +15,7 @@ Please note that this project is released with a [Contributor Code of Conduct][c
|
|
|
15
15
|
0. Configure and install the dependencies: `bundle install`
|
|
16
16
|
0. Make sure the tests pass on your machine: `bundle exec rspec spec`
|
|
17
17
|
0. Create a new branch: `git checkout -b my-branch-name`
|
|
18
|
-
0. Make your change, add tests, and make sure the tests still pass
|
|
18
|
+
0. Make your change, add tests, and make sure the tests still pass and that no warnings are raised
|
|
19
19
|
0. Push to your fork and [submit a pull request][pr]
|
|
20
20
|
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
|
21
21
|
|
|
@@ -25,12 +25,12 @@ Here are a few things you can do that will increase the likelihood of your pull
|
|
|
25
25
|
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
|
26
26
|
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
|
27
27
|
|
|
28
|
-
## Releasing
|
|
28
|
+
## Releasing
|
|
29
29
|
|
|
30
30
|
0. Ensure CI is green
|
|
31
31
|
0. Pull the latest code
|
|
32
32
|
0. Increment the version
|
|
33
|
-
0. Run `gem build
|
|
33
|
+
0. Run `gem build secure_headers.gemspec`
|
|
34
34
|
0. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
|
|
35
35
|
0. Test behavior locally, branch deploy, whatever needs to happen
|
|
36
36
|
0. Run `bundle exec rake release`
|
|
@@ -39,5 +39,3 @@ Here are a few things you can do that will increase the likelihood of your pull
|
|
|
39
39
|
|
|
40
40
|
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
|
41
41
|
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
|
42
|
-
|
|
43
|
-
|
data/Gemfile
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
source "https://rubygems.org"
|
|
2
3
|
|
|
3
4
|
gemspec
|
|
4
5
|
|
|
5
6
|
group :test do
|
|
6
|
-
gem "
|
|
7
|
-
gem "pry-nav"
|
|
7
|
+
gem "coveralls"
|
|
8
8
|
gem "json", "~> 1"
|
|
9
|
+
gem "pry-nav"
|
|
9
10
|
gem "rack", "~> 1"
|
|
10
11
|
gem "rspec"
|
|
11
|
-
gem "
|
|
12
|
+
gem "rubocop", "~> 0.47.0"
|
|
13
|
+
gem "rubocop-github"
|
|
12
14
|
gem "term-ansicolor", "< 1.4"
|
|
15
|
+
gem "tins", "~> 1.6.0" # 1.7 requires ruby 2.0
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
group :guard do
|
|
16
|
-
gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
|
|
17
19
|
gem "growl"
|
|
20
|
+
gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24]
|
|
18
21
|
gem "rb-fsevent"
|
|
19
22
|
end
|
data/Guardfile
CHANGED
data/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Secure Headers [](http://travis-ci.org/twitter/secureheaders) [](https://codeclimate.com/github/twitter/secureheaders) [](https://coveralls.io/r/twitter/secureheaders)
|
|
2
2
|
|
|
3
|
+
**master represents the unreleased 4.x line**. See the [upgrading to 4.x doc](upgrading-to-4-0.md) for instructions on how to upgrade. Bug fixes should go in the 3.x branch for now.
|
|
3
4
|
|
|
4
|
-
**The 3.x branch
|
|
5
|
+
**The [3.x](https://github.com/twitter/secureheaders/tree/2.x) branch is moving into maintenance mode**. 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
|
|
|
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.
|
|
7
|
+
**The [2.x branch](https://github.com/twitter/secureheaders/tree/2.x) will be not be maintained once 4.x is released**. 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.
|
|
7
8
|
|
|
8
9
|
The gem will automatically apply several headers that are related to security. This includes:
|
|
9
10
|
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](http://www.w3.org/TR/CSP2/)
|
|
@@ -18,7 +19,8 @@ The gem will automatically apply several headers that are related to security.
|
|
|
18
19
|
- 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)
|
|
19
20
|
- Referrer-Policy - [Referrer Policy draft](https://w3c.github.io/webappsec-referrer-policy/)
|
|
20
21
|
- 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)
|
|
21
|
-
-
|
|
22
|
+
- Expect-CT - Only use certificates that are present in the certificate transparency logs. [Expect-CT draft specification](https://datatracker.ietf.org/doc/draft-stark-expect-ct/).
|
|
23
|
+
- Clear-Site-Data - Clearing browser data for origin. [Clear-Site-Data specification](https://w3c.github.io/webappsec-clear-site-data/).
|
|
22
24
|
|
|
23
25
|
It can also mark all http cookies with the Secure, HttpOnly and SameSite attributes (when configured to do so).
|
|
24
26
|
|
|
@@ -53,6 +55,8 @@ If you do not supply a `default` configuration, exceptions will be raised. If yo
|
|
|
53
55
|
|
|
54
56
|
All `nil` values will fallback to their default values. `SecureHeaders::OPT_OUT` will disable the header entirely.
|
|
55
57
|
|
|
58
|
+
**Word of caution:** The following is not a default configuration per se. It serves as a sample implementation of the configuration. You should read more about these headers and determine what is appropriate for your requirements.
|
|
59
|
+
|
|
56
60
|
```ruby
|
|
57
61
|
SecureHeaders::Configuration.default do |config|
|
|
58
62
|
config.cookies = {
|
|
@@ -76,9 +80,13 @@ SecureHeaders::Configuration.default do |config|
|
|
|
76
80
|
"storage",
|
|
77
81
|
"executionContexts"
|
|
78
82
|
]
|
|
83
|
+
config.expect_certificate_transparency = {
|
|
84
|
+
enforce: false,
|
|
85
|
+
max_age: 1.day.to_i,
|
|
86
|
+
report_uri: "https://report-uri.io/example-ct"
|
|
87
|
+
}
|
|
79
88
|
config.csp = {
|
|
80
|
-
# "meta" values. these will
|
|
81
|
-
# report_only: true, # default: false [DEPRECATED from 3.5.0: instead, configure csp_report_only]
|
|
89
|
+
# "meta" values. these will shape the header, but the values are not included in the header.
|
|
82
90
|
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
|
|
83
91
|
|
|
84
92
|
# directive values: these values will directly translate into source directives
|
|
@@ -91,8 +99,10 @@ SecureHeaders::Configuration.default do |config|
|
|
|
91
99
|
form_action: %w('self' github.com),
|
|
92
100
|
frame_ancestors: %w('none'),
|
|
93
101
|
img_src: %w(mycdn.com data:),
|
|
102
|
+
manifest_src: %w('self'),
|
|
94
103
|
media_src: %w(utoob.com),
|
|
95
104
|
object_src: %w('self'),
|
|
105
|
+
sandbox: true, # true and [] will set a maximally restrictive setting
|
|
96
106
|
plugin_types: %w(application/x-shockwave-flash),
|
|
97
107
|
script_src: %w('self'),
|
|
98
108
|
style_src: %w('unsafe-inline'),
|
|
@@ -131,20 +141,6 @@ X-Permitted-Cross-Domain-Policies: none
|
|
|
131
141
|
X-Xss-Protection: 1; mode=block
|
|
132
142
|
```
|
|
133
143
|
|
|
134
|
-
### Default CSP
|
|
135
|
-
|
|
136
|
-
By default, the above CSP will be applied to all requests. If you **only** want to set a Report-Only header, opt-out of the default enforced header for clarity. The configuration will assume that if you only supply `csp_report_only` that you intended to opt-out of `csp` but that's for the sake of backwards compatibility and it will be removed in the future.
|
|
137
|
-
|
|
138
|
-
```ruby
|
|
139
|
-
Configuration.default do |config|
|
|
140
|
-
config.csp = SecureHeaders::OPT_OUT # If this line is omitted, we will assume you meant to opt out.
|
|
141
|
-
config.csp_report_only = {
|
|
142
|
-
default_src: %w('self')
|
|
143
|
-
}
|
|
144
|
-
end
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
|
|
148
144
|
## Similar libraries
|
|
149
145
|
|
|
150
146
|
* Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
|
data/Rakefile
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env rake
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
require "net/http"
|
|
6
|
+
require "net/https"
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
RSpec::Core::RakeTask.new do |t|
|
|
9
|
-
t.verbose = false
|
|
10
|
-
t.rspec_opts = "--format progress"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
task default: :spec
|
|
8
|
+
RSpec::Core::RakeTask.new
|
|
14
9
|
|
|
15
10
|
begin
|
|
16
|
-
require
|
|
11
|
+
require "rdoc/task"
|
|
17
12
|
rescue LoadError
|
|
18
|
-
require
|
|
19
|
-
require
|
|
13
|
+
require "rdoc/rdoc"
|
|
14
|
+
require "rake/rdoctask"
|
|
20
15
|
RDoc::Task = Rake::RDocTask
|
|
21
16
|
end
|
|
22
17
|
|
|
18
|
+
begin
|
|
19
|
+
require "rubocop/rake_task"
|
|
20
|
+
RuboCop::RakeTask.new
|
|
21
|
+
rescue LoadError
|
|
22
|
+
task(:rubocop) { $stderr.puts "RuboCop is disabled" }
|
|
23
|
+
end
|
|
24
|
+
|
|
23
25
|
RDoc::Task.new(:rdoc) do |rdoc|
|
|
24
|
-
rdoc.rdoc_dir =
|
|
25
|
-
rdoc.title =
|
|
26
|
-
rdoc.options <<
|
|
27
|
-
rdoc.rdoc_files.include(
|
|
26
|
+
rdoc.rdoc_dir = "rdoc"
|
|
27
|
+
rdoc.title = "SecureHeaders"
|
|
28
|
+
rdoc.options << "--line-numbers"
|
|
29
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
28
30
|
end
|
|
31
|
+
|
|
32
|
+
task default: [:spec, :rubocop]
|
data/docs/cookies.md
CHANGED
|
@@ -4,14 +4,28 @@ SecureHeaders supports `Secure`, `HttpOnly` and [`SameSite`](https://tools.ietf.
|
|
|
4
4
|
|
|
5
5
|
__Note__: Regardless of the configuration specified, Secure cookies are only enabled for HTTPS requests.
|
|
6
6
|
|
|
7
|
+
#### Defaults
|
|
8
|
+
|
|
9
|
+
By default, all cookies will get both `Secure`, `HttpOnly`, and `SameSite=Lax`.
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
config.cookies = {
|
|
13
|
+
secure: true, # defaults to true but will be a no op on non-HTTPS requests
|
|
14
|
+
httponly: true, # defaults to true
|
|
15
|
+
samesite: { # defaults to set `SameSite=Lax`
|
|
16
|
+
lax: true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
7
21
|
#### Boolean-based configuration
|
|
8
22
|
|
|
9
|
-
Boolean-based configuration is intended to globally enable or disable a specific cookie attribute.
|
|
23
|
+
Boolean-based configuration is intended to globally enable or disable a specific cookie attribute. *Note: As of 4.0, you must use OPT_OUT rather than false to opt out of the defaults.*
|
|
10
24
|
|
|
11
25
|
```ruby
|
|
12
26
|
config.cookies = {
|
|
13
27
|
secure: true, # mark all cookies as Secure
|
|
14
|
-
httponly:
|
|
28
|
+
httponly: OPT_OUT, # do not mark any cookies as HttpOnly
|
|
15
29
|
}
|
|
16
30
|
```
|
|
17
31
|
|
data/docs/hashes.md
CHANGED
|
@@ -21,7 +21,7 @@ You can add hash sources directly to your policy :
|
|
|
21
21
|
rake secure_headers:generate_hashes
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
This will generate a file (`config/
|
|
24
|
+
This will generate a file (`config/secure_headers_generated_hashes.yml` by default, you can override by setting `ENV["secure_headers_generated_hashes_file"]`) containing a mapping of file names with the array of hash values found on that page. When ActionView renders a given file, we check if there are any known hashes for that given file. If so, they are added as values to the header.
|
|
25
25
|
|
|
26
26
|
```yaml
|
|
27
27
|
---
|
|
@@ -103,3 +103,31 @@ Content-Security-Policy: ...
|
|
|
103
103
|
console.log("won't execute, not whitelisted")
|
|
104
104
|
</script>
|
|
105
105
|
```
|
|
106
|
+
|
|
107
|
+
## Clearing browser cache
|
|
108
|
+
|
|
109
|
+
You can clear the browser cache after the logout request by using the following.
|
|
110
|
+
|
|
111
|
+
``` ruby
|
|
112
|
+
class ApplicationController < ActionController::Base
|
|
113
|
+
# Configuration override to send the Clear-Site-Data header.
|
|
114
|
+
SecureHeaders::Configuration.override(:clear_browser_cache) do |config|
|
|
115
|
+
config.clear_site_data = [
|
|
116
|
+
SecureHeaders::ClearSiteData::ALL_TYPES
|
|
117
|
+
]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Clears the browser's cache for browsers supporting the Clear-Site-Data
|
|
122
|
+
# header.
|
|
123
|
+
#
|
|
124
|
+
# Returns nothing.
|
|
125
|
+
def clear_browser_cache
|
|
126
|
+
SecureHeaders.use_secure_headers_override(request, :clear_browser_cache)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class SessionsController < ApplicationController
|
|
131
|
+
after_action :clear_browser_cache, only: :destroy
|
|
132
|
+
end
|
|
133
|
+
```
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "yaml"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
class Configuration
|
|
@@ -31,7 +32,7 @@ module SecureHeaders
|
|
|
31
32
|
raise NotYetConfiguredError, "#{base} policy not yet supplied"
|
|
32
33
|
end
|
|
33
34
|
override = @configurations[base].dup
|
|
34
|
-
override.instance_eval
|
|
35
|
+
override.instance_eval(&block) if block_given?
|
|
35
36
|
add_configuration(name, override)
|
|
36
37
|
end
|
|
37
38
|
|
|
@@ -116,23 +117,41 @@ module SecureHeaders
|
|
|
116
117
|
|
|
117
118
|
attr_writer :hsts, :x_frame_options, :x_content_type_options,
|
|
118
119
|
:x_xss_protection, :x_download_options, :x_permitted_cross_domain_policies,
|
|
119
|
-
:referrer_policy, :clear_site_data
|
|
120
|
+
:referrer_policy, :clear_site_data, :expect_certificate_transparency
|
|
120
121
|
|
|
121
122
|
attr_reader :cached_headers, :csp, :cookies, :csp_report_only, :hpkp, :hpkp_report_host
|
|
122
123
|
|
|
124
|
+
@script_hashes = nil
|
|
125
|
+
@style_hashes = nil
|
|
126
|
+
|
|
123
127
|
HASH_CONFIG_FILE = ENV["secure_headers_generated_hashes_file"] || "config/secure_headers_generated_hashes.yml"
|
|
124
|
-
if File.
|
|
128
|
+
if File.exist?(HASH_CONFIG_FILE)
|
|
125
129
|
config = YAML.safe_load(File.open(HASH_CONFIG_FILE))
|
|
126
130
|
@script_hashes = config["scripts"]
|
|
127
131
|
@style_hashes = config["styles"]
|
|
128
132
|
end
|
|
129
133
|
|
|
130
134
|
def initialize(&block)
|
|
135
|
+
@cookies = nil
|
|
136
|
+
@clear_site_data = nil
|
|
137
|
+
@csp = nil
|
|
138
|
+
@csp_report_only = nil
|
|
139
|
+
@hpkp_report_host = nil
|
|
140
|
+
@hpkp = nil
|
|
141
|
+
@hsts = nil
|
|
142
|
+
@x_content_type_options = nil
|
|
143
|
+
@x_download_options = nil
|
|
144
|
+
@x_frame_options = nil
|
|
145
|
+
@x_permitted_cross_domain_policies = nil
|
|
146
|
+
@x_xss_protection = nil
|
|
147
|
+
@expect_certificate_transparency = nil
|
|
148
|
+
|
|
131
149
|
self.hpkp = OPT_OUT
|
|
132
150
|
self.referrer_policy = OPT_OUT
|
|
133
151
|
self.csp = ContentSecurityPolicyConfig.new(ContentSecurityPolicyConfig::DEFAULT)
|
|
134
152
|
self.csp_report_only = OPT_OUT
|
|
135
|
-
|
|
153
|
+
|
|
154
|
+
instance_eval(&block) if block_given?
|
|
136
155
|
end
|
|
137
156
|
|
|
138
157
|
# Public: copy everything but the cached headers
|
|
@@ -151,6 +170,7 @@ module SecureHeaders
|
|
|
151
170
|
copy.x_download_options = @x_download_options
|
|
152
171
|
copy.x_permitted_cross_domain_policies = @x_permitted_cross_domain_policies
|
|
153
172
|
copy.clear_site_data = @clear_site_data
|
|
173
|
+
copy.expect_certificate_transparency = @expect_certificate_transparency
|
|
154
174
|
copy.referrer_policy = @referrer_policy
|
|
155
175
|
copy.hpkp = @hpkp
|
|
156
176
|
copy.hpkp_report_host = @hpkp_report_host
|
|
@@ -183,13 +203,13 @@ module SecureHeaders
|
|
|
183
203
|
XDownloadOptions.validate_config!(@x_download_options)
|
|
184
204
|
XPermittedCrossDomainPolicies.validate_config!(@x_permitted_cross_domain_policies)
|
|
185
205
|
ClearSiteData.validate_config!(@clear_site_data)
|
|
206
|
+
ExpectCertificateTransparency.validate_config!(@expect_certificate_transparency)
|
|
186
207
|
PublicKeyPins.validate_config!(@hpkp)
|
|
187
208
|
Cookie.validate_config!(@cookies)
|
|
188
209
|
end
|
|
189
210
|
|
|
190
211
|
def secure_cookies=(secure_cookies)
|
|
191
|
-
|
|
192
|
-
@cookies = (@cookies || {}).merge(secure: secure_cookies)
|
|
212
|
+
raise ArgumentError, "#{Kernel.caller.first}: `#secure_cookies=` is no longer supported. Please use `#cookies=` to configure secure cookies instead."
|
|
193
213
|
end
|
|
194
214
|
|
|
195
215
|
def csp=(new_csp)
|
|
@@ -197,10 +217,8 @@ module SecureHeaders
|
|
|
197
217
|
@csp = new_csp.dup
|
|
198
218
|
else
|
|
199
219
|
if new_csp[:report_only]
|
|
200
|
-
#
|
|
201
|
-
|
|
202
|
-
@csp = OPT_OUT
|
|
203
|
-
self.csp_report_only = new_csp
|
|
220
|
+
# invalid configuration implies that CSPRO should be set, CSP should not - so opt out
|
|
221
|
+
raise ArgumentError, "#{Kernel.caller.first}: `#csp=` was supplied a config with report_only: true. Use #csp_report_only="
|
|
204
222
|
else
|
|
205
223
|
@csp = ContentSecurityPolicyConfig.new(new_csp)
|
|
206
224
|
end
|
|
@@ -227,11 +245,6 @@ module SecureHeaders
|
|
|
227
245
|
end
|
|
228
246
|
end
|
|
229
247
|
end
|
|
230
|
-
|
|
231
|
-
if !@csp_report_only.opt_out? && @csp.to_h == ContentSecurityPolicyConfig::DEFAULT
|
|
232
|
-
Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#csp_report_only=` was configured before `#csp=`. It is assumed you intended to opt out of `#csp=` so be sure to add `config.csp = SecureHeaders::OPT_OUT` to your config. Ensure that #csp_report_only is configured after #csp="
|
|
233
|
-
@csp = OPT_OUT
|
|
234
|
-
end
|
|
235
248
|
end
|
|
236
249
|
|
|
237
250
|
protected
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SecureHeaders
|
|
2
3
|
class ClearSiteDataConfigError < StandardError; end
|
|
3
4
|
class ClearSiteData
|
|
4
5
|
HEADER_NAME = "Clear-Site-Data".freeze
|
|
5
|
-
TYPES = "types".freeze
|
|
6
6
|
|
|
7
7
|
# Valid `types`
|
|
8
8
|
CACHE = "cache".freeze
|
|
9
9
|
COOKIES = "cookies".freeze
|
|
10
10
|
STORAGE = "storage".freeze
|
|
11
|
-
|
|
12
|
-
ALL_TYPES = [CACHE, COOKIES, STORAGE,
|
|
11
|
+
EXECUTION_CONTEXTS = "executionContexts".freeze
|
|
12
|
+
ALL_TYPES = [CACHE, COOKIES, STORAGE, EXECUTION_CONTEXTS]
|
|
13
13
|
|
|
14
14
|
CONFIG_KEY = :clear_site_data
|
|
15
15
|
|
|
@@ -17,14 +17,14 @@ module SecureHeaders
|
|
|
17
17
|
# Public: make an Clear-Site-Data header name, value pair
|
|
18
18
|
#
|
|
19
19
|
# Returns nil if not configured, returns header name and value if configured.
|
|
20
|
-
def make_header(config=nil)
|
|
20
|
+
def make_header(config = nil)
|
|
21
21
|
case config
|
|
22
22
|
when nil, OPT_OUT, []
|
|
23
23
|
# noop
|
|
24
24
|
when Array
|
|
25
|
-
[HEADER_NAME,
|
|
25
|
+
[HEADER_NAME, make_header_value(config)]
|
|
26
26
|
when true
|
|
27
|
-
[HEADER_NAME,
|
|
27
|
+
[HEADER_NAME, make_header_value(ALL_TYPES)]
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -36,16 +36,20 @@ module SecureHeaders
|
|
|
36
36
|
unless config.all? { |t| t.is_a?(String) }
|
|
37
37
|
raise ClearSiteDataConfigError.new("types must be Strings")
|
|
38
38
|
end
|
|
39
|
-
|
|
40
|
-
begin
|
|
41
|
-
JSON.dump(config)
|
|
42
|
-
rescue JSON::GeneratorError, Encoding::UndefinedConversionError
|
|
43
|
-
raise ClearSiteDataConfigError.new("types must serializable by JSON")
|
|
44
|
-
end
|
|
45
39
|
else
|
|
46
40
|
raise ClearSiteDataConfigError.new("config must be an Array of Strings or `true`")
|
|
47
41
|
end
|
|
48
42
|
end
|
|
43
|
+
|
|
44
|
+
# Public: Transform a Clear-Site-Data config (an Array of Strings) into a
|
|
45
|
+
# String that can be used as the value for the Clear-Site-Data header.
|
|
46
|
+
#
|
|
47
|
+
# types - An Array of String of types of data to clear.
|
|
48
|
+
#
|
|
49
|
+
# Returns a String of quoted values that are comma separated.
|
|
50
|
+
def make_header_value(types)
|
|
51
|
+
types.map { |t| "\"#{t}\""}.join(", ")
|
|
52
|
+
end
|
|
49
53
|
end
|
|
50
54
|
end
|
|
51
55
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
require_relative
|
|
3
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "policy_management"
|
|
3
|
+
require_relative "content_security_policy_config"
|
|
4
|
+
require "useragent"
|
|
4
5
|
|
|
5
6
|
module SecureHeaders
|
|
6
7
|
class ContentSecurityPolicy
|
|
@@ -54,18 +55,12 @@ module SecureHeaders
|
|
|
54
55
|
|
|
55
56
|
private
|
|
56
57
|
|
|
57
|
-
# frame-src is deprecated, child-src is being implemented. They are
|
|
58
|
-
# very similar and in most cases, the same value can be used for both.
|
|
59
58
|
def normalize_child_frame_src
|
|
60
59
|
if @config.frame_src && @config.child_src && @config.frame_src != @config.child_src
|
|
61
|
-
|
|
60
|
+
raise ArgumentError, "#{Kernel.caller.first}: both :child_src and :frame_src supplied and do not match. This can lead to inconsistent behavior across browsers."
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
@config.child_src || @config.frame_src
|
|
66
|
-
else
|
|
67
|
-
@config.frame_src || @config.child_src
|
|
68
|
-
end
|
|
63
|
+
@config.frame_src || @config.child_src
|
|
69
64
|
end
|
|
70
65
|
|
|
71
66
|
# Private: converts the config object into a string representing a policy.
|
|
@@ -80,20 +75,55 @@ module SecureHeaders
|
|
|
80
75
|
case DIRECTIVE_VALUE_TYPES[directive_name]
|
|
81
76
|
when :boolean
|
|
82
77
|
symbol_to_hyphen_case(directive_name) if @config.directive_value(directive_name)
|
|
83
|
-
when :
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
when :sandbox_list
|
|
79
|
+
build_sandbox_list_directive(directive_name)
|
|
80
|
+
when :media_type_list
|
|
81
|
+
build_media_type_list_directive(directive_name)
|
|
82
|
+
when :source_list
|
|
83
|
+
build_source_list_directive(directive_name)
|
|
87
84
|
end
|
|
88
85
|
end.compact.join("; ")
|
|
89
86
|
end
|
|
90
87
|
|
|
88
|
+
def build_sandbox_list_directive(directive)
|
|
89
|
+
return unless sandbox_list = @config.directive_value(directive)
|
|
90
|
+
max_strict_policy = case sandbox_list
|
|
91
|
+
when Array
|
|
92
|
+
sandbox_list.empty?
|
|
93
|
+
when true
|
|
94
|
+
true
|
|
95
|
+
else
|
|
96
|
+
false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A maximally strict sandbox policy is just the `sandbox` directive,
|
|
100
|
+
# whith no configuraiton values.
|
|
101
|
+
if max_strict_policy
|
|
102
|
+
symbol_to_hyphen_case(directive)
|
|
103
|
+
elsif sandbox_list && sandbox_list.any?
|
|
104
|
+
[
|
|
105
|
+
symbol_to_hyphen_case(directive),
|
|
106
|
+
sandbox_list.uniq
|
|
107
|
+
].join(" ")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_media_type_list_directive(directive)
|
|
112
|
+
return unless media_type_list = @config.directive_value(directive)
|
|
113
|
+
if media_type_list && media_type_list.any?
|
|
114
|
+
[
|
|
115
|
+
symbol_to_hyphen_case(directive),
|
|
116
|
+
media_type_list.uniq
|
|
117
|
+
].join(" ")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
91
121
|
# Private: builds a string that represents one directive in a minified form.
|
|
92
122
|
#
|
|
93
123
|
# directive_name - a symbol representing the various ALL_DIRECTIVES
|
|
94
124
|
#
|
|
95
125
|
# Returns a string representing a directive.
|
|
96
|
-
def
|
|
126
|
+
def build_source_list_directive(directive)
|
|
97
127
|
source_list = case directive
|
|
98
128
|
when :child_src
|
|
99
129
|
if supported_directives.include?(:child_src)
|
|
@@ -106,9 +136,11 @@ module SecureHeaders
|
|
|
106
136
|
else
|
|
107
137
|
@config.directive_value(directive)
|
|
108
138
|
end
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
139
|
+
|
|
140
|
+
if source_list != OPT_OUT && source_list && source_list.any?
|
|
141
|
+
normalized_source_list = minify_source_list(directive, source_list)
|
|
142
|
+
[symbol_to_hyphen_case(directive), normalized_source_list].join(" ")
|
|
143
|
+
end
|
|
112
144
|
end
|
|
113
145
|
|
|
114
146
|
# If a directive contains *, all other values are omitted.
|
|
@@ -229,7 +261,7 @@ module SecureHeaders
|
|
|
229
261
|
end
|
|
230
262
|
|
|
231
263
|
def symbol_to_hyphen_case(sym)
|
|
232
|
-
sym.to_s.tr(
|
|
264
|
+
sym.to_s.tr("_", "-")
|
|
233
265
|
end
|
|
234
266
|
end
|
|
235
267
|
end
|