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.
- checksums.yaml +4 -4
- data/.travis.yml +8 -4
- data/CHANGELOG.md +3 -7
- data/Gemfile +1 -1
- data/docs/upgrading-to-6-0.md +50 -0
- data/lib/secure_headers.rb +14 -76
- data/lib/secure_headers/configuration.rb +114 -164
- data/lib/secure_headers/headers/clear_site_data.rb +1 -3
- data/lib/secure_headers/headers/content_security_policy.rb +4 -17
- data/lib/secure_headers/headers/content_security_policy_config.rb +3 -13
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -3
- data/lib/secure_headers/headers/policy_management.rb +12 -11
- data/lib/secure_headers/headers/public_key_pins.rb +2 -3
- data/lib/secure_headers/headers/referrer_policy.rb +2 -2
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -2
- data/lib/secure_headers/headers/x_content_type_options.rb +2 -2
- data/lib/secure_headers/headers/x_download_options.rb +2 -2
- data/lib/secure_headers/headers/x_frame_options.rb +1 -2
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -2
- data/lib/secure_headers/headers/x_xss_protection.rb +3 -3
- data/secure_headers.gemspec +1 -1
- data/spec/lib/secure_headers/configuration_spec.rb +15 -70
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +12 -22
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +33 -7
- data/spec/lib/secure_headers/middleware_spec.rb +7 -1
- data/spec/lib/secure_headers/view_helpers_spec.rb +1 -0
- data/spec/lib/secure_headers_spec.rb +39 -40
- data/spec/spec_helper.rb +7 -3
- 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,
|
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,
|
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,
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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")
|
@@ -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,
|
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,
|
38
|
-
SecureHeaders.opt_out_of_header(request,
|
39
|
-
SecureHeaders.opt_out_of_header(request,
|
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(
|
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(
|
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
|
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
|
43
|
-
|
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.
|
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:
|
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:
|
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:
|
131
|
+
version: 1.3.1
|
131
132
|
requirements: []
|
132
133
|
rubyforge_project:
|
133
134
|
rubygems_version: 2.6.13
|