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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +3 -0
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +8 -6
  6. data/CHANGELOG.md +21 -1
  7. data/CONTRIBUTING.md +3 -5
  8. data/Gemfile +7 -4
  9. data/Guardfile +1 -0
  10. data/README.md +15 -19
  11. data/Rakefile +22 -18
  12. data/docs/cookies.md +16 -2
  13. data/docs/hashes.md +1 -1
  14. data/docs/per_action_configuration.md +28 -0
  15. data/lib/secure_headers/configuration.rb +29 -16
  16. data/lib/secure_headers/hash_helper.rb +2 -1
  17. data/lib/secure_headers/headers/clear_site_data.rb +16 -12
  18. data/lib/secure_headers/headers/content_security_policy.rb +52 -20
  19. data/lib/secure_headers/headers/content_security_policy_config.rb +25 -0
  20. data/lib/secure_headers/headers/cookie.rb +21 -3
  21. data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
  22. data/lib/secure_headers/headers/policy_management.rb +115 -68
  23. data/lib/secure_headers/headers/public_key_pins.rb +5 -4
  24. data/lib/secure_headers/headers/referrer_policy.rb +1 -0
  25. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  26. data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
  27. data/lib/secure_headers/headers/x_download_options.rb +2 -1
  28. data/lib/secure_headers/headers/x_frame_options.rb +1 -0
  29. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -1
  30. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  31. data/lib/secure_headers/middleware.rb +10 -9
  32. data/lib/secure_headers/railtie.rb +7 -6
  33. data/lib/secure_headers/utils/cookies_config.rb +13 -12
  34. data/lib/secure_headers/view_helper.rb +4 -3
  35. data/lib/secure_headers.rb +6 -2
  36. data/lib/tasks/tasks.rake +2 -1
  37. data/secure_headers.gemspec +13 -3
  38. data/spec/lib/secure_headers/configuration_spec.rb +9 -8
  39. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +11 -27
  40. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +42 -26
  41. data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
  42. data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
  43. data/spec/lib/secure_headers/headers/policy_management_spec.rb +57 -10
  44. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
  45. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
  46. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
  47. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
  48. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
  49. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
  50. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
  51. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
  52. data/spec/lib/secure_headers/middleware_spec.rb +29 -21
  53. data/spec/lib/secure_headers/view_helpers_spec.rb +5 -5
  54. data/spec/lib/secure_headers_spec.rb +96 -124
  55. data/spec/spec_helper.rb +10 -22
  56. data/upgrading-to-4-0.md +55 -0
  57. metadata +16 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fec7efedf547e7aff5f7193f3306802367df524e
4
- data.tar.gz: 5d4a99a2f5989e2c119b6d8be2ed232850635855
3
+ metadata.gz: de4e0f18558ac4a65a9983f56e4dd65ca95e5f82
4
+ data.tar.gz: 3c16a5bdeec36aab812bcbfbbdba544b969c3a33
5
5
  SHA512:
6
- metadata.gz: d5ca176a980b3acf9d97442d13e6109c5a8e8043f796733b3ffbee16f45554acd1f46ee2874fd3099be4f9217618b1591e75ec1ac6c7bc7e2e49acce3e06dbcf
7
- data.tar.gz: 9881f2a604975c4d1f64b4cbc690c063751b8f573f0db87a5c2593326fa5ffcdb83e533d95d9a1ce46551d97192c7faa48e6a5f9485765252d25f53b66d168f1
6
+ metadata.gz: 14f3bc42066b1b4bd6044b75a78818a6ce6edc60760e84af03d13623758a4d05b9397d0c4b4baa92c71d105e0334fa39ec56124c3014e8a63096236f9c6e6d51
7
+ data.tar.gz: f07cfc4d11b24a7d7ec1ad50b2fb756fd3a69c8558b922eb79daf35bbf5dc721c24ca27dfa8b5ca13865dc3345cb0162161f3c41fa04ffbff8c190908d8a7b32
data/.rspec CHANGED
@@ -1 +1,3 @@
1
1
  --order rand
2
+ --warnings
3
+ --format progress
data/.rubocop.yml ADDED
@@ -0,0 +1,3 @@
1
+ inherit_gem:
2
+ rubocop-github:
3
+ - config/default.yml
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.3.3
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.0
6
- - 2.3.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/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.
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 darrrr.gemspec`
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 "tins", "~> 1.6.0" # 1.7 requires ruby 2.0
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 "coveralls"
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
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  guard :rspec, cmd: "bundle exec rspec", all_on_start: true, all_after_pass: true do
2
3
  require "guard/rspec/dsl"
3
4
  dsl = Guard::RSpec::Dsl.new(self)
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Secure Headers [![Build Status](https://travis-ci.org/twitter/secureheaders.svg?branch=master)](http://travis-ci.org/twitter/secureheaders) [![Code Climate](https://codeclimate.com/github/twitter/secureheaders.svg)](https://codeclimate.com/github/twitter/secureheaders) [![Coverage Status](https://coveralls.io/repos/twitter/secureheaders/badge.svg)](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 was recently merged**. See the [upgrading to 3.x doc](upgrading-to-3-0.md) for instructions on how to upgrade including the differences and benefits of using the 3.x branch.
5
+ **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
- - Clear-Site-Data - Clearing browser data for origin. [Clear-Site-Data specification](https://www.w3.org/TR/clear-site-data/).
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 shaped the header, but the values are not included in the header.
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
- require 'bundler/gem_tasks'
3
- require 'rspec/core/rake_task'
4
- require 'net/http'
5
- require 'net/https'
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
- desc "Run RSpec"
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 'rdoc/task'
11
+ require "rdoc/task"
17
12
  rescue LoadError
18
- require 'rdoc/rdoc'
19
- require 'rake/rdoctask'
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 = 'rdoc'
25
- rdoc.title = 'SecureHeaders'
26
- rdoc.options << '--line-numbers'
27
- rdoc.rdoc_files.include('lib/**/*.rb')
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: false, # do not mark any cookies as 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/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.
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
- require 'yaml'
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 &block if block_given?
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.exists?(HASH_CONFIG_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
- instance_eval &block if block_given?
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
- Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#secure_cookies=` is deprecated. Please use `#cookies=` to configure secure cookies instead."
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
- # Deprecated configuration implies that CSPRO should be set, CSP should not - so opt out
201
- Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#csp=` was supplied a config with report_only: true. Use #csp_report_only="
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,4 +1,5 @@
1
- require 'base64'
1
+ # frozen_string_literal: true
2
+ require "base64"
2
3
 
3
4
  module SecureHeaders
4
5
  module HashHelper
@@ -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
- EXECTION_CONTEXTS = "executionContexts".freeze
12
- ALL_TYPES = [CACHE, COOKIES, STORAGE, EXECTION_CONTEXTS]
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, JSON.dump(TYPES => config)]
25
+ [HEADER_NAME, make_header_value(config)]
26
26
  when true
27
- [HEADER_NAME, JSON.dump(TYPES => ALL_TYPES)]
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
- require_relative 'policy_management'
2
- require_relative 'content_security_policy_config'
3
- require 'useragent'
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
- Kernel.warn("#{Kernel.caller.first}: [DEPRECATION] both :child_src and :frame_src supplied and do not match. This can lead to inconsistent behavior across browsers.")
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
- if supported_directives.include?(:child_src)
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 :string
84
- [symbol_to_hyphen_case(directive_name), @config.directive_value(directive_name)].join(" ")
85
- else
86
- build_directive(directive_name)
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 build_directive(directive)
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
- return unless source_list && source_list.any?
110
- normalized_source_list = minify_source_list(directive, source_list)
111
- [symbol_to_hyphen_case(directive), normalized_source_list].join(" ")
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