secure_headers 3.4.1 → 3.5.0.pre
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.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +32 -3
- data/lib/secure_headers.rb +122 -56
- data/lib/secure_headers/configuration.rb +56 -35
- data/lib/secure_headers/headers/content_security_policy.rb +60 -35
- data/lib/secure_headers/headers/content_security_policy_config.rb +128 -0
- data/lib/secure_headers/headers/policy_management.rb +13 -21
- data/secure_headers.gemspec +1 -1
- data/spec/lib/secure_headers/configuration_spec.rb +5 -5
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +2 -2
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +25 -34
- data/spec/lib/secure_headers/middleware_spec.rb +1 -1
- data/spec/lib/secure_headers/view_helpers_spec.rb +9 -6
- data/spec/lib/secure_headers_spec.rb +236 -58
- data/spec/spec_helper.rb +1 -1
- metadata +5 -4
@@ -40,70 +40,75 @@ module SecureHeaders
|
|
40
40
|
report_uri: %w(https://example.com/uri-directive)
|
41
41
|
}
|
42
42
|
|
43
|
-
|
43
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
|
44
44
|
end
|
45
45
|
|
46
46
|
it "requires a :default_src value" do
|
47
47
|
expect do
|
48
|
-
|
48
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
|
49
49
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
50
50
|
end
|
51
51
|
|
52
52
|
it "requires :report_only to be a truthy value" do
|
53
53
|
expect do
|
54
|
-
|
54
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
|
55
55
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
56
56
|
end
|
57
57
|
|
58
58
|
it "requires :preserve_schemes to be a truthy value" do
|
59
59
|
expect do
|
60
|
-
|
60
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
|
61
61
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
62
62
|
end
|
63
63
|
|
64
64
|
it "requires :block_all_mixed_content to be a boolean value" do
|
65
65
|
expect do
|
66
|
-
|
66
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
|
67
67
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
68
68
|
end
|
69
69
|
|
70
70
|
it "requires :upgrade_insecure_requests to be a boolean value" do
|
71
71
|
expect do
|
72
|
-
|
72
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
|
73
73
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
74
74
|
end
|
75
75
|
|
76
76
|
it "requires all source lists to be an array of strings" do
|
77
77
|
expect do
|
78
|
-
|
78
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
|
79
79
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
80
80
|
end
|
81
81
|
|
82
82
|
it "allows nil values" do
|
83
83
|
expect do
|
84
|
-
|
84
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
|
85
85
|
end.to_not raise_error
|
86
86
|
end
|
87
87
|
|
88
88
|
it "rejects unknown directives / config" do
|
89
89
|
expect do
|
90
|
-
|
90
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
|
91
91
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
92
92
|
end
|
93
93
|
|
94
94
|
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
95
95
|
it "performs light validation on source lists" do
|
96
96
|
expect do
|
97
|
-
|
97
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
|
98
98
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
102
|
describe "#combine_policies" do
|
103
103
|
it "combines the default-src value with the override if the directive was unconfigured" do
|
104
|
-
|
104
|
+
Configuration.default do |config|
|
105
|
+
config.csp = {
|
106
|
+
default_src: %w(https:)
|
107
|
+
}
|
108
|
+
end
|
109
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
|
105
110
|
csp = ContentSecurityPolicy.new(combined_config)
|
106
|
-
expect(csp.name).to eq(
|
111
|
+
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
|
107
112
|
expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
|
108
113
|
end
|
109
114
|
|
@@ -115,7 +120,7 @@ module SecureHeaders
|
|
115
120
|
}.freeze
|
116
121
|
end
|
117
122
|
report_uri = "https://report-uri.io/asdf"
|
118
|
-
combined_config =
|
123
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
|
119
124
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
120
125
|
expect(csp.value).to include("report-uri #{report_uri}")
|
121
126
|
end
|
@@ -127,12 +132,12 @@ module SecureHeaders
|
|
127
132
|
report_only: false
|
128
133
|
}.freeze
|
129
134
|
end
|
130
|
-
non_default_source_additions =
|
135
|
+
non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
|
131
136
|
hash[directive] = %w("http://example.org)
|
132
137
|
end
|
133
|
-
combined_config =
|
138
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
|
134
139
|
|
135
|
-
|
140
|
+
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
|
136
141
|
expect(combined_config[directive]).to eq(%w("http://example.org))
|
137
142
|
end
|
138
143
|
|
@@ -146,9 +151,9 @@ module SecureHeaders
|
|
146
151
|
report_only: false
|
147
152
|
}
|
148
153
|
end
|
149
|
-
combined_config =
|
154
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
|
150
155
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
151
|
-
expect(csp.name).to eq(
|
156
|
+
expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
|
152
157
|
end
|
153
158
|
|
154
159
|
it "overrides the :block_all_mixed_content flag" do
|
@@ -158,7 +163,7 @@ module SecureHeaders
|
|
158
163
|
block_all_mixed_content: false
|
159
164
|
}
|
160
165
|
end
|
161
|
-
combined_config =
|
166
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
|
162
167
|
csp = ContentSecurityPolicy.new(combined_config)
|
163
168
|
expect(csp.value).to eq("default-src https:; block-all-mixed-content")
|
164
169
|
end
|
@@ -168,23 +173,9 @@ module SecureHeaders
|
|
168
173
|
config.csp = OPT_OUT
|
169
174
|
end
|
170
175
|
expect do
|
171
|
-
|
176
|
+
ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
|
172
177
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
173
178
|
end
|
174
179
|
end
|
175
|
-
|
176
|
-
describe "#idempotent_additions?" do
|
177
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?(OPT_OUT, script_src: %w(b.com))).to be false }
|
178
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(c.com))).to be false }
|
179
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: %w(b.com))).to be false }
|
180
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(a.com b.com c.com))).to be false }
|
181
|
-
|
182
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com))).to be true }
|
183
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com a.com))).to be true }
|
184
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w())).to be true }
|
185
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: [nil])).to be true }
|
186
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: [nil])).to be true }
|
187
|
-
specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: nil)).to be true }
|
188
|
-
end
|
189
180
|
end
|
190
181
|
end
|
@@ -51,7 +51,7 @@ module SecureHeaders
|
|
51
51
|
SecureHeaders.use_secure_headers_override(request, "my_custom_config")
|
52
52
|
expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
|
53
53
|
_, env = middleware.call request.env
|
54
|
-
expect(env[
|
54
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
|
55
55
|
end
|
56
56
|
|
57
57
|
context "secure_cookies" do
|
@@ -72,8 +72,11 @@ module SecureHeaders
|
|
72
72
|
|
73
73
|
before(:all) do
|
74
74
|
Configuration.default do |config|
|
75
|
-
config.csp
|
76
|
-
|
75
|
+
config.csp = {
|
76
|
+
:default_src => %w('self'),
|
77
|
+
:script_src => %w('self'),
|
78
|
+
:style_src => %w('self')
|
79
|
+
}
|
77
80
|
end
|
78
81
|
end
|
79
82
|
|
@@ -115,10 +118,10 @@ module SecureHeaders
|
|
115
118
|
Message.new(request).result
|
116
119
|
_, env = middleware.call request.env
|
117
120
|
|
118
|
-
expect(env[
|
119
|
-
expect(env[
|
120
|
-
expect(env[
|
121
|
-
expect(env[
|
121
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
|
122
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
|
123
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
|
124
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
|
122
125
|
end
|
123
126
|
end
|
124
127
|
end
|
@@ -16,7 +16,7 @@ module SecureHeaders
|
|
16
16
|
|
17
17
|
it "raises a NotYetConfiguredError if trying to opt-out of unconfigured headers" do
|
18
18
|
expect do
|
19
|
-
SecureHeaders.opt_out_of_header(request,
|
19
|
+
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
|
20
20
|
end.to raise_error(Configuration::NotYetConfiguredError)
|
21
21
|
end
|
22
22
|
|
@@ -29,8 +29,12 @@ module SecureHeaders
|
|
29
29
|
|
30
30
|
describe "#header_hash_for" do
|
31
31
|
it "allows you to opt out of individual headers via API" do
|
32
|
-
Configuration.default
|
33
|
-
|
32
|
+
Configuration.default do |config|
|
33
|
+
config.csp = { default_src: %w('self')}
|
34
|
+
config.csp_report_only = config.csp
|
35
|
+
end
|
36
|
+
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
|
37
|
+
SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY)
|
34
38
|
SecureHeaders.opt_out_of_header(request, XContentTypeOptions::CONFIG_KEY)
|
35
39
|
hash = SecureHeaders.header_hash_for(request)
|
36
40
|
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
|
@@ -56,7 +60,21 @@ module SecureHeaders
|
|
56
60
|
end
|
57
61
|
|
58
62
|
it "allows you to opt out entirely" do
|
59
|
-
|
63
|
+
# configure the disabled-by-default headers to ensure they also do not get set
|
64
|
+
Configuration.default do |config|
|
65
|
+
config.csp = { :default_src => ["example.com"] }
|
66
|
+
config.csp_report_only = config.csp
|
67
|
+
config.hpkp = {
|
68
|
+
report_only: false,
|
69
|
+
max_age: 10000000,
|
70
|
+
include_subdomains: true,
|
71
|
+
report_uri: "https://report-uri.io/example-hpkp",
|
72
|
+
pins: [
|
73
|
+
{sha256: "abc"},
|
74
|
+
{sha256: "123"}
|
75
|
+
]
|
76
|
+
}
|
77
|
+
end
|
60
78
|
SecureHeaders.opt_out_of_all_protection(request)
|
61
79
|
hash = SecureHeaders.header_hash_for(request)
|
62
80
|
ALL_HEADER_CLASSES.each do |klass|
|
@@ -82,7 +100,7 @@ module SecureHeaders
|
|
82
100
|
SecureHeaders.override_content_security_policy_directives(request, default_src: %w(https:), script_src: %w('self'))
|
83
101
|
|
84
102
|
hash = SecureHeaders.header_hash_for(request)
|
85
|
-
expect(hash[
|
103
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
|
86
104
|
expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::SAMEORIGIN)
|
87
105
|
end
|
88
106
|
|
@@ -96,14 +114,14 @@ module SecureHeaders
|
|
96
114
|
firefox_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:firefox]))
|
97
115
|
|
98
116
|
# append an unsupported directive
|
99
|
-
SecureHeaders.override_content_security_policy_directives(firefox_request, plugin_types: %w(flash))
|
117
|
+
SecureHeaders.override_content_security_policy_directives(firefox_request, {plugin_types: %w(flash)})
|
100
118
|
# append a supported directive
|
101
|
-
SecureHeaders.override_content_security_policy_directives(firefox_request, script_src: %w('self'))
|
119
|
+
SecureHeaders.override_content_security_policy_directives(firefox_request, {script_src: %w('self')})
|
102
120
|
|
103
121
|
hash = SecureHeaders.header_hash_for(firefox_request)
|
104
122
|
|
105
123
|
# child-src is translated to frame-src
|
106
|
-
expect(hash[
|
124
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
|
107
125
|
end
|
108
126
|
|
109
127
|
it "produces a hash of headers with default config" do
|
@@ -152,7 +170,7 @@ module SecureHeaders
|
|
152
170
|
|
153
171
|
SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
|
154
172
|
hash = SecureHeaders.header_hash_for(request)
|
155
|
-
expect(hash[
|
173
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
|
156
174
|
end
|
157
175
|
|
158
176
|
it "supports named appends" do
|
@@ -174,7 +192,7 @@ module SecureHeaders
|
|
174
192
|
SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
|
175
193
|
hash = SecureHeaders.header_hash_for(request)
|
176
194
|
|
177
|
-
expect(hash[
|
195
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' https: 'unsafe-inline'")
|
178
196
|
end
|
179
197
|
|
180
198
|
it "appends a nonce to a missing script-src value" do
|
@@ -186,7 +204,7 @@ module SecureHeaders
|
|
186
204
|
|
187
205
|
SecureHeaders.content_security_policy_script_nonce(request) # should add the value to the header
|
188
206
|
hash = SecureHeaders.header_hash_for(chrome_request)
|
189
|
-
expect(hash[
|
207
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'nonce-.*'\z/
|
190
208
|
end
|
191
209
|
|
192
210
|
it "appends a hash to a missing script-src value" do
|
@@ -198,48 +216,7 @@ module SecureHeaders
|
|
198
216
|
|
199
217
|
SecureHeaders.append_content_security_policy_directives(request, script_src: %w('sha256-abc123'))
|
200
218
|
hash = SecureHeaders.header_hash_for(chrome_request)
|
201
|
-
expect(hash[
|
202
|
-
end
|
203
|
-
|
204
|
-
it "dups global configuration just once when overriding n times and only calls idempotent_additions? once" do
|
205
|
-
Configuration.default do |config|
|
206
|
-
config.csp = {
|
207
|
-
default_src: %w('self')
|
208
|
-
}
|
209
|
-
end
|
210
|
-
|
211
|
-
expect(CSP).to receive(:idempotent_additions?).once
|
212
|
-
|
213
|
-
# before an override occurs, the env is empty
|
214
|
-
expect(request.env[SECURE_HEADERS_CONFIG]).to be_nil
|
215
|
-
|
216
|
-
SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
|
217
|
-
new_config = SecureHeaders.config_for(request)
|
218
|
-
expect(new_config).to_not be(Configuration.get)
|
219
|
-
|
220
|
-
SecureHeaders.override_content_security_policy_directives(request, script_src: %w(yet.anothercdn.com))
|
221
|
-
current_config = SecureHeaders.config_for(request)
|
222
|
-
expect(current_config).to be(new_config)
|
223
|
-
|
224
|
-
SecureHeaders.header_hash_for(request)
|
225
|
-
end
|
226
|
-
|
227
|
-
it "doesn't allow you to muck with csp configs when a dynamic policy is in use" do
|
228
|
-
default_config = Configuration.default
|
229
|
-
expect { default_config.csp = {} }.to raise_error(NoMethodError)
|
230
|
-
|
231
|
-
# config is frozen
|
232
|
-
expect { default_config.send(:csp=, {}) }.to raise_error(RuntimeError)
|
233
|
-
|
234
|
-
SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
|
235
|
-
new_config = SecureHeaders.config_for(request)
|
236
|
-
expect { new_config.send(:csp=, {}) }.to raise_error(Configuration::IllegalPolicyModificationError)
|
237
|
-
|
238
|
-
expect do
|
239
|
-
new_config.instance_eval do
|
240
|
-
new_config.csp = {}
|
241
|
-
end
|
242
|
-
end.to raise_error(Configuration::IllegalPolicyModificationError)
|
219
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'sha256-abc123'\z/
|
243
220
|
end
|
244
221
|
|
245
222
|
it "overrides individual directives" do
|
@@ -250,14 +227,19 @@ module SecureHeaders
|
|
250
227
|
end
|
251
228
|
SecureHeaders.override_content_security_policy_directives(request, default_src: %w('none'))
|
252
229
|
hash = SecureHeaders.header_hash_for(request)
|
253
|
-
expect(hash[
|
230
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'none'")
|
254
231
|
end
|
255
232
|
|
256
233
|
it "overrides non-existant directives" do
|
257
|
-
Configuration.default
|
234
|
+
Configuration.default do |config|
|
235
|
+
config.csp = {
|
236
|
+
default_src: %w(https:)
|
237
|
+
}
|
238
|
+
end
|
258
239
|
SecureHeaders.override_content_security_policy_directives(request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
|
259
240
|
hash = SecureHeaders.header_hash_for(request)
|
260
|
-
expect(hash[
|
241
|
+
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to be_nil
|
242
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data:")
|
261
243
|
end
|
262
244
|
|
263
245
|
it "does not append a nonce when the browser does not support it" do
|
@@ -272,7 +254,7 @@ module SecureHeaders
|
|
272
254
|
safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
|
273
255
|
nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
|
274
256
|
hash = SecureHeaders.header_hash_for(safari_request)
|
275
|
-
expect(hash[
|
257
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
|
276
258
|
end
|
277
259
|
|
278
260
|
it "appends a nonce to the script-src when used" do
|
@@ -294,6 +276,202 @@ module SecureHeaders
|
|
294
276
|
hash = SecureHeaders.header_hash_for(chrome_request)
|
295
277
|
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
|
296
278
|
end
|
279
|
+
|
280
|
+
it "supports the deprecated `report_only: true` format" do
|
281
|
+
expect(Kernel).to receive(:warn).once
|
282
|
+
|
283
|
+
Configuration.default do |config|
|
284
|
+
config.csp = {
|
285
|
+
default_src: %w('self'),
|
286
|
+
report_only: true
|
287
|
+
}
|
288
|
+
end
|
289
|
+
|
290
|
+
expect(Configuration.get.csp).to eq(OPT_OUT)
|
291
|
+
expect(Configuration.get.csp_report_only).to be_a(ContentSecurityPolicyReportOnlyConfig)
|
292
|
+
|
293
|
+
hash = SecureHeaders.header_hash_for(request)
|
294
|
+
expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to be_nil
|
295
|
+
expect(hash[ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to eq("default-src 'self'")
|
296
|
+
end
|
297
|
+
|
298
|
+
it "Raises an error if csp_report_only is used with `report_only: false`" do
|
299
|
+
expect do
|
300
|
+
Configuration.default do |config|
|
301
|
+
config.csp_report_only = {
|
302
|
+
default_src: %w('self'),
|
303
|
+
report_only: false
|
304
|
+
}
|
305
|
+
end
|
306
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
307
|
+
end
|
308
|
+
|
309
|
+
context "setting two headers" do
|
310
|
+
before(:each) do
|
311
|
+
Configuration.default do |config|
|
312
|
+
config.csp = {
|
313
|
+
default_src: %w('self')
|
314
|
+
}
|
315
|
+
config.csp_report_only = config.csp
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
it "sets identical values when the configs are the same" do
|
320
|
+
Configuration.default do |config|
|
321
|
+
config.csp = {
|
322
|
+
default_src: %w('self')
|
323
|
+
}
|
324
|
+
config.csp_report_only = {
|
325
|
+
default_src: %w('self')
|
326
|
+
}
|
327
|
+
end
|
328
|
+
|
329
|
+
hash = SecureHeaders.header_hash_for(request)
|
330
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
|
331
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
332
|
+
end
|
333
|
+
|
334
|
+
it "sets different headers when the configs are different" do
|
335
|
+
Configuration.default do |config|
|
336
|
+
config.csp = {
|
337
|
+
default_src: %w('self')
|
338
|
+
}
|
339
|
+
config.csp_report_only = config.csp.merge({script_src: %w('self')})
|
340
|
+
end
|
341
|
+
|
342
|
+
hash = SecureHeaders.header_hash_for(request)
|
343
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
|
344
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self'")
|
345
|
+
end
|
346
|
+
|
347
|
+
it "allows you to opt-out of enforced CSP" do
|
348
|
+
Configuration.default do |config|
|
349
|
+
config.csp = SecureHeaders::OPT_OUT
|
350
|
+
config.csp_report_only = {
|
351
|
+
default_src: %w('self')
|
352
|
+
}
|
353
|
+
end
|
354
|
+
|
355
|
+
hash = SecureHeaders.header_hash_for(request)
|
356
|
+
expect(hash['Content-Security-Policy']).to be_nil
|
357
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
358
|
+
end
|
359
|
+
|
360
|
+
it "opts-out of enforced CSP when only csp_report_only is set" do
|
361
|
+
expect(Kernel).to receive(:warn).once
|
362
|
+
Configuration.default do |config|
|
363
|
+
config.csp_report_only = {
|
364
|
+
default_src: %w('self')
|
365
|
+
}
|
366
|
+
end
|
367
|
+
|
368
|
+
hash = SecureHeaders.header_hash_for(request)
|
369
|
+
expect(hash['Content-Security-Policy']).to be_nil
|
370
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
371
|
+
end
|
372
|
+
|
373
|
+
it "allows you to set csp_report_only before csp" do
|
374
|
+
expect(Kernel).to receive(:warn).once
|
375
|
+
Configuration.default do |config|
|
376
|
+
config.csp_report_only = {
|
377
|
+
default_src: %w('self')
|
378
|
+
}
|
379
|
+
config.csp = config.csp_report_only.merge({script_src: %w('unsafe-inline')})
|
380
|
+
end
|
381
|
+
|
382
|
+
hash = SecureHeaders.header_hash_for(request)
|
383
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' 'unsafe-inline'")
|
384
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
385
|
+
end
|
386
|
+
|
387
|
+
it "allows appending to the enforced policy" do
|
388
|
+
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
|
389
|
+
hash = SecureHeaders.header_hash_for(request)
|
390
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
391
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
392
|
+
end
|
393
|
+
|
394
|
+
it "allows appending to the report only policy" do
|
395
|
+
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
|
396
|
+
hash = SecureHeaders.header_hash_for(request)
|
397
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
|
398
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
399
|
+
end
|
400
|
+
|
401
|
+
it "allows appending to both policies" do
|
402
|
+
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
|
403
|
+
hash = SecureHeaders.header_hash_for(request)
|
404
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
405
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
406
|
+
end
|
407
|
+
|
408
|
+
it "allows overriding the enforced policy" do
|
409
|
+
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :enforced)
|
410
|
+
hash = SecureHeaders.header_hash_for(request)
|
411
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
|
412
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'")
|
413
|
+
end
|
414
|
+
|
415
|
+
it "allows overriding the report only policy" do
|
416
|
+
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :report_only)
|
417
|
+
hash = SecureHeaders.header_hash_for(request)
|
418
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'")
|
419
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src anothercdn.com")
|
420
|
+
end
|
421
|
+
|
422
|
+
it "allows overriding both policies" do
|
423
|
+
SecureHeaders.override_content_security_policy_directives(request, {script_src: %w(anothercdn.com)}, :both)
|
424
|
+
hash = SecureHeaders.header_hash_for(request)
|
425
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src anothercdn.com")
|
426
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src anothercdn.com")
|
427
|
+
end
|
428
|
+
|
429
|
+
context "when inferring which config to modify" do
|
430
|
+
it "updates the enforced header when configured" do
|
431
|
+
Configuration.default do |config|
|
432
|
+
config.csp = {
|
433
|
+
default_src: %w('self')
|
434
|
+
}
|
435
|
+
end
|
436
|
+
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
|
437
|
+
|
438
|
+
hash = SecureHeaders.header_hash_for(request)
|
439
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
440
|
+
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
|
441
|
+
end
|
442
|
+
|
443
|
+
it "updates the report only header when configured" do
|
444
|
+
Configuration.default do |config|
|
445
|
+
config.csp = OPT_OUT
|
446
|
+
config.csp_report_only = {
|
447
|
+
default_src: %w('self')
|
448
|
+
}
|
449
|
+
end
|
450
|
+
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
|
451
|
+
|
452
|
+
hash = SecureHeaders.header_hash_for(request)
|
453
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; script-src 'self' anothercdn.com")
|
454
|
+
expect(hash['Content-Security-Policy']).to be_nil
|
455
|
+
end
|
456
|
+
|
457
|
+
it "updates both headers if both are configured" do
|
458
|
+
Configuration.default do |config|
|
459
|
+
config.csp = {
|
460
|
+
default_src: %w(enforced.com)
|
461
|
+
}
|
462
|
+
config.csp_report_only = {
|
463
|
+
default_src: %w(reportonly.com)
|
464
|
+
}
|
465
|
+
end
|
466
|
+
SecureHeaders.append_content_security_policy_directives(request, {script_src: %w(anothercdn.com)})
|
467
|
+
|
468
|
+
hash = SecureHeaders.header_hash_for(request)
|
469
|
+
expect(hash['Content-Security-Policy']).to eq("default-src enforced.com; script-src enforced.com anothercdn.com")
|
470
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src reportonly.com; script-src reportonly.com anothercdn.com")
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|
474
|
+
end
|
297
475
|
end
|
298
476
|
end
|
299
477
|
|
@@ -309,7 +487,7 @@ module SecureHeaders
|
|
309
487
|
it "validates your csp config upon configuration" do
|
310
488
|
expect do
|
311
489
|
Configuration.default do |config|
|
312
|
-
config.csp = {
|
490
|
+
config.csp = { ContentSecurityPolicy::DEFAULT_SRC => '123456' }
|
313
491
|
end
|
314
492
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
315
493
|
end
|