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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -2
  3. data/Gemfile +2 -0
  4. data/README.md +81 -15
  5. data/lib/secure_headers/configuration.rb +64 -13
  6. data/lib/secure_headers/headers/clear_site_data.rb +31 -33
  7. data/lib/secure_headers/headers/content_security_policy.rb +80 -46
  8. data/lib/secure_headers/headers/content_security_policy_config.rb +17 -58
  9. data/lib/secure_headers/headers/cookie.rb +4 -6
  10. data/lib/secure_headers/headers/expect_certificate_transparency.rb +20 -22
  11. data/lib/secure_headers/headers/policy_management.rb +66 -18
  12. data/lib/secure_headers/headers/referrer_policy.rb +20 -22
  13. data/lib/secure_headers/headers/reporting_endpoints.rb +54 -0
  14. data/lib/secure_headers/headers/strict_transport_security.rb +13 -15
  15. data/lib/secure_headers/headers/x_content_type_options.rb +14 -16
  16. data/lib/secure_headers/headers/x_download_options.rb +14 -16
  17. data/lib/secure_headers/headers/x_frame_options.rb +14 -16
  18. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +14 -16
  19. data/lib/secure_headers/headers/x_xss_protection.rb +14 -16
  20. data/lib/secure_headers/middleware.rb +11 -7
  21. data/lib/secure_headers/railtie.rb +11 -8
  22. data/lib/secure_headers/task_helper.rb +65 -0
  23. data/lib/secure_headers/version.rb +1 -1
  24. data/lib/secure_headers/view_helper.rb +7 -6
  25. data/lib/secure_headers.rb +26 -2
  26. data/lib/tasks/tasks.rake +4 -53
  27. data/secure_headers.gemspec +19 -7
  28. metadata +38 -70
  29. data/.github/ISSUE_TEMPLATE.md +0 -41
  30. data/.github/PULL_REQUEST_TEMPLATE.md +0 -20
  31. data/.github/workflows/build.yml +0 -24
  32. data/.github/workflows/github-release.yml +0 -28
  33. data/.gitignore +0 -13
  34. data/.rspec +0 -3
  35. data/.rubocop.yml +0 -4
  36. data/.ruby-gemset +0 -1
  37. data/.ruby-version +0 -1
  38. data/CODE_OF_CONDUCT.md +0 -46
  39. data/CONTRIBUTING.md +0 -41
  40. data/Guardfile +0 -13
  41. data/Rakefile +0 -32
  42. data/docs/cookies.md +0 -65
  43. data/docs/hashes.md +0 -64
  44. data/docs/named_overrides_and_appends.md +0 -104
  45. data/docs/per_action_configuration.md +0 -139
  46. data/docs/sinatra.md +0 -25
  47. data/docs/upgrading-to-3-0.md +0 -42
  48. data/docs/upgrading-to-4-0.md +0 -35
  49. data/docs/upgrading-to-5-0.md +0 -15
  50. data/docs/upgrading-to-6-0.md +0 -50
  51. data/spec/lib/secure_headers/configuration_spec.rb +0 -121
  52. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +0 -87
  53. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +0 -190
  54. data/spec/lib/secure_headers/headers/cookie_spec.rb +0 -179
  55. data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +0 -42
  56. data/spec/lib/secure_headers/headers/policy_management_spec.rb +0 -264
  57. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +0 -91
  58. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +0 -33
  59. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +0 -31
  60. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +0 -29
  61. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +0 -36
  62. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +0 -48
  63. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +0 -47
  64. data/spec/lib/secure_headers/middleware_spec.rb +0 -117
  65. data/spec/lib/secure_headers/view_helpers_spec.rb +0 -191
  66. data/spec/lib/secure_headers_spec.rb +0 -516
  67. data/spec/spec_helper.rb +0 -64
@@ -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.
@@ -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.
@@ -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
- ```
@@ -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