secure_headers 3.7.2 → 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 +1 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +8 -6
- data/CHANGELOG.md +2 -6
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +7 -4
- data/Guardfile +1 -0
- data/README.md +7 -19
- data/Rakefile +22 -18
- data/docs/cookies.md +16 -2
- data/docs/per_action_configuration.md +28 -0
- data/lib/secure_headers/configuration.rb +5 -12
- data/lib/secure_headers/hash_helper.rb +2 -1
- data/lib/secure_headers/headers/clear_site_data.rb +4 -3
- data/lib/secure_headers/headers/content_security_policy.rb +12 -15
- data/lib/secure_headers/headers/content_security_policy_config.rb +1 -1
- data/lib/secure_headers/headers/cookie.rb +21 -3
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +1 -1
- data/lib/secure_headers/headers/policy_management.rb +14 -8
- data/lib/secure_headers/headers/public_key_pins.rb +4 -3
- 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 +2 -1
- data/lib/secure_headers.rb +2 -1
- 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 +2 -1
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +18 -18
- data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +26 -11
- 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 -4
- data/spec/lib/secure_headers_spec.rb +92 -120
- data/spec/spec_helper.rb +9 -22
- data/upgrading-to-4-0.md +55 -0
- metadata +13 -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,6 +1,6 @@
|
|
|
1
|
-
##
|
|
1
|
+
## 4.x
|
|
2
2
|
|
|
3
|
-
-
|
|
3
|
+
- See the [upgrading to 4.0](upgrading-to-4-0.md) guide. Lots of breaking changes.
|
|
4
4
|
|
|
5
5
|
## 3.7.1
|
|
6
6
|
|
|
@@ -14,10 +14,6 @@ Adds support for the `Expect-CT` header (@jacobbednarz: https://github.com/twitt
|
|
|
14
14
|
|
|
15
15
|
Actually set manifest-src when configured. https://github.com/twitter/secureheaders/pull/339 Thanks @carlosantoniodasilva!
|
|
16
16
|
|
|
17
|
-
## 3.6.6
|
|
18
|
-
|
|
19
|
-
wat?
|
|
20
|
-
|
|
21
17
|
## 3.6.5
|
|
22
18
|
|
|
23
19
|
Update clear-site-data header to use current format specified by the specification.
|
data/CONTRIBUTING.md
CHANGED
|
@@ -30,7 +30,7 @@ Here are a few things you can do that will increase the likelihood of your pull
|
|
|
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`
|
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/)
|
|
@@ -54,6 +55,8 @@ If you do not supply a `default` configuration, exceptions will be raised. If yo
|
|
|
54
55
|
|
|
55
56
|
All `nil` values will fallback to their default values. `SecureHeaders::OPT_OUT` will disable the header entirely.
|
|
56
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
|
+
|
|
57
60
|
```ruby
|
|
58
61
|
SecureHeaders::Configuration.default do |config|
|
|
59
62
|
config.cookies = {
|
|
@@ -83,8 +86,7 @@ SecureHeaders::Configuration.default do |config|
|
|
|
83
86
|
report_uri: "https://report-uri.io/example-ct"
|
|
84
87
|
}
|
|
85
88
|
config.csp = {
|
|
86
|
-
# "meta" values. these will
|
|
87
|
-
# 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.
|
|
88
90
|
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
|
|
89
91
|
|
|
90
92
|
# directive values: these values will directly translate into source directives
|
|
@@ -100,10 +102,10 @@ SecureHeaders::Configuration.default do |config|
|
|
|
100
102
|
manifest_src: %w('self'),
|
|
101
103
|
media_src: %w(utoob.com),
|
|
102
104
|
object_src: %w('self'),
|
|
105
|
+
sandbox: true, # true and [] will set a maximally restrictive setting
|
|
103
106
|
plugin_types: %w(application/x-shockwave-flash),
|
|
104
107
|
script_src: %w('self'),
|
|
105
108
|
style_src: %w('unsafe-inline'),
|
|
106
|
-
worker_src: %w('self'),
|
|
107
109
|
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
|
|
108
110
|
report_uri: %w(https://report-uri.io/example-csp)
|
|
109
111
|
}
|
|
@@ -139,20 +141,6 @@ X-Permitted-Cross-Domain-Policies: none
|
|
|
139
141
|
X-Xss-Protection: 1; mode=block
|
|
140
142
|
```
|
|
141
143
|
|
|
142
|
-
### Default CSP
|
|
143
|
-
|
|
144
|
-
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.
|
|
145
|
-
|
|
146
|
-
```ruby
|
|
147
|
-
Configuration.default do |config|
|
|
148
|
-
config.csp = SecureHeaders::OPT_OUT # If this line is omitted, we will assume you meant to opt out.
|
|
149
|
-
config.csp_report_only = {
|
|
150
|
-
default_src: %w('self')
|
|
151
|
-
}
|
|
152
|
-
end
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
|
|
156
144
|
## Similar libraries
|
|
157
145
|
|
|
158
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
|
|
|
@@ -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
|
|
@@ -208,8 +209,7 @@ module SecureHeaders
|
|
|
208
209
|
end
|
|
209
210
|
|
|
210
211
|
def secure_cookies=(secure_cookies)
|
|
211
|
-
|
|
212
|
-
@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."
|
|
213
213
|
end
|
|
214
214
|
|
|
215
215
|
def csp=(new_csp)
|
|
@@ -217,10 +217,8 @@ module SecureHeaders
|
|
|
217
217
|
@csp = new_csp.dup
|
|
218
218
|
else
|
|
219
219
|
if new_csp[:report_only]
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
@csp = OPT_OUT
|
|
223
|
-
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="
|
|
224
222
|
else
|
|
225
223
|
@csp = ContentSecurityPolicyConfig.new(new_csp)
|
|
226
224
|
end
|
|
@@ -247,11 +245,6 @@ module SecureHeaders
|
|
|
247
245
|
end
|
|
248
246
|
end
|
|
249
247
|
end
|
|
250
|
-
|
|
251
|
-
if !@csp_report_only.opt_out? && @csp.to_h == ContentSecurityPolicyConfig::DEFAULT
|
|
252
|
-
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="
|
|
253
|
-
@csp = OPT_OUT
|
|
254
|
-
end
|
|
255
248
|
end
|
|
256
249
|
|
|
257
250
|
protected
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SecureHeaders
|
|
2
3
|
class ClearSiteDataConfigError < StandardError; end
|
|
3
4
|
class ClearSiteData
|
|
@@ -7,8 +8,8 @@ module SecureHeaders
|
|
|
7
8
|
CACHE = "cache".freeze
|
|
8
9
|
COOKIES = "cookies".freeze
|
|
9
10
|
STORAGE = "storage".freeze
|
|
10
|
-
|
|
11
|
-
ALL_TYPES = [CACHE, COOKIES, STORAGE,
|
|
11
|
+
EXECUTION_CONTEXTS = "executionContexts".freeze
|
|
12
|
+
ALL_TYPES = [CACHE, COOKIES, STORAGE, EXECUTION_CONTEXTS]
|
|
12
13
|
|
|
13
14
|
CONFIG_KEY = :clear_site_data
|
|
14
15
|
|
|
@@ -16,7 +17,7 @@ module SecureHeaders
|
|
|
16
17
|
# Public: make an Clear-Site-Data header name, value pair
|
|
17
18
|
#
|
|
18
19
|
# Returns nil if not configured, returns header name and value if configured.
|
|
19
|
-
def make_header(config=nil)
|
|
20
|
+
def make_header(config = nil)
|
|
20
21
|
case config
|
|
21
22
|
when nil, OPT_OUT, []
|
|
22
23
|
# noop
|
|
@@ -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.
|
|
@@ -141,9 +136,11 @@ module SecureHeaders
|
|
|
141
136
|
else
|
|
142
137
|
@config.directive_value(directive)
|
|
143
138
|
end
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
147
144
|
end
|
|
148
145
|
|
|
149
146
|
# If a directive contains *, all other values are omitted.
|
|
@@ -264,7 +261,7 @@ module SecureHeaders
|
|
|
264
261
|
end
|
|
265
262
|
|
|
266
263
|
def symbol_to_hyphen_case(sym)
|
|
267
|
-
sym.to_s.tr(
|
|
264
|
+
sym.to_s.tr("_", "-")
|
|
268
265
|
end
|
|
269
266
|
end
|
|
270
267
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SecureHeaders
|
|
2
3
|
module DynamicConfig
|
|
3
4
|
def self.included(base)
|
|
@@ -37,7 +38,6 @@ module SecureHeaders
|
|
|
37
38
|
@script_src = nil
|
|
38
39
|
@style_nonce = nil
|
|
39
40
|
@style_src = nil
|
|
40
|
-
@worker_src = nil
|
|
41
41
|
@upgrade_insecure_requests = nil
|
|
42
42
|
|
|
43
43
|
from_hash(hash)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "cgi"
|
|
3
|
+
require "secure_headers/utils/cookies_config"
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
module SecureHeaders
|
|
5
7
|
class CookiesConfigError < StandardError; end
|
|
@@ -13,8 +15,18 @@ module SecureHeaders
|
|
|
13
15
|
|
|
14
16
|
attr_reader :raw_cookie, :config
|
|
15
17
|
|
|
18
|
+
COOKIE_DEFAULTS = {
|
|
19
|
+
httponly: true,
|
|
20
|
+
secure: true,
|
|
21
|
+
samesite: { lax: true },
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
16
24
|
def initialize(cookie, config)
|
|
17
25
|
@raw_cookie = cookie
|
|
26
|
+
unless config == OPT_OUT
|
|
27
|
+
config ||= {}
|
|
28
|
+
config = COOKIE_DEFAULTS.merge(config)
|
|
29
|
+
end
|
|
18
30
|
@config = config
|
|
19
31
|
@attributes = {
|
|
20
32
|
httponly: nil,
|
|
@@ -56,6 +68,7 @@ module SecureHeaders
|
|
|
56
68
|
end
|
|
57
69
|
|
|
58
70
|
def flag_cookie?(attribute)
|
|
71
|
+
return false if config == OPT_OUT
|
|
59
72
|
case config[attribute]
|
|
60
73
|
when TrueClass
|
|
61
74
|
true
|
|
@@ -85,6 +98,7 @@ module SecureHeaders
|
|
|
85
98
|
end
|
|
86
99
|
|
|
87
100
|
def flag_samesite?
|
|
101
|
+
return false if config == OPT_OUT || config[:samesite] == OPT_OUT
|
|
88
102
|
flag_samesite_lax? || flag_samesite_strict?
|
|
89
103
|
end
|
|
90
104
|
|
|
@@ -99,6 +113,10 @@ module SecureHeaders
|
|
|
99
113
|
def flag_samesite_enforcement?(mode)
|
|
100
114
|
return unless config[:samesite]
|
|
101
115
|
|
|
116
|
+
if config[:samesite].is_a?(TrueClass) && mode == :lax
|
|
117
|
+
return true
|
|
118
|
+
end
|
|
119
|
+
|
|
102
120
|
case config[:samesite][mode]
|
|
103
121
|
when Hash
|
|
104
122
|
conditionally_flag?(config[:samesite][mode])
|
|
@@ -113,7 +131,7 @@ module SecureHeaders
|
|
|
113
131
|
return unless cookie
|
|
114
132
|
|
|
115
133
|
cookie.split(/[;,]\s?/).each do |pairs|
|
|
116
|
-
name, values = pairs.split(
|
|
134
|
+
name, values = pairs.split("=", 2)
|
|
117
135
|
name = CGI.unescape(name)
|
|
118
136
|
|
|
119
137
|
attribute = name.downcase.to_sym
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SecureHeaders
|
|
2
3
|
module PolicyManagement
|
|
3
4
|
def self.included(base)
|
|
@@ -5,8 +6,14 @@ module SecureHeaders
|
|
|
5
6
|
end
|
|
6
7
|
|
|
7
8
|
MODERN_BROWSERS = %w(Chrome Opera Firefox)
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
DEFAULT_CONFIG = {
|
|
10
|
+
default_src: %w(https:),
|
|
11
|
+
img_src: %w(https: data: 'self'),
|
|
12
|
+
object_src: %w('none'),
|
|
13
|
+
script_src: %w(https:),
|
|
14
|
+
style_src: %w('self' 'unsafe-inline' https:),
|
|
15
|
+
form_action: %w('self')
|
|
16
|
+
}.freeze
|
|
10
17
|
DATA_PROTOCOL = "data:".freeze
|
|
11
18
|
BLOB_PROTOCOL = "blob:".freeze
|
|
12
19
|
SELF = "'self'".freeze
|
|
@@ -65,13 +72,10 @@ module SecureHeaders
|
|
|
65
72
|
BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
|
|
66
73
|
MANIFEST_SRC = :manifest_src
|
|
67
74
|
UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
|
|
68
|
-
WORKER_SRC = :worker_src
|
|
69
|
-
|
|
70
75
|
DIRECTIVES_3_0 = [
|
|
71
76
|
DIRECTIVES_2_0,
|
|
72
77
|
BLOCK_ALL_MIXED_CONTENT,
|
|
73
78
|
MANIFEST_SRC,
|
|
74
|
-
WORKER_SRC,
|
|
75
79
|
UPGRADE_INSECURE_REQUESTS
|
|
76
80
|
].flatten.freeze
|
|
77
81
|
|
|
@@ -82,7 +86,6 @@ module SecureHeaders
|
|
|
82
86
|
FIREFOX_UNSUPPORTED_DIRECTIVES = [
|
|
83
87
|
BLOCK_ALL_MIXED_CONTENT,
|
|
84
88
|
CHILD_SRC,
|
|
85
|
-
WORKER_SRC,
|
|
86
89
|
PLUGIN_TYPES
|
|
87
90
|
].freeze
|
|
88
91
|
|
|
@@ -92,7 +95,6 @@ module SecureHeaders
|
|
|
92
95
|
|
|
93
96
|
FIREFOX_46_UNSUPPORTED_DIRECTIVES = [
|
|
94
97
|
BLOCK_ALL_MIXED_CONTENT,
|
|
95
|
-
WORKER_SRC,
|
|
96
98
|
PLUGIN_TYPES
|
|
97
99
|
].freeze
|
|
98
100
|
|
|
@@ -146,7 +148,6 @@ module SecureHeaders
|
|
|
146
148
|
SANDBOX => :sandbox_list,
|
|
147
149
|
SCRIPT_SRC => :source_list,
|
|
148
150
|
STYLE_SRC => :source_list,
|
|
149
|
-
WORKER_SRC => :source_list,
|
|
150
151
|
UPGRADE_INSECURE_REQUESTS => :boolean
|
|
151
152
|
}.freeze
|
|
152
153
|
|
|
@@ -205,6 +206,10 @@ module SecureHeaders
|
|
|
205
206
|
def validate_config!(config)
|
|
206
207
|
return if config.nil? || config.opt_out?
|
|
207
208
|
raise ContentSecurityPolicyConfigError.new(":default_src is required") unless config.directive_value(:default_src)
|
|
209
|
+
if config.directive_value(:script_src).nil?
|
|
210
|
+
raise ContentSecurityPolicyConfigError.new(":script_src is required, falling back to default-src is too dangerous. Use `script_src: OPT_OUT` to override")
|
|
211
|
+
end
|
|
212
|
+
|
|
208
213
|
ContentSecurityPolicyConfig.attrs.each do |key|
|
|
209
214
|
value = config.directive_value(key)
|
|
210
215
|
next unless value
|
|
@@ -389,6 +394,7 @@ module SecureHeaders
|
|
|
389
394
|
end
|
|
390
395
|
|
|
391
396
|
def ensure_valid_sources!(directive, source_expression)
|
|
397
|
+
return if source_expression == OPT_OUT
|
|
392
398
|
source_expression.each do |expression|
|
|
393
399
|
if ContentSecurityPolicy::DEPRECATED_SOURCE_VALUES.include?(expression)
|
|
394
400
|
raise ContentSecurityPolicyConfigError.new("#{directive} contains an invalid keyword source (#{expression}). This value must be single quoted.")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SecureHeaders
|
|
2
3
|
class PublicKeyPinsConfigError < StandardError; end
|
|
3
4
|
class PublicKeyPins
|
|
@@ -54,7 +55,7 @@ module SecureHeaders
|
|
|
54
55
|
pin_directives,
|
|
55
56
|
report_uri_directive,
|
|
56
57
|
subdomain_directive
|
|
57
|
-
].compact.join(
|
|
58
|
+
].compact.join("; ").strip
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def pin_directives
|
|
@@ -63,7 +64,7 @@ module SecureHeaders
|
|
|
63
64
|
pin.map do |token, hash|
|
|
64
65
|
"pin-#{token}=\"#{hash}\"" if HASH_ALGORITHMS.include?(token)
|
|
65
66
|
end
|
|
66
|
-
end.join(
|
|
67
|
+
end.join("; ")
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
def max_age_directive
|
|
@@ -75,7 +76,7 @@ module SecureHeaders
|
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
def subdomain_directive
|
|
78
|
-
@include_subdomains ?
|
|
79
|
+
@include_subdomains ? "includeSubDomains" : nil
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
82
|
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SecureHeaders
|
|
2
3
|
class STSConfigError < StandardError; end
|
|
3
4
|
|
|
4
5
|
class StrictTransportSecurity
|
|
5
|
-
HEADER_NAME =
|
|
6
|
+
HEADER_NAME = "Strict-Transport-Security".freeze
|
|
6
7
|
HSTS_MAX_AGE = "631138519"
|
|
7
8
|
DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
|
|
8
9
|
VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module SecureHeaders
|
|
2
3
|
class XDOConfigError < StandardError; end
|
|
3
4
|
class XDownloadOptions
|
|
4
5
|
HEADER_NAME = "X-Download-Options".freeze
|
|
5
|
-
DEFAULT_VALUE =
|
|
6
|
+
DEFAULT_VALUE = "noopen"
|
|
6
7
|
CONFIG_KEY = :x_download_options
|
|
7
8
|
|
|
8
9
|
class << self
|