secure_headers 3.6.7 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +8 -6
- data/CHANGELOG.md +12 -4
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +7 -4
- data/Guardfile +1 -0
- data/README.md +13 -18
- data/Rakefile +22 -18
- data/docs/cookies.md +16 -2
- data/docs/per_action_configuration.md +28 -0
- data/lib/secure_headers/configuration.rb +9 -13
- data/lib/secure_headers/hash_helper.rb +2 -1
- data/lib/secure_headers/headers/clear_site_data.rb +4 -3
- data/lib/secure_headers/headers/content_security_policy.rb +52 -20
- data/lib/secure_headers/headers/content_security_policy_config.rb +1 -0
- data/lib/secure_headers/headers/cookie.rb +21 -3
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
- data/lib/secure_headers/headers/policy_management.rb +104 -49
- data/lib/secure_headers/headers/public_key_pins.rb +4 -3
- data/lib/secure_headers/headers/referrer_policy.rb +1 -0
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
- data/lib/secure_headers/headers/x_download_options.rb +2 -1
- data/lib/secure_headers/headers/x_frame_options.rb +1 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -1
- data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
- data/lib/secure_headers/middleware.rb +10 -9
- data/lib/secure_headers/railtie.rb +7 -6
- data/lib/secure_headers/utils/cookies_config.rb +13 -12
- data/lib/secure_headers/view_helper.rb +2 -1
- data/lib/secure_headers.rb +4 -1
- data/lib/tasks/tasks.rake +2 -1
- data/secure_headers.gemspec +13 -3
- data/spec/lib/secure_headers/configuration_spec.rb +9 -8
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +42 -25
- data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
- data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +56 -10
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
- data/spec/lib/secure_headers/middleware_spec.rb +29 -21
- data/spec/lib/secure_headers/view_helpers_spec.rb +5 -4
- data/spec/lib/secure_headers_spec.rb +92 -120
- data/spec/spec_helper.rb +10 -22
- data/upgrading-to-4-0.md +55 -0
- metadata +16 -6
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe XXssProtection do
|
|
5
6
|
specify { expect(XXssProtection.make_header).to eq([XXssProtection::HEADER_NAME, XXssProtection::DEFAULT_VALUE]) }
|
|
6
|
-
specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME,
|
|
7
|
+
specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME, "1; mode=block; report=https://www.secure.com/reports"]) }
|
|
7
8
|
|
|
8
9
|
context "with invalid configuration" do
|
|
9
10
|
it "should raise an error when providing a string that is not valid" do
|
|
@@ -19,7 +20,7 @@ module SecureHeaders
|
|
|
19
20
|
context "when using a hash value" do
|
|
20
21
|
it "should allow string values ('1' or '0' are the only valid strings)" do
|
|
21
22
|
expect do
|
|
22
|
-
XXssProtection.validate_config!(
|
|
23
|
+
XXssProtection.validate_config!("1")
|
|
23
24
|
end.not_to raise_error
|
|
24
25
|
end
|
|
25
26
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
@@ -19,8 +20,8 @@ module SecureHeaders
|
|
|
19
20
|
config.hpkp = {
|
|
20
21
|
max_age: 10000000,
|
|
21
22
|
pins: [
|
|
22
|
-
{sha256:
|
|
23
|
-
{sha256:
|
|
23
|
+
{sha256: "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
|
|
24
|
+
{sha256: "73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"}
|
|
24
25
|
],
|
|
25
26
|
report_uri: "https://#{report_host}/example-hpkp"
|
|
26
27
|
}
|
|
@@ -54,68 +55,75 @@ module SecureHeaders
|
|
|
54
55
|
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
|
|
55
56
|
end
|
|
56
57
|
|
|
57
|
-
context "
|
|
58
|
+
context "cookies" do
|
|
58
59
|
context "cookies should be flagged" do
|
|
59
60
|
it "flags cookies as secure" do
|
|
60
|
-
|
|
61
|
-
Configuration.default { |config| config.secure_cookies = true }
|
|
62
|
-
end
|
|
61
|
+
Configuration.default { |config| config.cookies = {secure: true, httponly: OPT_OUT, samesite: OPT_OUT} }
|
|
63
62
|
request = Rack::Request.new("HTTPS" => "on")
|
|
64
63
|
_, env = cookie_middleware.call request.env
|
|
65
|
-
expect(env[
|
|
64
|
+
expect(env["Set-Cookie"]).to eq("foo=bar; secure")
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
67
|
|
|
68
|
+
it "allows opting out of cookie protection with OPT_OUT alone" do
|
|
69
|
+
Configuration.default { |config| config.cookies = OPT_OUT}
|
|
70
|
+
|
|
71
|
+
# do NOT make this request https. non-https requests modify a config,
|
|
72
|
+
# causing an exception when operating on OPT_OUT. This ensures we don't
|
|
73
|
+
# try to modify the config.
|
|
74
|
+
request = Rack::Request.new({})
|
|
75
|
+
_, env = cookie_middleware.call request.env
|
|
76
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
77
|
+
end
|
|
78
|
+
|
|
69
79
|
context "cookies should not be flagged" do
|
|
70
80
|
it "does not flags cookies as secure" do
|
|
71
|
-
|
|
72
|
-
Configuration.default { |config| config.secure_cookies = false }
|
|
73
|
-
end
|
|
81
|
+
Configuration.default { |config| config.cookies = {secure: OPT_OUT, httponly: OPT_OUT, samesite: OPT_OUT} }
|
|
74
82
|
request = Rack::Request.new("HTTPS" => "on")
|
|
75
83
|
_, env = cookie_middleware.call request.env
|
|
76
|
-
expect(env[
|
|
84
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
77
85
|
end
|
|
78
86
|
end
|
|
79
87
|
end
|
|
80
88
|
|
|
81
89
|
context "cookies" do
|
|
82
90
|
it "flags cookies from configuration" do
|
|
83
|
-
Configuration.default { |config| config.cookies = { secure: true, httponly: true } }
|
|
91
|
+
Configuration.default { |config| config.cookies = { secure: true, httponly: true, samesite: { lax: true} } }
|
|
84
92
|
request = Rack::Request.new("HTTPS" => "on")
|
|
85
93
|
_, env = cookie_middleware.call request.env
|
|
86
94
|
|
|
87
|
-
expect(env[
|
|
95
|
+
expect(env["Set-Cookie"]).to eq("foo=bar; secure; HttpOnly; SameSite=Lax")
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
it "flags cookies with a combination of SameSite configurations" do
|
|
91
99
|
cookie_middleware = Middleware.new(lambda { |env| [200, env.merge("Set-Cookie" => ["_session=foobar", "_guest=true"]), "app"] })
|
|
92
100
|
|
|
93
|
-
Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } } } }
|
|
101
|
+
Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } }, httponly: OPT_OUT, secure: OPT_OUT} }
|
|
94
102
|
request = Rack::Request.new("HTTPS" => "on")
|
|
95
103
|
_, env = cookie_middleware.call request.env
|
|
96
104
|
|
|
97
|
-
expect(env[
|
|
98
|
-
expect(env[
|
|
105
|
+
expect(env["Set-Cookie"]).to match("_session=foobar; SameSite=Strict")
|
|
106
|
+
expect(env["Set-Cookie"]).to match("_guest=true; SameSite=Lax")
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
it "disables secure cookies for non-https requests" do
|
|
102
|
-
Configuration.default { |config| config.cookies = { secure: true } }
|
|
110
|
+
Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
|
|
103
111
|
|
|
104
112
|
request = Rack::Request.new("HTTPS" => "off")
|
|
105
113
|
_, env = cookie_middleware.call request.env
|
|
106
|
-
expect(env[
|
|
114
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
107
115
|
end
|
|
108
116
|
|
|
109
117
|
it "sets the secure cookie flag correctly on interleaved http/https requests" do
|
|
110
|
-
Configuration.default { |config| config.cookies = { secure: true } }
|
|
118
|
+
Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
|
|
111
119
|
|
|
112
120
|
request = Rack::Request.new("HTTPS" => "off")
|
|
113
121
|
_, env = cookie_middleware.call request.env
|
|
114
|
-
expect(env[
|
|
122
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
115
123
|
|
|
116
124
|
request = Rack::Request.new("HTTPS" => "on")
|
|
117
125
|
_, env = cookie_middleware.call request.env
|
|
118
|
-
expect(env[
|
|
126
|
+
expect(env["Set-Cookie"]).to eq("foo=bar; secure")
|
|
119
127
|
end
|
|
120
128
|
end
|
|
121
129
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require "spec_helper"
|
|
2
3
|
require "erb"
|
|
3
4
|
|
|
@@ -58,7 +59,7 @@ TEMPLATE
|
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
if options.is_a?(Hash)
|
|
61
|
-
options = options.map {|k,v| " #{k}=#{v}"}
|
|
62
|
+
options = options.map {|k, v| " #{k}=#{v}"}
|
|
62
63
|
end
|
|
63
64
|
"<#{type}#{options}>#{content}</#{type}>"
|
|
64
65
|
end
|
|
@@ -82,9 +83,9 @@ module SecureHeaders
|
|
|
82
83
|
before(:all) do
|
|
83
84
|
Configuration.default do |config|
|
|
84
85
|
config.csp = {
|
|
85
|
-
:
|
|
86
|
-
:
|
|
87
|
-
:
|
|
86
|
+
default_src: %w('self'),
|
|
87
|
+
script_src: %w('self'),
|
|
88
|
+
style_src: %w('self')
|
|
88
89
|
}
|
|
89
90
|
end
|
|
90
91
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe SecureHeaders do
|
|
@@ -30,16 +31,16 @@ module SecureHeaders
|
|
|
30
31
|
describe "#header_hash_for" do
|
|
31
32
|
it "allows you to opt out of individual headers via API" do
|
|
32
33
|
Configuration.default do |config|
|
|
33
|
-
config.csp = { default_src: %w('self')}
|
|
34
|
+
config.csp = { default_src: %w('self'), script_src: %w('self')}
|
|
34
35
|
config.csp_report_only = config.csp
|
|
35
36
|
end
|
|
36
37
|
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
|
|
37
38
|
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY)
|
|
38
39
|
SecureHeaders.opt_out_of_header(request, XContentTypeOptions::CONFIG_KEY)
|
|
39
40
|
hash = SecureHeaders.header_hash_for(request)
|
|
40
|
-
expect(hash[
|
|
41
|
-
expect(hash[
|
|
42
|
-
expect(hash[
|
|
41
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to be_nil
|
|
42
|
+
expect(hash["Content-Security-Policy"]).to be_nil
|
|
43
|
+
expect(hash["X-Content-Type-Options"]).to be_nil
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
it "Carries options over when using overrides" do
|
|
@@ -54,15 +55,15 @@ module SecureHeaders
|
|
|
54
55
|
|
|
55
56
|
SecureHeaders.use_secure_headers_override(request, :api)
|
|
56
57
|
hash = SecureHeaders.header_hash_for(request)
|
|
57
|
-
expect(hash[
|
|
58
|
-
expect(hash[
|
|
59
|
-
expect(hash[
|
|
58
|
+
expect(hash["X-Download-Options"]).to be_nil
|
|
59
|
+
expect(hash["X-Permitted-Cross-Domain-Policies"]).to be_nil
|
|
60
|
+
expect(hash["X-Frame-Options"]).to be_nil
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
it "allows you to opt out entirely" do
|
|
63
64
|
# configure the disabled-by-default headers to ensure they also do not get set
|
|
64
65
|
Configuration.default do |config|
|
|
65
|
-
config.csp = { :
|
|
66
|
+
config.csp = { default_src: ["example.com"], script_src: %w('self') }
|
|
66
67
|
config.csp_report_only = config.csp
|
|
67
68
|
config.hpkp = {
|
|
68
69
|
report_only: false,
|
|
@@ -108,6 +109,7 @@ module SecureHeaders
|
|
|
108
109
|
Configuration.default do |config|
|
|
109
110
|
config.csp = {
|
|
110
111
|
default_src: %w('self'),
|
|
112
|
+
script_src: %w('self'),
|
|
111
113
|
child_src: %w('self')
|
|
112
114
|
}
|
|
113
115
|
end
|
|
@@ -144,10 +146,10 @@ module SecureHeaders
|
|
|
144
146
|
config.hpkp = {
|
|
145
147
|
max_age: 1_000_000,
|
|
146
148
|
include_subdomains: true,
|
|
147
|
-
report_uri:
|
|
149
|
+
report_uri: "//example.com/uri-directive",
|
|
148
150
|
pins: [
|
|
149
|
-
{ sha256:
|
|
150
|
-
{ sha256:
|
|
151
|
+
{ sha256: "abc" },
|
|
152
|
+
{ sha256: "123" }
|
|
151
153
|
]
|
|
152
154
|
}
|
|
153
155
|
end
|
|
@@ -173,42 +175,32 @@ module SecureHeaders
|
|
|
173
175
|
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
|
|
174
176
|
end
|
|
175
177
|
|
|
176
|
-
it "
|
|
178
|
+
it "child-src and frame-src must match" do
|
|
177
179
|
Configuration.default do |config|
|
|
178
180
|
config.csp = {
|
|
179
181
|
default_src: %w('self'),
|
|
180
|
-
frame_src: %w(frame_src.com)
|
|
182
|
+
frame_src: %w(frame_src.com),
|
|
183
|
+
script_src: %w('self')
|
|
181
184
|
}
|
|
182
185
|
end
|
|
183
186
|
|
|
184
187
|
SecureHeaders.append_content_security_policy_directives(chrome_request, child_src: %w(child_src.com))
|
|
185
|
-
hash = SecureHeaders.header_hash_for(chrome_request)
|
|
186
|
-
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; child-src frame_src.com child_src.com")
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
it "appends frame-src to child-src" do
|
|
190
|
-
Configuration.default do |config|
|
|
191
|
-
config.csp = {
|
|
192
|
-
default_src: %w('self'),
|
|
193
|
-
child_src: %w(child_src.com)
|
|
194
|
-
}
|
|
195
|
-
end
|
|
196
188
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; frame-src child_src.com frame_src.com")
|
|
189
|
+
expect {
|
|
190
|
+
SecureHeaders.header_hash_for(chrome_request)
|
|
191
|
+
}.to raise_error(ArgumentError)
|
|
201
192
|
end
|
|
202
193
|
|
|
203
194
|
it "supports named appends" do
|
|
204
195
|
Configuration.default do |config|
|
|
205
196
|
config.csp = {
|
|
206
|
-
default_src: %w('self')
|
|
197
|
+
default_src: %w('self'),
|
|
198
|
+
script_src: %w('self')
|
|
207
199
|
}
|
|
208
200
|
end
|
|
209
201
|
|
|
210
202
|
Configuration.named_append(:moar_default_sources) do |request|
|
|
211
|
-
{ default_src: %w(https:)}
|
|
203
|
+
{ default_src: %w(https:), style_src: %w('self')}
|
|
212
204
|
end
|
|
213
205
|
|
|
214
206
|
Configuration.named_append(:how_about_a_script_src_too) do |request|
|
|
@@ -219,13 +211,14 @@ module SecureHeaders
|
|
|
219
211
|
SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
|
|
220
212
|
hash = SecureHeaders.header_hash_for(request)
|
|
221
213
|
|
|
222
|
-
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self'
|
|
214
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' 'unsafe-inline'; style-src 'self'")
|
|
223
215
|
end
|
|
224
216
|
|
|
225
217
|
it "appends a nonce to a missing script-src value" do
|
|
226
218
|
Configuration.default do |config|
|
|
227
219
|
config.csp = {
|
|
228
|
-
default_src: %w('self')
|
|
220
|
+
default_src: %w('self'),
|
|
221
|
+
script_src: %w('self')
|
|
229
222
|
}
|
|
230
223
|
end
|
|
231
224
|
|
|
@@ -237,7 +230,8 @@ module SecureHeaders
|
|
|
237
230
|
it "appends a hash to a missing script-src value" do
|
|
238
231
|
Configuration.default do |config|
|
|
239
232
|
config.csp = {
|
|
240
|
-
default_src: %w('self')
|
|
233
|
+
default_src: %w('self'),
|
|
234
|
+
script_src: %w('self')
|
|
241
235
|
}
|
|
242
236
|
end
|
|
243
237
|
|
|
@@ -249,24 +243,26 @@ module SecureHeaders
|
|
|
249
243
|
it "overrides individual directives" do
|
|
250
244
|
Configuration.default do |config|
|
|
251
245
|
config.csp = {
|
|
252
|
-
default_src: %w('self')
|
|
246
|
+
default_src: %w('self'),
|
|
247
|
+
script_src: %w('self')
|
|
253
248
|
}
|
|
254
249
|
end
|
|
255
250
|
SecureHeaders.override_content_security_policy_directives(request, default_src: %w('none'))
|
|
256
251
|
hash = SecureHeaders.header_hash_for(request)
|
|
257
|
-
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'")
|
|
252
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'; script-src 'self'")
|
|
258
253
|
end
|
|
259
254
|
|
|
260
255
|
it "overrides non-existant directives" do
|
|
261
256
|
Configuration.default do |config|
|
|
262
257
|
config.csp = {
|
|
263
|
-
default_src: %w(https:)
|
|
258
|
+
default_src: %w(https:),
|
|
259
|
+
script_src: %w('self')
|
|
264
260
|
}
|
|
265
261
|
end
|
|
266
262
|
SecureHeaders.override_content_security_policy_directives(request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
|
|
267
263
|
hash = SecureHeaders.header_hash_for(request)
|
|
268
264
|
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to be_nil
|
|
269
|
-
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data
|
|
265
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data:; script-src 'self'")
|
|
270
266
|
end
|
|
271
267
|
|
|
272
268
|
it "does not append a nonce when the browser does not support it" do
|
|
@@ -301,7 +297,7 @@ module SecureHeaders
|
|
|
301
297
|
SecureHeaders.content_security_policy_script_nonce(chrome_request)
|
|
302
298
|
|
|
303
299
|
hash = SecureHeaders.header_hash_for(chrome_request)
|
|
304
|
-
expect(hash[
|
|
300
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
|
|
305
301
|
end
|
|
306
302
|
|
|
307
303
|
it "uses a nonce for safari 10+" do
|
|
@@ -315,25 +311,18 @@ module SecureHeaders
|
|
|
315
311
|
safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari10]))
|
|
316
312
|
nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
|
|
317
313
|
hash = SecureHeaders.header_hash_for(safari_request)
|
|
318
|
-
expect(hash[
|
|
314
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'")
|
|
319
315
|
end
|
|
320
316
|
|
|
321
|
-
it "
|
|
322
|
-
expect
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
expect(Configuration.get.csp).to eq(OPT_OUT)
|
|
332
|
-
expect(Configuration.get.csp_report_only).to be_a(ContentSecurityPolicyReportOnlyConfig)
|
|
333
|
-
|
|
334
|
-
hash = SecureHeaders.header_hash_for(request)
|
|
335
|
-
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to be_nil
|
|
336
|
-
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'")
|
|
317
|
+
it "does not support the deprecated `report_only: true` format" do
|
|
318
|
+
expect {
|
|
319
|
+
Configuration.default do |config|
|
|
320
|
+
config.csp = {
|
|
321
|
+
default_src: %w('self'),
|
|
322
|
+
report_only: true
|
|
323
|
+
}
|
|
324
|
+
end
|
|
325
|
+
}.to raise_error(ArgumentError)
|
|
337
326
|
end
|
|
338
327
|
|
|
339
328
|
it "Raises an error if csp_report_only is used with `report_only: false`" do
|
|
@@ -341,6 +330,7 @@ module SecureHeaders
|
|
|
341
330
|
Configuration.default do |config|
|
|
342
331
|
config.csp_report_only = {
|
|
343
332
|
default_src: %w('self'),
|
|
333
|
+
script_src: %w('self'),
|
|
344
334
|
report_only: false
|
|
345
335
|
}
|
|
346
336
|
end
|
|
@@ -351,7 +341,8 @@ module SecureHeaders
|
|
|
351
341
|
before(:each) do
|
|
352
342
|
Configuration.default do |config|
|
|
353
343
|
config.csp = {
|
|
354
|
-
default_src: %w('self')
|
|
344
|
+
default_src: %w('self'),
|
|
345
|
+
script_src: %w('self')
|
|
355
346
|
}
|
|
356
347
|
config.csp_report_only = config.csp
|
|
357
348
|
end
|
|
@@ -360,155 +351,136 @@ module SecureHeaders
|
|
|
360
351
|
it "sets identical values when the configs are the same" do
|
|
361
352
|
Configuration.default do |config|
|
|
362
353
|
config.csp = {
|
|
363
|
-
default_src: %w('self')
|
|
354
|
+
default_src: %w('self'),
|
|
355
|
+
script_src: %w('self')
|
|
364
356
|
}
|
|
365
357
|
config.csp_report_only = {
|
|
366
|
-
default_src: %w('self')
|
|
358
|
+
default_src: %w('self'),
|
|
359
|
+
script_src: %w('self')
|
|
367
360
|
}
|
|
368
361
|
end
|
|
369
362
|
|
|
370
363
|
hash = SecureHeaders.header_hash_for(request)
|
|
371
|
-
expect(hash[
|
|
372
|
-
expect(hash[
|
|
364
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
|
|
365
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
|
|
373
366
|
end
|
|
374
367
|
|
|
375
368
|
it "sets different headers when the configs are different" do
|
|
376
369
|
Configuration.default do |config|
|
|
377
370
|
config.csp = {
|
|
378
|
-
default_src: %w('self')
|
|
371
|
+
default_src: %w('self'),
|
|
372
|
+
script_src: %w('self')
|
|
379
373
|
}
|
|
380
|
-
config.csp_report_only = config.csp.merge({script_src: %w(
|
|
374
|
+
config.csp_report_only = config.csp.merge({script_src: %w(foo.com)})
|
|
381
375
|
end
|
|
382
376
|
|
|
383
377
|
hash = SecureHeaders.header_hash_for(request)
|
|
384
|
-
expect(hash[
|
|
385
|
-
expect(hash[
|
|
378
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
|
|
379
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' foo.com")
|
|
386
380
|
end
|
|
387
381
|
|
|
388
382
|
it "allows you to opt-out of enforced CSP" do
|
|
389
383
|
Configuration.default do |config|
|
|
390
384
|
config.csp = SecureHeaders::OPT_OUT
|
|
391
385
|
config.csp_report_only = {
|
|
392
|
-
default_src: %w('self')
|
|
393
|
-
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
hash = SecureHeaders.header_hash_for(request)
|
|
397
|
-
expect(hash['Content-Security-Policy']).to be_nil
|
|
398
|
-
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
it "opts-out of enforced CSP when only csp_report_only is set" do
|
|
402
|
-
expect(Kernel).to receive(:warn).once
|
|
403
|
-
Configuration.default do |config|
|
|
404
|
-
config.csp_report_only = {
|
|
405
|
-
default_src: %w('self')
|
|
406
|
-
}
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
hash = SecureHeaders.header_hash_for(request)
|
|
410
|
-
expect(hash['Content-Security-Policy']).to be_nil
|
|
411
|
-
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
it "allows you to set csp_report_only before csp" do
|
|
415
|
-
expect(Kernel).to receive(:warn).once
|
|
416
|
-
Configuration.default do |config|
|
|
417
|
-
config.csp_report_only = {
|
|
418
|
-
default_src: %w('self')
|
|
386
|
+
default_src: %w('self'),
|
|
387
|
+
script_src: %w('self')
|
|
419
388
|
}
|
|
420
|
-
config.csp = config.csp_report_only.merge({script_src: %w('unsafe-inline')})
|
|
421
389
|
end
|
|
422
390
|
|
|
423
391
|
hash = SecureHeaders.header_hash_for(request)
|
|
424
|
-
expect(hash[
|
|
425
|
-
expect(hash[
|
|
392
|
+
expect(hash["Content-Security-Policy"]).to be_nil
|
|
393
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
|
|
426
394
|
end
|
|
427
395
|
|
|
428
396
|
it "allows appending to the enforced policy" do
|
|
429
397
|
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
|
|
430
398
|
hash = SecureHeaders.header_hash_for(request)
|
|
431
|
-
expect(hash[
|
|
432
|
-
expect(hash[
|
|
399
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
|
400
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
|
|
433
401
|
end
|
|
434
402
|
|
|
435
403
|
it "allows appending to the report only policy" do
|
|
436
404
|
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
|
|
437
405
|
hash = SecureHeaders.header_hash_for(request)
|
|
438
|
-
expect(hash[
|
|
439
|
-
expect(hash[
|
|
406
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
|
|
407
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
|
440
408
|
end
|
|
441
409
|
|
|
442
410
|
it "allows appending to both policies" do
|
|
443
411
|
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
|
|
444
412
|
hash = SecureHeaders.header_hash_for(request)
|
|
445
|
-
expect(hash[
|
|
446
|
-
expect(hash[
|
|
413
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
|
414
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
|
447
415
|
end
|
|
448
416
|
|
|
449
417
|
it "allows overriding the enforced policy" do
|
|
450
418
|
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
|
|
451
419
|
hash = SecureHeaders.header_hash_for(request)
|
|
452
|
-
expect(hash[
|
|
453
|
-
expect(hash[
|
|
420
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src anothercdn.com")
|
|
421
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self'")
|
|
454
422
|
end
|
|
455
423
|
|
|
456
424
|
it "allows overriding the report only policy" do
|
|
457
425
|
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
|
|
458
426
|
hash = SecureHeaders.header_hash_for(request)
|
|
459
|
-
expect(hash[
|
|
460
|
-
expect(hash[
|
|
427
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self'")
|
|
428
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src anothercdn.com")
|
|
461
429
|
end
|
|
462
430
|
|
|
463
431
|
it "allows overriding both policies" do
|
|
464
432
|
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
|
|
465
433
|
hash = SecureHeaders.header_hash_for(request)
|
|
466
|
-
expect(hash[
|
|
467
|
-
expect(hash[
|
|
434
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src anothercdn.com")
|
|
435
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src anothercdn.com")
|
|
468
436
|
end
|
|
469
437
|
|
|
470
438
|
context "when inferring which config to modify" do
|
|
471
439
|
it "updates the enforced header when configured" do
|
|
472
440
|
Configuration.default do |config|
|
|
473
441
|
config.csp = {
|
|
474
|
-
default_src: %w('self')
|
|
442
|
+
default_src: %w('self'),
|
|
443
|
+
script_src: %w('self')
|
|
475
444
|
}
|
|
476
445
|
end
|
|
477
446
|
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
|
|
478
447
|
|
|
479
448
|
hash = SecureHeaders.header_hash_for(request)
|
|
480
|
-
expect(hash[
|
|
481
|
-
expect(hash[
|
|
449
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
|
450
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to be_nil
|
|
482
451
|
end
|
|
483
452
|
|
|
484
453
|
it "updates the report only header when configured" do
|
|
485
454
|
Configuration.default do |config|
|
|
486
455
|
config.csp = OPT_OUT
|
|
487
456
|
config.csp_report_only = {
|
|
488
|
-
default_src: %w('self')
|
|
457
|
+
default_src: %w('self'),
|
|
458
|
+
script_src: %w('self')
|
|
489
459
|
}
|
|
490
460
|
end
|
|
491
461
|
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
|
|
492
462
|
|
|
493
463
|
hash = SecureHeaders.header_hash_for(request)
|
|
494
|
-
expect(hash[
|
|
495
|
-
expect(hash[
|
|
464
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
|
465
|
+
expect(hash["Content-Security-Policy"]).to be_nil
|
|
496
466
|
end
|
|
497
467
|
|
|
498
468
|
it "updates both headers if both are configured" do
|
|
499
469
|
Configuration.default do |config|
|
|
500
470
|
config.csp = {
|
|
501
|
-
default_src: %w(enforced.com)
|
|
471
|
+
default_src: %w(enforced.com),
|
|
472
|
+
script_src: %w('self')
|
|
502
473
|
}
|
|
503
474
|
config.csp_report_only = {
|
|
504
|
-
default_src: %w(reportonly.com)
|
|
475
|
+
default_src: %w(reportonly.com),
|
|
476
|
+
script_src: %w('self')
|
|
505
477
|
}
|
|
506
478
|
end
|
|
507
479
|
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
|
|
508
480
|
|
|
509
481
|
hash = SecureHeaders.header_hash_for(request)
|
|
510
|
-
expect(hash[
|
|
511
|
-
expect(hash[
|
|
482
|
+
expect(hash["Content-Security-Policy"]).to eq("default-src enforced.com; script-src 'self' anothercdn.com")
|
|
483
|
+
expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src reportonly.com; script-src 'self' anothercdn.com")
|
|
512
484
|
end
|
|
513
485
|
|
|
514
486
|
end
|
|
@@ -520,7 +492,7 @@ module SecureHeaders
|
|
|
520
492
|
it "validates your hsts config upon configuration" do
|
|
521
493
|
expect do
|
|
522
494
|
Configuration.default do |config|
|
|
523
|
-
config.hsts =
|
|
495
|
+
config.hsts = "lol"
|
|
524
496
|
end
|
|
525
497
|
end.to raise_error(STSConfigError)
|
|
526
498
|
end
|
|
@@ -528,7 +500,7 @@ module SecureHeaders
|
|
|
528
500
|
it "validates your csp config upon configuration" do
|
|
529
501
|
expect do
|
|
530
502
|
Configuration.default do |config|
|
|
531
|
-
config.csp = { ContentSecurityPolicy::DEFAULT_SRC =>
|
|
503
|
+
config.csp = { ContentSecurityPolicy::DEFAULT_SRC => "123456" }
|
|
532
504
|
end
|
|
533
505
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
534
506
|
end
|
|
@@ -536,7 +508,7 @@ module SecureHeaders
|
|
|
536
508
|
it "raises errors for unknown directives" do
|
|
537
509
|
expect do
|
|
538
510
|
Configuration.default do |config|
|
|
539
|
-
config.csp = { made_up_directive:
|
|
511
|
+
config.csp = { made_up_directive: "123456" }
|
|
540
512
|
end
|
|
541
513
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
542
514
|
end
|