secure_headers 6.3.4 → 7.3.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/CHANGELOG.md +10 -2
- data/Gemfile +2 -0
- data/README.md +81 -15
- data/lib/secure_headers/configuration.rb +64 -13
- data/lib/secure_headers/headers/clear_site_data.rb +31 -33
- data/lib/secure_headers/headers/content_security_policy.rb +80 -46
- data/lib/secure_headers/headers/content_security_policy_config.rb +17 -58
- data/lib/secure_headers/headers/cookie.rb +4 -6
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +20 -22
- data/lib/secure_headers/headers/policy_management.rb +66 -18
- data/lib/secure_headers/headers/referrer_policy.rb +20 -22
- data/lib/secure_headers/headers/reporting_endpoints.rb +54 -0
- data/lib/secure_headers/headers/strict_transport_security.rb +13 -15
- data/lib/secure_headers/headers/x_content_type_options.rb +14 -16
- data/lib/secure_headers/headers/x_download_options.rb +14 -16
- data/lib/secure_headers/headers/x_frame_options.rb +14 -16
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +14 -16
- data/lib/secure_headers/headers/x_xss_protection.rb +14 -16
- data/lib/secure_headers/middleware.rb +11 -7
- data/lib/secure_headers/railtie.rb +11 -8
- data/lib/secure_headers/task_helper.rb +65 -0
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers/view_helper.rb +7 -6
- data/lib/secure_headers.rb +26 -2
- data/lib/tasks/tasks.rake +4 -53
- data/secure_headers.gemspec +19 -7
- metadata +38 -70
- data/.github/ISSUE_TEMPLATE.md +0 -41
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -20
- data/.github/workflows/build.yml +0 -24
- data/.github/workflows/github-release.yml +0 -28
- data/.gitignore +0 -13
- data/.rspec +0 -3
- data/.rubocop.yml +0 -4
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/CODE_OF_CONDUCT.md +0 -46
- data/CONTRIBUTING.md +0 -41
- data/Guardfile +0 -13
- data/Rakefile +0 -32
- data/docs/cookies.md +0 -65
- data/docs/hashes.md +0 -64
- data/docs/named_overrides_and_appends.md +0 -104
- data/docs/per_action_configuration.md +0 -139
- data/docs/sinatra.md +0 -25
- data/docs/upgrading-to-3-0.md +0 -42
- data/docs/upgrading-to-4-0.md +0 -35
- data/docs/upgrading-to-5-0.md +0 -15
- data/docs/upgrading-to-6-0.md +0 -50
- data/spec/lib/secure_headers/configuration_spec.rb +0 -121
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +0 -87
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +0 -190
- data/spec/lib/secure_headers/headers/cookie_spec.rb +0 -179
- data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +0 -42
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +0 -264
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +0 -91
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +0 -33
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +0 -31
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +0 -29
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +0 -36
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +0 -48
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +0 -47
- data/spec/lib/secure_headers/middleware_spec.rb +0 -117
- data/spec/lib/secure_headers/view_helpers_spec.rb +0 -191
- data/spec/lib/secure_headers_spec.rb +0 -516
- data/spec/spec_helper.rb +0 -64
data/docs/upgrading-to-3-0.md
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
`secure_headers` 3.0 is a near-complete rewrite. It includes breaking changes and removes a lot of features that were either leftover from the days when the CSP standard was not fully adopted or were just downright confusing.
|
|
2
|
-
|
|
3
|
-
Changes
|
|
4
|
-
==
|
|
5
|
-
|
|
6
|
-
| What | < = 2.x | >= 3.0 |
|
|
7
|
-
| ---------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
-
| Global configuration | `SecureHeaders::Configuration.configure` block | `SecureHeaders::Configuration.default` block |
|
|
9
|
-
| All headers besides HPKP and CSP | Accept hashes as config values | Must be strings (validated during configuration) |
|
|
10
|
-
| CSP directive values | Accepted space delimited strings OR arrays of strings | Must be arrays of strings |
|
|
11
|
-
| CSP Nonce values in views | `@content_security_policy_nonce` | `content_security_policy_nonce(:script)` or `content_security_policy_nonce(:style)` |
|
|
12
|
-
| nonce is no longer a source expression | `config.csp = "'self' 'nonce'"` | Remove `'nonce'` from source expression and use [nonce helpers](https://github.com/twitter/secureheaders#nonce). |
|
|
13
|
-
| `self`/`none` source expressions | Could be `self` / `none` / `'self'` / `'none'` | Must be `'self'` or `'none'` |
|
|
14
|
-
| `inline` / `eval` source expressions | Could be `inline`, `eval`, `'unsafe-inline'`, or `'unsafe-eval'` | Must be `'unsafe-eval'` or `'unsafe-inline'` |
|
|
15
|
-
| Per-action configuration | Override [`def secure_header_options_for(header, options)`](https://github.com/twitter/secureheaders/commit/bb9ebc6c12a677aad29af8e0f08ffd1def56efec#diff-04c6e90faac2675aa89e2176d2eec7d8R111) | Use [named overrides](https://github.com/twitter/secureheaders#named-overrides) or [per-action helpers](https://github.com/twitter/secureheaders#per-action-configuration) |
|
|
16
|
-
| CSP/HPKP use `report_only` config that defaults to false | `enforce: false` | `report_only: false` |
|
|
17
|
-
| Schemes in source expressions | Schemes were not stripped | Schemes are stripped by default to discourage mixed content. Setting `preserve_schemes: true` will revert to previous behavior |
|
|
18
|
-
| Opting out of default configuration | `skip_before_filter :set_x_download_options_header` or `config.x_download_options = false` | Within default block: `config.x_download_options = SecureHeaders::OPT_OUT` |
|
|
19
|
-
|
|
20
|
-
Migrating to 3.x from <= 2.x
|
|
21
|
-
==
|
|
22
|
-
|
|
23
|
-
1. Convert all headers except for CSP/HPKP using hashes to string values. The values are validated at runtime and will provide guidance on misconfigured headers.
|
|
24
|
-
1. Convert all instances of `self`/`none`/`eval`/`inline` to the corresponding values in the above table.
|
|
25
|
-
1. Convert all CSP space-delimited directives to an array of strings.
|
|
26
|
-
1. Convert all `enforce: true|false` to `report_only: true|false`.
|
|
27
|
-
1. Remove `ensure_security_headers` from controllers (3.x uses a middleware instead).
|
|
28
|
-
|
|
29
|
-
Everything is terrible, why should I upgrade?
|
|
30
|
-
==
|
|
31
|
-
|
|
32
|
-
`secure_headers` <= 2.x built every header per request using a series of automatically included `before_filters`. This is horribly inefficient because:
|
|
33
|
-
|
|
34
|
-
1. `before_filters` are slow and adding 8 per request isn't great
|
|
35
|
-
1. We are rebuilding strings that may never change for every request
|
|
36
|
-
1. Errors in the request may mean that the headers never get set in the first place
|
|
37
|
-
|
|
38
|
-
`secure_headers` 3.x sets headers in rack middleware that runs once per request and uses configuration values passed via `request.env`. This is much more efficient and somewhat guarantees that headers will always be set. **The values for the headers are cached and reused per request**.
|
|
39
|
-
|
|
40
|
-
Also, there is a more flexible API for customizing content security policies / X-Frame-Options. In practice, none of the other headers need granular controls. One way of customizing headers per request is to use the helper methods. The only downside of this technique is that headers will be computed from scratch.
|
|
41
|
-
|
|
42
|
-
See the [README](README.md) for more information.
|
data/docs/upgrading-to-4-0.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
## script_src must be set
|
|
2
|
-
|
|
3
|
-
Not setting a `script_src` value means your policy falls back to whatever `default_src` (also required) is set to. This can be very dangerous and indicates the policy is too loose.
|
|
4
|
-
|
|
5
|
-
However, sometimes you really don't need a `script-src` e.g. API responses (`default-src 'none'`) so you can set `script_src: SecureHeaders::OPT_OUT` to work around this.
|
|
6
|
-
|
|
7
|
-
## Default Content Security Policy
|
|
8
|
-
|
|
9
|
-
The default CSP has changed to be more universal without sacrificing too much security.
|
|
10
|
-
|
|
11
|
-
* Flash/Java disabled by default
|
|
12
|
-
* `img-src` allows data: images and favicons (among others)
|
|
13
|
-
* `style-src` allows inline CSS by default (most find it impossible/impractical to remove inline content today)
|
|
14
|
-
* `form-action` (not governed by `default-src`, practically treated as `*`) is set to `'self'`
|
|
15
|
-
|
|
16
|
-
Previously, the default CSP was:
|
|
17
|
-
|
|
18
|
-
`Content-Security-Policy: default-src 'self'`
|
|
19
|
-
|
|
20
|
-
The new default policy is:
|
|
21
|
-
|
|
22
|
-
`default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:`
|
|
23
|
-
|
|
24
|
-
## CSP configuration
|
|
25
|
-
|
|
26
|
-
* Setting `report_only: true` in a CSP config will raise an error. Instead, set `csp_report_only`.
|
|
27
|
-
* Setting `frame_src` and `child_src` when values don't match will raise an error. Just use `frame_src`.
|
|
28
|
-
|
|
29
|
-
## config.secure_cookies removed
|
|
30
|
-
|
|
31
|
-
Use `config.cookies` instead.
|
|
32
|
-
|
|
33
|
-
## Supported ruby versions
|
|
34
|
-
|
|
35
|
-
We've dropped support for ruby versions <= 2.2. Sorry.
|
data/docs/upgrading-to-5-0.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
## All cookies default to secure/httponly/SameSite=Lax
|
|
2
|
-
|
|
3
|
-
By default, *all* cookies will be marked as `SameSite=lax`,`secure`, and `httponly`. To opt-out, supply `SecureHeaders::OPT_OUT` as the value for `SecureHeaders.cookies` or the individual configs. Setting these values to `false` will raise an error.
|
|
4
|
-
|
|
5
|
-
```ruby
|
|
6
|
-
# specific opt outs
|
|
7
|
-
config.cookies = {
|
|
8
|
-
secure: SecureHeaders::OPT_OUT,
|
|
9
|
-
httponly: SecureHeaders::OPT_OUT,
|
|
10
|
-
samesite: SecureHeaders::OPT_OUT,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
# nuclear option, just make things work again
|
|
14
|
-
config.cookies = SecureHeaders::OPT_OUT
|
|
15
|
-
```
|
data/docs/upgrading-to-6-0.md
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
## Named overrides are now dynamically applied
|
|
2
|
-
|
|
3
|
-
The original implementation of name overrides worked by making a copy of the default policy, applying the overrides, and storing the result for later use. But, this lead to unexpected results if named overrides were combined with a dynamic policy change. If a change was made to the default configuration during a request, followed by a named override, the dynamic changes would be lost. To keep things consistent named overrides have been rewritten to work the same as named appends in that they always operate on the configuration for the current request. As an example:
|
|
4
|
-
|
|
5
|
-
```ruby
|
|
6
|
-
class ApplicationController < ActionController::Base
|
|
7
|
-
Configuration.default do |config|
|
|
8
|
-
config.x_frame_options = SecureHeaders::OPT_OUT
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
SecureHeaders::Configuration.override(:dynamic_override) do |config|
|
|
12
|
-
config.x_content_type_options = "nosniff"
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
class FooController < ApplicationController
|
|
17
|
-
def bar
|
|
18
|
-
# Dynamically update the default config for this request
|
|
19
|
-
override_x_frame_options("DENY")
|
|
20
|
-
append_content_security_policy_directives(frame_src: "3rdpartyprovider.com")
|
|
21
|
-
|
|
22
|
-
# Override everything, discard modifications above
|
|
23
|
-
use_secure_headers_override(:dynamic_override)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Prior to 6.0.0, the response would NOT include a `X-Frame-Options` header since the named override would be a copy of the default configuration, but with `X-Content-Type-Options` set to `nosniff`. As of 6.0.0, the above code results in both `X-Frame-Options` set to `DENY` AND `X-Content-Type-Options` set to `nosniff`.
|
|
29
|
-
|
|
30
|
-
## `ContentSecurityPolicyConfig#merge` and `ContentSecurityPolicyReportOnlyConfig#merge` work more like `Hash#merge`
|
|
31
|
-
|
|
32
|
-
These classes are typically not directly instantiated by users of SecureHeaders. But, if you access `config.csp` you end up accessing one of these objects. Prior to 6.0.0, `#merge` worked more like `#append` in that it would combine policies (i.e. if both policies contained the same key the values would be combined rather than overwritten). This was not consistent with `#merge!`, which worked more like Ruby's `Hash#merge!` (overwriting duplicate keys). As of 6.0.0, `#merge` works the same as `#merge!`, but returns a new object instead of mutating `self`.
|
|
33
|
-
|
|
34
|
-
## `Configuration#get` has been removed
|
|
35
|
-
|
|
36
|
-
This method is not typically directly called by users of SecureHeaders. Given that named overrides are no longer statically stored, fetching them no longer makes sense.
|
|
37
|
-
|
|
38
|
-
## Configuration headers are no longer cached
|
|
39
|
-
|
|
40
|
-
Prior to 6.0.0 SecureHeaders pre-built and cached the headers that corresponded to the default configuration. The same was also done for named overrides. However, now that named overrides are applied dynamically, those can no longer be cached. As a result, caching has been removed in the name of simplicity. Some micro-benchmarks indicate this shouldn't be a performance problem and will help to eliminate a class of bugs entirely.
|
|
41
|
-
|
|
42
|
-
## Calling the default configuration more than once will result in an Exception
|
|
43
|
-
|
|
44
|
-
Prior to 6.0.0 you could conceivably, though unlikely, have `Configure#default` called more than once. Because configurations are dynamic, configuring more than once could result in unexpected behavior. So, as of 6.0.0 we raise `AlreadyConfiguredError` if the default configuration is setup more than once.
|
|
45
|
-
|
|
46
|
-
## All user agent sniffing has been removed
|
|
47
|
-
|
|
48
|
-
The policy configured is the policy that is delivered in terms of which directives are sent. We still dedup, strip schemes, and look for other optimizations but we will not e.g. conditionally send `frame-src` / `child-src` or apply `nonce`s / `unsafe-inline`.
|
|
49
|
-
|
|
50
|
-
The primary reason for these per-browser customization was to reduce console warnings. This has lead to many bugs and results in confusing behavior. Also, console logs are incredibly noisy today and increasingly warn you about perfectly valid things (like sending `X-Frame-Options` and `frame-ancestors` together).
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe Configuration do
|
|
6
|
-
before(:each) do
|
|
7
|
-
reset_config
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
it "has a default config" do
|
|
11
|
-
expect(Configuration.default).to_not be_nil
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it "has an 'noop' override" do
|
|
15
|
-
Configuration.default
|
|
16
|
-
expect(Configuration.overrides(Configuration::NOOP_OVERRIDE)).to_not be_nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "dup results in a copy of the default config" do
|
|
20
|
-
Configuration.default
|
|
21
|
-
original_configuration = Configuration.send(:default_config)
|
|
22
|
-
configuration = Configuration.dup
|
|
23
|
-
expect(original_configuration).not_to be(configuration)
|
|
24
|
-
Configuration::CONFIG_ATTRIBUTES.each do |attr|
|
|
25
|
-
expect(original_configuration.send(attr)).to eq(configuration.send(attr))
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "stores an override" do
|
|
30
|
-
Configuration.override(:test_override) do |config|
|
|
31
|
-
config.x_frame_options = "DENY"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
expect(Configuration.overrides(:test_override)).to_not be_nil
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
describe "#override" do
|
|
38
|
-
it "raises on configuring an existing override" do
|
|
39
|
-
set_override = Proc.new {
|
|
40
|
-
Configuration.override(:test_override) do |config|
|
|
41
|
-
config.x_frame_options = "DENY"
|
|
42
|
-
end
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
set_override.call
|
|
46
|
-
|
|
47
|
-
expect { set_override.call }
|
|
48
|
-
.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it "raises when a named append with the given name exists" do
|
|
52
|
-
Configuration.named_append(:test_override) do |config|
|
|
53
|
-
config.x_frame_options = "DENY"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
expect do
|
|
57
|
-
Configuration.override(:test_override) do |config|
|
|
58
|
-
config.x_frame_options = "SAMEORIGIN"
|
|
59
|
-
end
|
|
60
|
-
end.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
describe "#named_append" do
|
|
65
|
-
it "raises on configuring an existing append" do
|
|
66
|
-
set_override = Proc.new {
|
|
67
|
-
Configuration.named_append(:test_override) do |config|
|
|
68
|
-
config.x_frame_options = "DENY"
|
|
69
|
-
end
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
set_override.call
|
|
73
|
-
|
|
74
|
-
expect { set_override.call }
|
|
75
|
-
.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "raises when an override with the given name exists" do
|
|
79
|
-
Configuration.override(:test_override) do |config|
|
|
80
|
-
config.x_frame_options = "DENY"
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
expect do
|
|
84
|
-
Configuration.named_append(:test_override) do |config|
|
|
85
|
-
config.x_frame_options = "SAMEORIGIN"
|
|
86
|
-
end
|
|
87
|
-
end.to raise_error(Configuration::AlreadyConfiguredError, "Configuration already exists")
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it "deprecates the secure_cookies configuration" do
|
|
92
|
-
expect {
|
|
93
|
-
Configuration.default do |config|
|
|
94
|
-
config.secure_cookies = true
|
|
95
|
-
end
|
|
96
|
-
}.to raise_error(ArgumentError)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
it "gives cookies a default config" do
|
|
100
|
-
expect(Configuration.default.cookies).to eq({httponly: true, secure: true, samesite: {lax: true}})
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
it "allows OPT_OUT" do
|
|
104
|
-
Configuration.default do |config|
|
|
105
|
-
config.cookies = OPT_OUT
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
config = Configuration.dup
|
|
109
|
-
expect(config.cookies).to eq(OPT_OUT)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
it "allows me to be explicit too" do
|
|
113
|
-
Configuration.default do |config|
|
|
114
|
-
config.cookies = {httponly: true, secure: true, samesite: {lax: false}}
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
config = Configuration.dup
|
|
118
|
-
expect(config.cookies).to eq({httponly: true, secure: true, samesite: {lax: false}})
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe ClearSiteData do
|
|
6
|
-
describe "make_header" do
|
|
7
|
-
it "returns nil with nil config" do
|
|
8
|
-
expect(described_class.make_header).to be_nil
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it "returns nil with empty config" do
|
|
12
|
-
expect(described_class.make_header([])).to be_nil
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
it "returns nil with opt-out config" do
|
|
16
|
-
expect(described_class.make_header(OPT_OUT)).to be_nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "returns all types with `true` config" do
|
|
20
|
-
name, value = described_class.make_header(true)
|
|
21
|
-
|
|
22
|
-
expect(name).to eq(ClearSiteData::HEADER_NAME)
|
|
23
|
-
expect(value).to eq(
|
|
24
|
-
%("cache", "cookies", "storage", "executionContexts")
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
it "returns specified types" do
|
|
29
|
-
name, value = described_class.make_header(["foo", "bar"])
|
|
30
|
-
|
|
31
|
-
expect(name).to eq(ClearSiteData::HEADER_NAME)
|
|
32
|
-
expect(value).to eq(%("foo", "bar"))
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
describe "validate_config!" do
|
|
37
|
-
it "succeeds for `true` config" do
|
|
38
|
-
expect do
|
|
39
|
-
described_class.validate_config!(true)
|
|
40
|
-
end.not_to raise_error
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
it "succeeds for `nil` config" do
|
|
44
|
-
expect do
|
|
45
|
-
described_class.validate_config!(nil)
|
|
46
|
-
end.not_to raise_error
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
it "succeeds for opt-out config" do
|
|
50
|
-
expect do
|
|
51
|
-
described_class.validate_config!(OPT_OUT)
|
|
52
|
-
end.not_to raise_error
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it "succeeds for empty config" do
|
|
56
|
-
expect do
|
|
57
|
-
described_class.validate_config!([])
|
|
58
|
-
end.not_to raise_error
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it "succeeds for Array of Strings config" do
|
|
62
|
-
expect do
|
|
63
|
-
described_class.validate_config!(["foo"])
|
|
64
|
-
end.not_to raise_error
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it "fails for Array of non-String config" do
|
|
68
|
-
expect do
|
|
69
|
-
described_class.validate_config!([1])
|
|
70
|
-
end.to raise_error(ClearSiteDataConfigError)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it "fails for other types of config" do
|
|
74
|
-
expect do
|
|
75
|
-
described_class.validate_config!(:cookies)
|
|
76
|
-
end.to raise_error(ClearSiteDataConfigError)
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
describe "make_header_value" do
|
|
81
|
-
it "returns a string of quoted values that are comma separated" do
|
|
82
|
-
value = described_class.make_header_value(["foo", "bar"])
|
|
83
|
-
expect(value).to eq(%("foo", "bar"))
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe ContentSecurityPolicy do
|
|
6
|
-
let (:default_opts) do
|
|
7
|
-
{
|
|
8
|
-
default_src: %w(https:),
|
|
9
|
-
img_src: %w(https: data:),
|
|
10
|
-
script_src: %w('unsafe-inline' 'unsafe-eval' https: data:),
|
|
11
|
-
style_src: %w('unsafe-inline' https: about:),
|
|
12
|
-
report_uri: %w(/csp_report)
|
|
13
|
-
}
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
describe "#name" do
|
|
17
|
-
context "when in report-only mode" do
|
|
18
|
-
specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME) }
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
context "when in enforce mode" do
|
|
22
|
-
specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicyConfig::HEADER_NAME) }
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
describe "#value" do
|
|
27
|
-
it "uses a safe but non-breaking default value" do
|
|
28
|
-
expect(ContentSecurityPolicy.new.value).to eq("default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it "deprecates and escapes semicolons in directive source lists" do
|
|
32
|
-
expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a ; in "google.com;script-src *;.;" which will raise an error in future versions. It has been replaced with a blank space.))
|
|
33
|
-
expect(ContentSecurityPolicy.new(frame_ancestors: %w(https://google.com;script-src https://*;.;)).value).to eq("frame-ancestors google.com script-src * .")
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it "deprecates and escapes semicolons in directive source lists" do
|
|
37
|
-
expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a \n in "\\nfoo.com\\nhacked" which will raise an error in future versions. It has been replaced with a blank space.))
|
|
38
|
-
expect(ContentSecurityPolicy.new(frame_ancestors: ["\nfoo.com\nhacked"]).value).to eq("frame-ancestors foo.com hacked")
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it "discards 'none' values if any other source expressions are present" do
|
|
42
|
-
csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
|
|
43
|
-
expect(csp.value).not_to include("'none'")
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it "discards source expressions (besides unsafe-* and non-host source values) when * is present" do
|
|
47
|
-
csp = ContentSecurityPolicy.new(default_src: %w(* 'unsafe-inline' 'unsafe-eval' http: https: example.org data: blob:))
|
|
48
|
-
expect(csp.value).to eq("default-src * 'unsafe-inline' 'unsafe-eval' data: blob:")
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it "minifies source expressions based on overlapping wildcards" do
|
|
52
|
-
config = {
|
|
53
|
-
default_src: %w(a.example.org b.example.org *.example.org https://*.example.org)
|
|
54
|
-
}
|
|
55
|
-
csp = ContentSecurityPolicy.new(config)
|
|
56
|
-
expect(csp.value).to eq("default-src *.example.org")
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
it "removes http/s schemes from hosts" do
|
|
60
|
-
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org))
|
|
61
|
-
expect(csp.value).to eq("default-src example.org")
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it "does not build directives with a value of OPT_OUT (and bypasses directive requirements)" do
|
|
65
|
-
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), script_src: OPT_OUT)
|
|
66
|
-
expect(csp.value).to eq("default-src example.org")
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it "does not remove schemes from report-uri values" do
|
|
70
|
-
csp = ContentSecurityPolicy.new(default_src: %w(https:), report_uri: %w(https://example.org))
|
|
71
|
-
expect(csp.value).to eq("default-src https:; report-uri https://example.org")
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
it "does not remove schemes when :preserve_schemes is true" do
|
|
75
|
-
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), preserve_schemes: true)
|
|
76
|
-
expect(csp.value).to eq("default-src https://example.org")
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
it "removes nil from source lists" do
|
|
80
|
-
csp = ContentSecurityPolicy.new(default_src: ["https://example.org", nil])
|
|
81
|
-
expect(csp.value).to eq("default-src example.org")
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
it "does not add a directive if the value is an empty array (or all nil)" do
|
|
85
|
-
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], script_src: [nil])
|
|
86
|
-
expect(csp.value).to eq("default-src example.org")
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
it "does not add a directive if the value is nil" do
|
|
90
|
-
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], script_src: nil)
|
|
91
|
-
expect(csp.value).to eq("default-src example.org")
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
it "does add a boolean directive if the value is true" do
|
|
95
|
-
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: true)
|
|
96
|
-
expect(csp.value).to eq("default-src example.org; block-all-mixed-content; upgrade-insecure-requests")
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
it "does not add a boolean directive if the value is false" do
|
|
100
|
-
csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: false)
|
|
101
|
-
expect(csp.value).to eq("default-src example.org; block-all-mixed-content")
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
it "deduplicates any source expressions" do
|
|
105
|
-
csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
|
|
106
|
-
expect(csp.value).to eq("default-src example.org")
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
it "does not deduplicate non-matching schema source expressions" do
|
|
110
|
-
csp = ContentSecurityPolicy.new(default_src: %w(*.example.org wss://example.example.org))
|
|
111
|
-
expect(csp.value).to eq("default-src *.example.org wss://example.example.org")
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
it "creates maximally strict sandbox policy when passed no sandbox token values" do
|
|
115
|
-
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: [])
|
|
116
|
-
expect(csp.value).to eq("default-src example.org; sandbox")
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
it "creates maximally strict sandbox policy when passed true" do
|
|
120
|
-
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: true)
|
|
121
|
-
expect(csp.value).to eq("default-src example.org; sandbox")
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
it "creates sandbox policy when passed valid sandbox token values" do
|
|
125
|
-
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: %w(allow-forms allow-scripts))
|
|
126
|
-
expect(csp.value).to eq("default-src example.org; sandbox allow-forms allow-scripts")
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it "does not emit a warning when using frame-src" do
|
|
130
|
-
expect(Kernel).to_not receive(:warn)
|
|
131
|
-
ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
it "allows script as a require-sri-src" do
|
|
135
|
-
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(script))
|
|
136
|
-
expect(csp.value).to eq("default-src 'self'; require-sri-for script")
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
it "allows style as a require-sri-src" do
|
|
140
|
-
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(style))
|
|
141
|
-
expect(csp.value).to eq("default-src 'self'; require-sri-for style")
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
it "allows script and style as a require-sri-src" do
|
|
145
|
-
csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(script style))
|
|
146
|
-
expect(csp.value).to eq("default-src 'self'; require-sri-for script style")
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
it "includes prefetch-src" do
|
|
150
|
-
csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com))
|
|
151
|
-
expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com")
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
it "includes navigate-to" do
|
|
155
|
-
csp = ContentSecurityPolicy.new(default_src: %w('self'), navigate_to: %w(foo.com))
|
|
156
|
-
expect(csp.value).to eq("default-src 'self'; navigate-to foo.com")
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
it "supports strict-dynamic" do
|
|
160
|
-
csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456})
|
|
161
|
-
expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456' 'unsafe-inline'")
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
it "supports strict-dynamic and opting out of the appended 'unsafe-inline'" do
|
|
165
|
-
csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456, disable_nonce_backwards_compatibility: true })
|
|
166
|
-
expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
it "supports script-src-elem directive" do
|
|
170
|
-
csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_elem: %w('self')})
|
|
171
|
-
expect(csp.value).to eq("script-src 'self'; script-src-elem 'self'")
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
it "supports script-src-attr directive" do
|
|
175
|
-
csp = ContentSecurityPolicy.new({script_src: %w('self'), script_src_attr: %w('self')})
|
|
176
|
-
expect(csp.value).to eq("script-src 'self'; script-src-attr 'self'")
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
it "supports style-src-elem directive" do
|
|
180
|
-
csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_elem: %w('self')})
|
|
181
|
-
expect(csp.value).to eq("style-src 'self'; style-src-elem 'self'")
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
it "supports style-src-attr directive" do
|
|
185
|
-
csp = ContentSecurityPolicy.new({style_src: %w('self'), style_src_attr: %w('self')})
|
|
186
|
-
expect(csp.value).to eq("style-src 'self'; style-src-attr 'self'")
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|