secure_headers 5.2.0 → 6.0.0.alpha01

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.

Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -4
  3. data/CHANGELOG.md +3 -7
  4. data/Gemfile +1 -1
  5. data/docs/upgrading-to-6-0.md +50 -0
  6. data/lib/secure_headers.rb +14 -76
  7. data/lib/secure_headers/configuration.rb +114 -164
  8. data/lib/secure_headers/headers/clear_site_data.rb +1 -3
  9. data/lib/secure_headers/headers/content_security_policy.rb +4 -17
  10. data/lib/secure_headers/headers/content_security_policy_config.rb +3 -13
  11. data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -3
  12. data/lib/secure_headers/headers/policy_management.rb +12 -11
  13. data/lib/secure_headers/headers/public_key_pins.rb +2 -3
  14. data/lib/secure_headers/headers/referrer_policy.rb +2 -2
  15. data/lib/secure_headers/headers/strict_transport_security.rb +2 -2
  16. data/lib/secure_headers/headers/x_content_type_options.rb +2 -2
  17. data/lib/secure_headers/headers/x_download_options.rb +2 -2
  18. data/lib/secure_headers/headers/x_frame_options.rb +1 -2
  19. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -2
  20. data/lib/secure_headers/headers/x_xss_protection.rb +3 -3
  21. data/secure_headers.gemspec +1 -1
  22. data/spec/lib/secure_headers/configuration_spec.rb +15 -70
  23. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +12 -22
  24. data/spec/lib/secure_headers/headers/policy_management_spec.rb +33 -7
  25. data/spec/lib/secure_headers/middleware_spec.rb +7 -1
  26. data/spec/lib/secure_headers/view_helpers_spec.rb +1 -0
  27. data/spec/lib/secure_headers_spec.rb +39 -40
  28. data/spec/spec_helper.rb +7 -3
  29. metadata +5 -4
@@ -28,16 +28,6 @@ module SecureHeaders
28
28
  expect(ContentSecurityPolicy.new.value).to eq("default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:")
29
29
  end
30
30
 
31
- it "deprecates and escapes semicolons in directive source lists" do
32
- expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a ; in "google.com;script-src *;.;" which will raise an error in future versions. It has been replaced with a blank space.))
33
- expect(ContentSecurityPolicy.new(frame_ancestors: %w(https://google.com;script-src https://*;.;)).value).to eq("frame-ancestors google.com script-src * .")
34
- end
35
-
36
- it "deprecates and escapes semicolons in directive source lists" do
37
- expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a \n in "\\nfoo.com\\nhacked" which will raise an error in future versions. It has been replaced with a blank space.))
38
- expect(ContentSecurityPolicy.new(frame_ancestors: ["\nfoo.com\nhacked"]).value).to eq("frame-ancestors foo.com hacked")
39
- end
40
-
41
31
  it "discards 'none' values if any other source expressions are present" do
42
32
  csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
43
33
  expect(csp.value).not_to include("'none'")
@@ -134,7 +124,7 @@ module SecureHeaders
134
124
 
135
125
  it "supports strict-dynamic" do
136
126
  csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456}, USER_AGENTS[:chrome])
137
- expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
127
+ expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456' 'unsafe-inline'")
138
128
  end
139
129
 
140
130
  context "browser sniffing" do
@@ -153,44 +143,44 @@ module SecureHeaders
153
143
 
154
144
  it "does not filter any directives for Chrome" do
155
145
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
156
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; worker-src worker-src.com; report-uri report-uri.com")
146
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; worker-src worker-src.com; report-uri report-uri.com")
157
147
  end
158
148
 
159
149
  it "does not filter any directives for Opera" do
160
150
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
161
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; worker-src worker-src.com; report-uri report-uri.com")
151
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; worker-src worker-src.com; report-uri report-uri.com")
162
152
  end
163
153
 
164
154
  it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
165
155
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
166
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
156
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
167
157
  end
168
158
 
169
159
  it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
170
160
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
171
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
161
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
172
162
  end
173
163
 
174
- it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
164
+ it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, hash sources, and plugin-types for Edge" do
175
165
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
176
- expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
166
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
177
167
  end
178
168
 
179
- it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
169
+ it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, hash sources, and plugin-types for safari" do
180
170
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
181
- expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
171
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
182
172
  end
183
173
 
184
- it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
174
+ it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, and hash sources for safari 10 and higher" do
185
175
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
186
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; report-uri report-uri.com")
176
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
187
177
  end
188
178
 
189
179
  it "falls back to standard Firefox defaults when the useragent version is not present" do
190
180
  ua = USER_AGENTS[:firefox].dup
191
181
  allow(ua).to receive(:version).and_return(nil)
192
182
  policy = ContentSecurityPolicy.new(complex_opts, ua)
193
- expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
183
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
194
184
  end
195
185
  end
196
186
  end
@@ -3,6 +3,11 @@ require "spec_helper"
3
3
 
4
4
  module SecureHeaders
5
5
  describe PolicyManagement do
6
+ before(:each) do
7
+ reset_config
8
+ Configuration.default
9
+ end
10
+
6
11
  let (:default_opts) do
7
12
  {
8
13
  default_src: %w(https:),
@@ -18,7 +23,7 @@ module SecureHeaders
18
23
  # (pulled from README)
19
24
  config = {
20
25
  # "meta" values. these will shape the header, but the values are not included in the header.
21
- report_only: true, # default: false
26
+ report_only: false,
22
27
  preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
23
28
 
24
29
  # directive values: these values will directly translate into source directives
@@ -142,9 +147,24 @@ module SecureHeaders
142
147
  ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["application/pdf"])))
143
148
  end.to_not raise_error
144
149
  end
150
+
151
+ it "doesn't allow report_only to be set in a non-report-only config" do
152
+ expect do
153
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: true)))
154
+ end.to raise_error(ContentSecurityPolicyConfigError)
155
+ end
156
+
157
+ it "allows report_only to be set in a report-only config" do
158
+ expect do
159
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyReportOnlyConfig.new(default_opts.merge(report_only: true)))
160
+ end.to_not raise_error
161
+ end
145
162
  end
146
163
 
147
164
  describe "#combine_policies" do
165
+ before(:each) do
166
+ reset_config
167
+ end
148
168
  it "combines the default-src value with the override if the directive was unconfigured" do
149
169
  Configuration.default do |config|
150
170
  config.csp = {
@@ -152,7 +172,8 @@ module SecureHeaders
152
172
  script_src: %w('self'),
153
173
  }
154
174
  end
155
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, style_src: %w(anothercdn.com))
175
+ default_policy = Configuration.dup
176
+ combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, style_src: %w(anothercdn.com))
156
177
  csp = ContentSecurityPolicy.new(combined_config)
157
178
  expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
158
179
  expect(csp.value).to eq("default-src https:; script-src 'self'; style-src https: anothercdn.com")
@@ -167,7 +188,8 @@ module SecureHeaders
167
188
  }.freeze
168
189
  end
169
190
  report_uri = "https://report-uri.io/asdf"
170
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, report_uri: [report_uri])
191
+ default_policy = Configuration.dup
192
+ combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, report_uri: [report_uri])
171
193
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
172
194
  expect(csp.value).to include("report-uri #{report_uri}")
173
195
  end
@@ -183,7 +205,8 @@ module SecureHeaders
183
205
  non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
184
206
  hash[directive] = %w("http://example.org)
185
207
  end
186
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, non_default_source_additions)
208
+ default_policy = Configuration.dup
209
+ combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, non_default_source_additions)
187
210
 
188
211
  ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
189
212
  expect(combined_config[directive]).to eq(%w("http://example.org))
@@ -198,7 +221,8 @@ module SecureHeaders
198
221
  report_only: false
199
222
  }
200
223
  end
201
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, report_only: true)
224
+ default_policy = Configuration.dup
225
+ combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, report_only: true)
202
226
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
203
227
  expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
204
228
  end
@@ -211,7 +235,8 @@ module SecureHeaders
211
235
  block_all_mixed_content: false
212
236
  }
213
237
  end
214
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, block_all_mixed_content: true)
238
+ default_policy = Configuration.dup
239
+ combined_config = ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, block_all_mixed_content: true)
215
240
  csp = ContentSecurityPolicy.new(combined_config)
216
241
  expect(csp.value).to eq("default-src https:; block-all-mixed-content; script-src 'self'")
217
242
  end
@@ -220,8 +245,9 @@ module SecureHeaders
220
245
  Configuration.default do |config|
221
246
  config.csp = OPT_OUT
222
247
  end
248
+ default_policy = Configuration.dup
223
249
  expect do
224
- ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, script_src: %w(anothercdn.com))
250
+ ContentSecurityPolicy.combine_policies(default_policy.csp.to_h, script_src: %w(anothercdn.com))
225
251
  end.to raise_error(ContentSecurityPolicyConfigError)
226
252
  end
227
253
  end
@@ -16,6 +16,7 @@ module SecureHeaders
16
16
 
17
17
  it "warns if the hpkp report-uri host is the same as the current host" do
18
18
  report_host = "report-uri.io"
19
+ reset_config
19
20
  Configuration.default do |config|
20
21
  config.hpkp = {
21
22
  max_age: 10000000,
@@ -50,12 +51,14 @@ module SecureHeaders
50
51
  end
51
52
  request = Rack::Request.new({})
52
53
  SecureHeaders.use_secure_headers_override(request, "my_custom_config")
53
- expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config", internal: true))
54
54
  _, env = middleware.call request.env
55
55
  expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
56
56
  end
57
57
 
58
58
  context "cookies" do
59
+ before(:each) do
60
+ reset_config
61
+ end
59
62
  context "cookies should be flagged" do
60
63
  it "flags cookies as secure" do
61
64
  Configuration.default { |config| config.cookies = {secure: true, httponly: OPT_OUT, samesite: OPT_OUT} }
@@ -87,6 +90,9 @@ module SecureHeaders
87
90
  end
88
91
 
89
92
  context "cookies" do
93
+ before(:each) do
94
+ reset_config
95
+ end
90
96
  it "flags cookies from configuration" do
91
97
  Configuration.default { |config| config.cookies = { secure: true, httponly: true, samesite: { lax: true} } }
92
98
  request = Rack::Request.new("HTTPS" => "on")
@@ -105,6 +105,7 @@ module SecureHeaders
105
105
  let(:filename) { "app/views/asdfs/index.html.erb" }
106
106
 
107
107
  before(:all) do
108
+ reset_config
108
109
  Configuration.default do |config|
109
110
  config.csp = {
110
111
  default_src: %w('self'),
@@ -17,10 +17,17 @@ module SecureHeaders
17
17
 
18
18
  it "raises a NotYetConfiguredError if trying to opt-out of unconfigured headers" do
19
19
  expect do
20
- SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
20
+ SecureHeaders.opt_out_of_header(request, :csp)
21
21
  end.to raise_error(Configuration::NotYetConfiguredError)
22
22
  end
23
23
 
24
+ it "raises a AlreadyConfiguredError if trying to configure and default has already been set " do
25
+ Configuration.default
26
+ expect do
27
+ Configuration.default
28
+ end.to raise_error(Configuration::AlreadyConfiguredError)
29
+ end
30
+
24
31
  it "raises and ArgumentError when referencing an override that has not been set" do
25
32
  expect do
26
33
  Configuration.default
@@ -34,9 +41,9 @@ module SecureHeaders
34
41
  config.csp = { default_src: %w('self'), script_src: %w('self')}
35
42
  config.csp_report_only = config.csp
36
43
  end
37
- SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyConfig::CONFIG_KEY)
38
- SecureHeaders.opt_out_of_header(request, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY)
39
- SecureHeaders.opt_out_of_header(request, XContentTypeOptions::CONFIG_KEY)
44
+ SecureHeaders.opt_out_of_header(request, :csp)
45
+ SecureHeaders.opt_out_of_header(request, :csp_report_only)
46
+ SecureHeaders.opt_out_of_header(request, :x_content_type_options)
40
47
  hash = SecureHeaders.header_hash_for(request)
41
48
  expect(hash["Content-Security-Policy-Report-Only"]).to be_nil
42
49
  expect(hash["Content-Security-Policy"]).to be_nil
@@ -60,6 +67,24 @@ module SecureHeaders
60
67
  expect(hash["X-Frame-Options"]).to be_nil
61
68
  end
62
69
 
70
+ it "Overrides the current default config if default config changes during request" do
71
+ Configuration.default do |config|
72
+ config.x_frame_options = OPT_OUT
73
+ end
74
+
75
+ # Dynamically update the default config for this request
76
+ SecureHeaders.override_x_frame_options(request, "DENY")
77
+
78
+ Configuration.override(:dynamic_override) do |config|
79
+ config.x_content_type_options = "nosniff"
80
+ end
81
+
82
+ SecureHeaders.use_secure_headers_override(request, :dynamic_override)
83
+ hash = SecureHeaders.header_hash_for(request)
84
+ expect(hash["X-Content-Type-Options"]).to eq("nosniff")
85
+ expect(hash["X-Frame-Options"]).to eq("DENY")
86
+ end
87
+
63
88
  it "allows you to opt out entirely" do
64
89
  # configure the disabled-by-default headers to ensure they also do not get set
65
90
  Configuration.default do |config|
@@ -78,9 +103,6 @@ module SecureHeaders
78
103
  end
79
104
  SecureHeaders.opt_out_of_all_protection(request)
80
105
  hash = SecureHeaders.header_hash_for(request)
81
- ALL_HEADER_CLASSES.each do |klass|
82
- expect(hash[klass::CONFIG_KEY]).to be_nil
83
- end
84
106
  expect(hash.count).to eq(0)
85
107
  end
86
108
 
@@ -116,7 +138,7 @@ module SecureHeaders
116
138
  firefox_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:firefox]))
117
139
 
118
140
  # append an unsupported directive
119
- SecureHeaders.override_content_security_policy_directives(firefox_request, {plugin_types: %w(flash)})
141
+ SecureHeaders.override_content_security_policy_directives(firefox_request, {plugin_types: %w(application/pdf)})
120
142
  # append a supported directive
121
143
  SecureHeaders.override_content_security_policy_directives(firefox_request, {script_src: %w('self')})
122
144
 
@@ -265,21 +287,6 @@ module SecureHeaders
265
287
  expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src https:; img-src data:; script-src 'self'")
266
288
  end
267
289
 
268
- it "does not append a nonce when the browser does not support it" do
269
- Configuration.default do |config|
270
- config.csp = {
271
- default_src: %w('self'),
272
- script_src: %w(mycdn.com 'unsafe-inline'),
273
- style_src: %w('self')
274
- }
275
- end
276
-
277
- safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
278
- SecureHeaders.content_security_policy_script_nonce(safari_request)
279
- hash = SecureHeaders.header_hash_for(safari_request)
280
- expect(hash[ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
281
- end
282
-
283
290
  it "appends a nonce to the script-src when used" do
284
291
  Configuration.default do |config|
285
292
  config.csp = {
@@ -297,21 +304,7 @@ module SecureHeaders
297
304
  SecureHeaders.content_security_policy_script_nonce(chrome_request)
298
305
 
299
306
  hash = SecureHeaders.header_hash_for(chrome_request)
300
- expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
301
- end
302
-
303
- it "uses a nonce for safari 10+" do
304
- Configuration.default do |config|
305
- config.csp = {
306
- default_src: %w('self'),
307
- script_src: %w(mycdn.com)
308
- }
309
- end
310
-
311
- safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari10]))
312
- nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
313
- hash = SecureHeaders.header_hash_for(safari_request)
314
- expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'")
307
+ expect(hash["Content-Security-Policy"]).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}' 'unsafe-inline'; style-src 'self'")
315
308
  end
316
309
 
317
310
  it "does not support the deprecated `report_only: true` format" do
@@ -322,7 +315,7 @@ module SecureHeaders
322
315
  report_only: true
323
316
  }
324
317
  end
325
- }.to raise_error(ArgumentError)
318
+ }.to raise_error(ContentSecurityPolicyConfigError)
326
319
  end
327
320
 
328
321
  it "Raises an error if csp_report_only is used with `report_only: false`" do
@@ -349,6 +342,7 @@ module SecureHeaders
349
342
  end
350
343
 
351
344
  it "sets identical values when the configs are the same" do
345
+ reset_config
352
346
  Configuration.default do |config|
353
347
  config.csp = {
354
348
  default_src: %w('self'),
@@ -366,6 +360,7 @@ module SecureHeaders
366
360
  end
367
361
 
368
362
  it "sets different headers when the configs are different" do
363
+ reset_config
369
364
  Configuration.default do |config|
370
365
  config.csp = {
371
366
  default_src: %w('self'),
@@ -376,10 +371,11 @@ module SecureHeaders
376
371
 
377
372
  hash = SecureHeaders.header_hash_for(request)
378
373
  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")
374
+ expect(hash["Content-Security-Policy-Report-Only"]).to eq("default-src 'self'; script-src foo.com")
380
375
  end
381
376
 
382
377
  it "allows you to opt-out of enforced CSP" do
378
+ reset_config
383
379
  Configuration.default do |config|
384
380
  config.csp = SecureHeaders::OPT_OUT
385
381
  config.csp_report_only = {
@@ -437,6 +433,7 @@ module SecureHeaders
437
433
 
438
434
  context "when inferring which config to modify" do
439
435
  it "updates the enforced header when configured" do
436
+ reset_config
440
437
  Configuration.default do |config|
441
438
  config.csp = {
442
439
  default_src: %w('self'),
@@ -451,6 +448,7 @@ module SecureHeaders
451
448
  end
452
449
 
453
450
  it "updates the report only header when configured" do
451
+ reset_config
454
452
  Configuration.default do |config|
455
453
  config.csp = OPT_OUT
456
454
  config.csp_report_only = {
@@ -466,6 +464,7 @@ module SecureHeaders
466
464
  end
467
465
 
468
466
  it "updates both headers if both are configured" do
467
+ reset_config
469
468
  Configuration.default do |config|
470
469
  config.csp = {
471
470
  default_src: %w(enforced.com),
data/spec/spec_helper.rb CHANGED
@@ -26,6 +26,7 @@ USER_AGENTS = {
26
26
 
27
27
  def expect_default_values(hash)
28
28
  expect(hash[SecureHeaders::ContentSecurityPolicyConfig::HEADER_NAME]).to eq("default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'")
29
+ expect(hash[SecureHeaders::ContentSecurityPolicyReportOnlyConfig::HEADER_NAME]).to be_nil
29
30
  expect(hash[SecureHeaders::XFrameOptions::HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::DEFAULT_VALUE)
30
31
  expect(hash[SecureHeaders::XDownloadOptions::HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::DEFAULT_VALUE)
31
32
  expect(hash[SecureHeaders::StrictTransportSecurity::HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::DEFAULT_VALUE)
@@ -34,18 +35,21 @@ def expect_default_values(hash)
34
35
  expect(hash[SecureHeaders::XPermittedCrossDomainPolicies::HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::DEFAULT_VALUE)
35
36
  expect(hash[SecureHeaders::ReferrerPolicy::HEADER_NAME]).to be_nil
36
37
  expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
38
+ expect(hash[SecureHeaders::ClearSiteData::HEADER_NAME]).to be_nil
39
+ expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
40
+ expect(hash[SecureHeaders::PublicKeyPins::HEADER_NAME]).to be_nil
37
41
  end
38
42
 
39
43
  module SecureHeaders
40
44
  class Configuration
41
45
  class << self
42
- def clear_configurations
43
- @configurations = nil
46
+ def clear_default_config
47
+ remove_instance_variable(:@default_config) if defined?(@default_config)
44
48
  end
45
49
  end
46
50
  end
47
51
  end
48
52
 
49
53
  def reset_config
50
- SecureHeaders::Configuration.clear_configurations
54
+ SecureHeaders::Configuration.clear_default_config
51
55
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secure_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 6.0.0.alpha01
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-21 00:00:00.000000000 Z
11
+ date: 2018-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -69,6 +69,7 @@ files:
69
69
  - docs/upgrading-to-3-0.md
70
70
  - docs/upgrading-to-4-0.md
71
71
  - docs/upgrading-to-5-0.md
72
+ - docs/upgrading-to-6-0.md
72
73
  - lib/secure_headers.rb
73
74
  - lib/secure_headers/configuration.rb
74
75
  - lib/secure_headers/hash_helper.rb
@@ -125,9 +126,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
126
  version: '0'
126
127
  required_rubygems_version: !ruby/object:Gem::Requirement
127
128
  requirements:
128
- - - ">="
129
+ - - ">"
129
130
  - !ruby/object:Gem::Version
130
- version: '0'
131
+ version: 1.3.1
131
132
  requirements: []
132
133
  rubyforge_project:
133
134
  rubygems_version: 2.6.13