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.

@@ -40,70 +40,75 @@ module SecureHeaders
40
40
  report_uri: %w(https://example.com/uri-directive)
41
41
  }
42
42
 
43
- CSP.validate_config!(config)
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
- CSP.validate_config!(script_src: %('self'))
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
- CSP.validate_config!(default_opts.merge(report_only: "steve"))
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
- CSP.validate_config!(default_opts.merge(preserve_schemes: "steve"))
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
- CSP.validate_config!(default_opts.merge(block_all_mixed_content: "steve"))
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
- CSP.validate_config!(default_opts.merge(upgrade_insecure_requests: "steve"))
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
- CSP.validate_config!(default_src: "steve")
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
- CSP.validate_config!(default_src: %w('self'), script_src: ["https:", nil])
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
- CSP.validate_config!(default_src: %w('self'), default_src_totally_mispelled: "steve")
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
- CSP.validate_config!(default_src: %w(self none inline eval))
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
- combined_config = CSP.combine_policies(Configuration.default.csp, script_src: %w(anothercdn.com))
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(CSP::HEADER_NAME)
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 = CSP.combine_policies(Configuration.get.csp, report_uri: [report_uri])
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 = CSP::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
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 = CSP.combine_policies(Configuration.get.csp, non_default_source_additions)
138
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
134
139
 
135
- CSP::NON_FETCH_SOURCES.each do |directive|
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 = CSP.combine_policies(Configuration.get.csp, report_only: true)
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(CSP::REPORT_ONLY)
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 = CSP.combine_policies(Configuration.get.csp, block_all_mixed_content: true)
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
- CSP.combine_policies(Configuration.get.csp, script_src: %w(anothercdn.com))
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[CSP::HEADER_NAME]).to match("example.org")
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[:script_src] = %w('self')
76
- config.csp[:style_src] = %w('self')
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[CSP::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
119
- expect(env[CSP::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
120
- expect(env[CSP::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
121
- expect(env[CSP::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
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, CSP::CONFIG_KEY)
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
- SecureHeaders.opt_out_of_header(request, CSP::CONFIG_KEY)
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
- Configuration.default
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[CSP::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
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[CSP::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
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[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
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[CSP::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' https: 'unsafe-inline'")
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[CSP::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'nonce-.*'\z/
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[CSP::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'sha256-abc123'\z/
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[CSP::HEADER_NAME]).to eq("default-src 'none'")
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[CSP::HEADER_NAME]).to eq("default-src https:; img-src data:")
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[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
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 = { CSP::DEFAULT_SRC => '123456' }
490
+ config.csp = { ContentSecurityPolicy::DEFAULT_SRC => '123456' }
313
491
  end
314
492
  end.to raise_error(ContentSecurityPolicyConfigError)
315
493
  end