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
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe Cookie do
|
|
6
|
-
let(:raw_cookie) { "_session=thisisatest" }
|
|
7
|
-
|
|
8
|
-
it "does not tamper with cookies when using OPT_OUT is used" do
|
|
9
|
-
cookie = Cookie.new(raw_cookie, OPT_OUT)
|
|
10
|
-
expect(cookie.to_s).to eq(raw_cookie)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it "applies httponly, secure, and samesite by default" do
|
|
14
|
-
cookie = Cookie.new(raw_cookie, nil)
|
|
15
|
-
expect(cookie.to_s).to eq("_session=thisisatest; secure; HttpOnly; SameSite=Lax")
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it "preserves existing attributes" do
|
|
19
|
-
cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
20
|
-
expect(cookie.to_s).to eq("_session=thisisatest; secure")
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "prevents duplicate flagging of attributes" do
|
|
24
|
-
cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT)
|
|
25
|
-
expect(cookie.to_s.scan(/secure/i).count).to eq(1)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
context "Secure cookies" do
|
|
29
|
-
context "when configured with a boolean" do
|
|
30
|
-
it "flags cookies as Secure" do
|
|
31
|
-
cookie = Cookie.new(raw_cookie, secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
32
|
-
expect(cookie.to_s).to eq("_session=thisisatest; secure")
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
context "when configured with a Hash" do
|
|
37
|
-
it "flags cookies as Secure when whitelisted" do
|
|
38
|
-
cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]}, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
39
|
-
expect(cookie.to_s).to eq("_session=thisisatest; secure")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
it "does not flag cookies as Secure when excluded" do
|
|
43
|
-
cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] }, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
44
|
-
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
context "HttpOnly cookies" do
|
|
50
|
-
context "when configured with a boolean" do
|
|
51
|
-
it "flags cookies as HttpOnly" do
|
|
52
|
-
cookie = Cookie.new(raw_cookie, httponly: true, secure: OPT_OUT, samesite: OPT_OUT)
|
|
53
|
-
expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
context "when configured with a Hash" do
|
|
58
|
-
it "flags cookies as HttpOnly when whitelisted" do
|
|
59
|
-
cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]}, secure: OPT_OUT, samesite: OPT_OUT)
|
|
60
|
-
expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
it "does not flag cookies as HttpOnly when excluded" do
|
|
64
|
-
cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] }, secure: OPT_OUT, samesite: OPT_OUT)
|
|
65
|
-
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
context "SameSite cookies" do
|
|
71
|
-
%w(None Lax Strict).each do |flag|
|
|
72
|
-
it "flags SameSite=#{flag}" do
|
|
73
|
-
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
74
|
-
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it "flags SameSite=#{flag} when configured with a boolean" do
|
|
78
|
-
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => true}, secure: OPT_OUT, httponly: OPT_OUT)
|
|
79
|
-
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it "does not flag cookies as SameSite=#{flag} when excluded" do
|
|
83
|
-
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
84
|
-
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
it "flags SameSite=Strict when configured with a boolean" do
|
|
89
|
-
cookie = Cookie.new(raw_cookie, {samesite: { strict: true}, secure: OPT_OUT, httponly: OPT_OUT})
|
|
90
|
-
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
it "flags properly when both lax and strict are configured" do
|
|
94
|
-
raw_cookie = "_session=thisisatest"
|
|
95
|
-
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
96
|
-
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
it "ignores configuration if the cookie is already flagged" do
|
|
100
|
-
raw_cookie = "_session=thisisatest; SameSite=Strict"
|
|
101
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: true }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
102
|
-
expect(cookie.to_s).to eq(raw_cookie)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
it "samesite: true sets all cookies to samesite=lax" do
|
|
106
|
-
raw_cookie = "_session=thisisatest"
|
|
107
|
-
cookie = Cookie.new(raw_cookie, samesite: true, secure: OPT_OUT, httponly: OPT_OUT)
|
|
108
|
-
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
context "with an invalid configuration" do
|
|
114
|
-
it "raises an exception when not configured with a Hash" do
|
|
115
|
-
expect do
|
|
116
|
-
Cookie.validate_config!("configuration")
|
|
117
|
-
end.to raise_error(CookiesConfigError)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
it "raises an exception when configured without a boolean(true or OPT_OUT)/Hash" do
|
|
121
|
-
expect do
|
|
122
|
-
Cookie.validate_config!(secure: "true")
|
|
123
|
-
end.to raise_error(CookiesConfigError)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it "raises an exception when configured with false" do
|
|
127
|
-
expect do
|
|
128
|
-
Cookie.validate_config!(secure: false)
|
|
129
|
-
end.to raise_error(CookiesConfigError)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
it "raises an exception when both only and except filters are provided" do
|
|
133
|
-
expect do
|
|
134
|
-
Cookie.validate_config!(secure: { only: [], except: [] })
|
|
135
|
-
end.to raise_error(CookiesConfigError)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
it "raises an exception when SameSite is not configured with a Hash" do
|
|
139
|
-
expect do
|
|
140
|
-
Cookie.validate_config!(samesite: true)
|
|
141
|
-
end.to raise_error(CookiesConfigError)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
cookie_options = %i(none lax strict)
|
|
145
|
-
cookie_options.each do |flag|
|
|
146
|
-
(cookie_options - [flag]).each do |other_flag|
|
|
147
|
-
it "raises an exception when SameSite #{flag} and #{other_flag} enforcement modes are configured with booleans" do
|
|
148
|
-
expect do
|
|
149
|
-
Cookie.validate_config!(samesite: { flag => true, other_flag => true})
|
|
150
|
-
end.to raise_error(CookiesConfigError)
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
|
|
156
|
-
expect do
|
|
157
|
-
Cookie.validate_config!(samesite: { lax: true, strict: { only: ["_anything"] } })
|
|
158
|
-
end.to raise_error(CookiesConfigError)
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
it "raises an exception when both only and except filters are provided to SameSite configurations" do
|
|
162
|
-
expect do
|
|
163
|
-
Cookie.validate_config!(samesite: { lax: { only: ["_anything"], except: ["_anythingelse"] } })
|
|
164
|
-
end.to raise_error(CookiesConfigError)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
|
|
168
|
-
expect do
|
|
169
|
-
Cookie.validate_config!(samesite: { lax: { only: ["_anything"] }, strict: { only: ["_anything"] } })
|
|
170
|
-
end.to raise_error(CookiesConfigError)
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
|
|
174
|
-
expect do
|
|
175
|
-
Cookie.validate_config!(samesite: { lax: { except: ["_anything"] }, strict: { except: ["_anything"] } })
|
|
176
|
-
end.to raise_error(CookiesConfigError)
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe ExpectCertificateTransparency do
|
|
6
|
-
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce, max-age=1234") }
|
|
7
|
-
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
|
|
8
|
-
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: "yolocopter").value).to eq("max-age=1234") }
|
|
9
|
-
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, report_uri: "https://report-uri.io/expect-ct").value).to eq("max-age=1234, report-uri=\"https://report-uri.io/expect-ct\"") }
|
|
10
|
-
specify do
|
|
11
|
-
config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" }
|
|
12
|
-
header_value = "enforce, max-age=1234, report-uri=\"https://report-uri.io/expect-ct\""
|
|
13
|
-
expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
context "with an invalid configuration" do
|
|
17
|
-
it "raises an exception when configuration isn't a hash" do
|
|
18
|
-
expect do
|
|
19
|
-
ExpectCertificateTransparency.validate_config!(%w(a))
|
|
20
|
-
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "raises an exception when max-age is not provided" do
|
|
24
|
-
expect do
|
|
25
|
-
ExpectCertificateTransparency.validate_config!(foo: "bar")
|
|
26
|
-
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "raises an exception with an invalid max-age" do
|
|
30
|
-
expect do
|
|
31
|
-
ExpectCertificateTransparency.validate_config!(max_age: "abc123")
|
|
32
|
-
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "raises an exception with an invalid enforce value" do
|
|
36
|
-
expect do
|
|
37
|
-
ExpectCertificateTransparency.validate_config!(enforce: "brokenstring")
|
|
38
|
-
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe PolicyManagement do
|
|
6
|
-
before(:each) do
|
|
7
|
-
reset_config
|
|
8
|
-
Configuration.default
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
let (:default_opts) do
|
|
12
|
-
{
|
|
13
|
-
default_src: %w(https:),
|
|
14
|
-
img_src: %w(https: data:),
|
|
15
|
-
script_src: %w('unsafe-inline' 'unsafe-eval' https: data:),
|
|
16
|
-
style_src: %w('unsafe-inline' https: about:),
|
|
17
|
-
report_uri: %w(/csp_report)
|
|
18
|
-
}
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
describe "#validate_config!" do
|
|
22
|
-
it "accepts all keys" do
|
|
23
|
-
# (pulled from README)
|
|
24
|
-
config = {
|
|
25
|
-
# "meta" values. these will shape the header, but the values are not included in the header.
|
|
26
|
-
report_only: false,
|
|
27
|
-
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
|
|
28
|
-
|
|
29
|
-
# directive values: these values will directly translate into source directives
|
|
30
|
-
default_src: %w(https: 'self'),
|
|
31
|
-
|
|
32
|
-
base_uri: %w('self'),
|
|
33
|
-
block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
|
|
34
|
-
connect_src: %w(wss:),
|
|
35
|
-
child_src: %w('self' *.twimg.com itunes.apple.com),
|
|
36
|
-
font_src: %w('self' data:),
|
|
37
|
-
form_action: %w('self' github.com),
|
|
38
|
-
frame_ancestors: %w('none'),
|
|
39
|
-
frame_src: %w('self' *.twimg.com itunes.apple.com),
|
|
40
|
-
img_src: %w(mycdn.com data:),
|
|
41
|
-
manifest_src: %w(manifest.com),
|
|
42
|
-
media_src: %w(utoob.com),
|
|
43
|
-
navigate_to: %w(netscape.com),
|
|
44
|
-
object_src: %w('self'),
|
|
45
|
-
plugin_types: %w(application/x-shockwave-flash),
|
|
46
|
-
prefetch_src: %w(fetch.com),
|
|
47
|
-
require_sri_for: %w(script style),
|
|
48
|
-
script_src: %w('self'),
|
|
49
|
-
style_src: %w('unsafe-inline'),
|
|
50
|
-
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
|
|
51
|
-
worker_src: %w(worker.com),
|
|
52
|
-
script_src_elem: %w(example.com),
|
|
53
|
-
script_src_attr: %w(example.com),
|
|
54
|
-
style_src_elem: %w(example.com),
|
|
55
|
-
style_src_attr: %w(example.com),
|
|
56
|
-
|
|
57
|
-
report_uri: %w(https://example.com/uri-directive),
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
it "requires a :default_src value" do
|
|
64
|
-
expect do
|
|
65
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %w('self')))
|
|
66
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it "requires a :script_src value" do
|
|
70
|
-
expect do
|
|
71
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self')))
|
|
72
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it "accepts OPT_OUT as a script-src value" do
|
|
76
|
-
expect do
|
|
77
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: OPT_OUT))
|
|
78
|
-
end.to_not raise_error
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it "requires :report_only to be a truthy value" do
|
|
82
|
-
expect do
|
|
83
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
|
|
84
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
it "requires :preserve_schemes to be a truthy value" do
|
|
88
|
-
expect do
|
|
89
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
|
|
90
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
it "requires :block_all_mixed_content to be a boolean value" do
|
|
94
|
-
expect do
|
|
95
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
|
|
96
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
it "requires :upgrade_insecure_requests to be a boolean value" do
|
|
100
|
-
expect do
|
|
101
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
|
|
102
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
it "requires all source lists to be an array of strings" do
|
|
106
|
-
expect do
|
|
107
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
|
|
108
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
it "allows nil values" do
|
|
112
|
-
expect do
|
|
113
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
|
|
114
|
-
end.to_not raise_error
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it "rejects unknown directives / config" do
|
|
118
|
-
expect do
|
|
119
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
|
|
120
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
|
124
|
-
it "performs light validation on source lists" do
|
|
125
|
-
expect do
|
|
126
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval), script_src: %w('self')))
|
|
127
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it "rejects anything not of the form allow-* as a sandbox value" do
|
|
131
|
-
expect do
|
|
132
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["steve"])))
|
|
133
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
it "accepts anything of the form allow-* as a sandbox value " do
|
|
137
|
-
expect do
|
|
138
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["allow-foo"])))
|
|
139
|
-
end.to_not raise_error
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
it "accepts true as a sandbox policy" do
|
|
143
|
-
expect do
|
|
144
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: true)))
|
|
145
|
-
end.to_not raise_error
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
it "rejects anything not of the form type/subtype as a plugin-type value" do
|
|
149
|
-
expect do
|
|
150
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["steve"])))
|
|
151
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
it "accepts anything of the form type/subtype as a plugin-type value " do
|
|
155
|
-
expect do
|
|
156
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["application/pdf"])))
|
|
157
|
-
end.to_not raise_error
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
it "doesn't allow report_only to be set in a non-report-only config" do
|
|
161
|
-
expect do
|
|
162
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: true)))
|
|
163
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
it "allows report_only to be set in a report-only config" do
|
|
167
|
-
expect do
|
|
168
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyReportOnlyConfig.new(default_opts.merge(report_only: true)))
|
|
169
|
-
end.to_not raise_error
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
describe "#combine_policies" do
|
|
174
|
-
before(:each) do
|
|
175
|
-
reset_config
|
|
176
|
-
end
|
|
177
|
-
it "combines the default-src value with the override if the directive was unconfigured" do
|
|
178
|
-
Configuration.default do |config|
|
|
179
|
-
config.csp = {
|
|
180
|
-
default_src: %w(https:),
|
|
181
|
-
script_src: %w('self'),
|
|
182
|
-
}
|
|
183
|
-
end
|
|
184
|
-
default_policy = Configuration.dup
|
|
185
|
-
combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, style_src: %w(anothercdn.com))
|
|
186
|
-
csp = ContentSecurityPolicy.new(combined_config)
|
|
187
|
-
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
|
|
188
|
-
expect(csp.value).to eq("default-src https:; script-src 'self'; style-src https: anothercdn.com")
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
it "combines directives where the original value is nil and the hash is frozen" do
|
|
192
|
-
Configuration.default do |config|
|
|
193
|
-
config.csp = {
|
|
194
|
-
default_src: %w('self'),
|
|
195
|
-
script_src: %w('self'),
|
|
196
|
-
report_only: false
|
|
197
|
-
}.freeze
|
|
198
|
-
end
|
|
199
|
-
report_uri = "https://report-uri.io/asdf"
|
|
200
|
-
default_policy = Configuration.dup
|
|
201
|
-
combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, report_uri: [report_uri])
|
|
202
|
-
csp = ContentSecurityPolicy.new(combined_config)
|
|
203
|
-
expect(csp.value).to include("report-uri #{report_uri}")
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
it "does not combine the default-src value for directives that don't fall back to default sources" do
|
|
207
|
-
Configuration.default do |config|
|
|
208
|
-
config.csp = {
|
|
209
|
-
default_src: %w('self'),
|
|
210
|
-
script_src: %w('self'),
|
|
211
|
-
report_only: false
|
|
212
|
-
}.freeze
|
|
213
|
-
end
|
|
214
|
-
non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
|
|
215
|
-
hash[directive] = %w("http://example.org)
|
|
216
|
-
end
|
|
217
|
-
default_policy = Configuration.dup
|
|
218
|
-
combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, non_default_source_additions)
|
|
219
|
-
|
|
220
|
-
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
|
|
221
|
-
expect(combined_config[directive]).to eq(%w("http://example.org))
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
it "overrides the report_only flag" do
|
|
226
|
-
Configuration.default do |config|
|
|
227
|
-
config.csp = {
|
|
228
|
-
default_src: %w('self'),
|
|
229
|
-
script_src: %w('self'),
|
|
230
|
-
report_only: false
|
|
231
|
-
}
|
|
232
|
-
end
|
|
233
|
-
default_policy = Configuration.dup
|
|
234
|
-
combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, report_only: true)
|
|
235
|
-
csp = ContentSecurityPolicy.new(combined_config)
|
|
236
|
-
expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
it "overrides the :block_all_mixed_content flag" do
|
|
240
|
-
Configuration.default do |config|
|
|
241
|
-
config.csp = {
|
|
242
|
-
default_src: %w(https:),
|
|
243
|
-
script_src: %w('self'),
|
|
244
|
-
block_all_mixed_content: false
|
|
245
|
-
}
|
|
246
|
-
end
|
|
247
|
-
default_policy = Configuration.dup
|
|
248
|
-
combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, block_all_mixed_content: true)
|
|
249
|
-
csp = ContentSecurityPolicy.new(combined_config)
|
|
250
|
-
expect(csp.value).to eq("default-src https:; block-all-mixed-content; script-src 'self'")
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
it "raises an error if appending to a OPT_OUT policy" do
|
|
254
|
-
Configuration.default do |config|
|
|
255
|
-
config.csp = OPT_OUT
|
|
256
|
-
end
|
|
257
|
-
default_policy = Configuration.dup
|
|
258
|
-
expect do
|
|
259
|
-
ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, script_src: %w(anothercdn.com))
|
|
260
|
-
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
end
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe ReferrerPolicy do
|
|
6
|
-
specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
|
|
7
|
-
specify { expect(ReferrerPolicy.make_header("no-referrer")).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
|
|
8
|
-
specify { expect(ReferrerPolicy.make_header(%w(origin-when-cross-origin strict-origin-when-cross-origin))).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin, strict-origin-when-cross-origin"]) }
|
|
9
|
-
|
|
10
|
-
context "valid configuration values" do
|
|
11
|
-
it "accepts 'no-referrer'" do
|
|
12
|
-
expect do
|
|
13
|
-
ReferrerPolicy.validate_config!("no-referrer")
|
|
14
|
-
end.not_to raise_error
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it "accepts 'no-referrer-when-downgrade'" do
|
|
18
|
-
expect do
|
|
19
|
-
ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
|
|
20
|
-
end.not_to raise_error
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "accepts 'same-origin'" do
|
|
24
|
-
expect do
|
|
25
|
-
ReferrerPolicy.validate_config!("same-origin")
|
|
26
|
-
end.not_to raise_error
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "accepts 'strict-origin'" do
|
|
30
|
-
expect do
|
|
31
|
-
ReferrerPolicy.validate_config!("strict-origin")
|
|
32
|
-
end.not_to raise_error
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "accepts 'strict-origin-when-cross-origin'" do
|
|
36
|
-
expect do
|
|
37
|
-
ReferrerPolicy.validate_config!("strict-origin-when-cross-origin")
|
|
38
|
-
end.not_to raise_error
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it "accepts 'origin'" do
|
|
42
|
-
expect do
|
|
43
|
-
ReferrerPolicy.validate_config!("origin")
|
|
44
|
-
end.not_to raise_error
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "accepts 'origin-when-cross-origin'" do
|
|
48
|
-
expect do
|
|
49
|
-
ReferrerPolicy.validate_config!("origin-when-cross-origin")
|
|
50
|
-
end.not_to raise_error
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it "accepts 'unsafe-url'" do
|
|
54
|
-
expect do
|
|
55
|
-
ReferrerPolicy.validate_config!("unsafe-url")
|
|
56
|
-
end.not_to raise_error
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
it "accepts nil" do
|
|
60
|
-
expect do
|
|
61
|
-
ReferrerPolicy.validate_config!(nil)
|
|
62
|
-
end.not_to raise_error
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it "accepts array of policy values" do
|
|
66
|
-
expect do
|
|
67
|
-
ReferrerPolicy.validate_config!(
|
|
68
|
-
%w(
|
|
69
|
-
origin-when-cross-origin
|
|
70
|
-
strict-origin-when-cross-origin
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
end.not_to raise_error
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
context "invalid configuration values" do
|
|
78
|
-
it "doesn't accept invalid values" do
|
|
79
|
-
expect do
|
|
80
|
-
ReferrerPolicy.validate_config!("open")
|
|
81
|
-
end.to raise_error(ReferrerPolicyConfigError)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
it "doesn't accept invalid types" do
|
|
85
|
-
expect do
|
|
86
|
-
ReferrerPolicy.validate_config!({})
|
|
87
|
-
end.to raise_error(TypeError)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe StrictTransportSecurity do
|
|
6
|
-
describe "#value" do
|
|
7
|
-
specify { expect(StrictTransportSecurity.make_header).to eq([StrictTransportSecurity::HEADER_NAME, StrictTransportSecurity::DEFAULT_VALUE]) }
|
|
8
|
-
specify { expect(StrictTransportSecurity.make_header("max-age=1234; includeSubdomains; preload")).to eq([StrictTransportSecurity::HEADER_NAME, "max-age=1234; includeSubdomains; preload"]) }
|
|
9
|
-
|
|
10
|
-
context "with an invalid configuration" do
|
|
11
|
-
context "with a string argument" do
|
|
12
|
-
it "raises an exception with an invalid max-age" do
|
|
13
|
-
expect do
|
|
14
|
-
StrictTransportSecurity.validate_config!("max-age=abc123")
|
|
15
|
-
end.to raise_error(STSConfigError)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it "raises an exception if max-age is not supplied" do
|
|
19
|
-
expect do
|
|
20
|
-
StrictTransportSecurity.validate_config!("includeSubdomains")
|
|
21
|
-
end.to raise_error(STSConfigError)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
it "raises an exception with an invalid format" do
|
|
25
|
-
expect do
|
|
26
|
-
StrictTransportSecurity.validate_config!("max-age=123includeSubdomains")
|
|
27
|
-
end.to raise_error(STSConfigError)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe XContentTypeOptions do
|
|
6
|
-
describe "#value" do
|
|
7
|
-
specify { expect(XContentTypeOptions.make_header).to eq([XContentTypeOptions::HEADER_NAME, XContentTypeOptions::DEFAULT_VALUE]) }
|
|
8
|
-
specify { expect(XContentTypeOptions.make_header("nosniff")).to eq([XContentTypeOptions::HEADER_NAME, "nosniff"]) }
|
|
9
|
-
|
|
10
|
-
context "invalid configuration values" do
|
|
11
|
-
it "accepts nosniff" do
|
|
12
|
-
expect do
|
|
13
|
-
XContentTypeOptions.validate_config!("nosniff")
|
|
14
|
-
end.not_to raise_error
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it "accepts nil" do
|
|
18
|
-
expect do
|
|
19
|
-
XContentTypeOptions.validate_config!(nil)
|
|
20
|
-
end.not_to raise_error
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "doesn't accept anything besides no-sniff" do
|
|
24
|
-
expect do
|
|
25
|
-
XContentTypeOptions.validate_config!("donkey")
|
|
26
|
-
end.to raise_error(XContentTypeOptionsConfigError)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "spec_helper"
|
|
3
|
-
|
|
4
|
-
module SecureHeaders
|
|
5
|
-
describe XDownloadOptions do
|
|
6
|
-
specify { expect(XDownloadOptions.make_header).to eq([XDownloadOptions::HEADER_NAME, XDownloadOptions::DEFAULT_VALUE]) }
|
|
7
|
-
specify { expect(XDownloadOptions.make_header("noopen")).to eq([XDownloadOptions::HEADER_NAME, "noopen"]) }
|
|
8
|
-
|
|
9
|
-
context "invalid configuration values" do
|
|
10
|
-
it "accepts noopen" do
|
|
11
|
-
expect do
|
|
12
|
-
XDownloadOptions.validate_config!("noopen")
|
|
13
|
-
end.not_to raise_error
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
it "accepts nil" do
|
|
17
|
-
expect do
|
|
18
|
-
XDownloadOptions.validate_config!(nil)
|
|
19
|
-
end.not_to raise_error
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it "doesn't accept anything besides noopen" do
|
|
23
|
-
expect do
|
|
24
|
-
XDownloadOptions.validate_config!("open")
|
|
25
|
-
end.to raise_error(XDOConfigError)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|