secure_headers 3.2.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE.md +41 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -1
- data/.travis.yml +2 -0
- data/CHANGELOG.md +160 -1
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +41 -0
- data/Gemfile +3 -1
- data/LICENSE +4 -199
- data/README.md +75 -334
- data/docs/HPKP.md +17 -0
- data/docs/cookies.md +51 -0
- data/docs/hashes.md +64 -0
- data/docs/named_overrides_and_appends.md +107 -0
- data/docs/per_action_configuration.md +105 -0
- data/docs/sinatra.md +25 -0
- data/lib/secure_headers/configuration.rb +120 -39
- data/lib/secure_headers/headers/clear_site_data.rb +54 -0
- data/lib/secure_headers/headers/content_security_policy.rb +134 -36
- data/lib/secure_headers/headers/content_security_policy_config.rb +162 -0
- data/lib/secure_headers/headers/cookie.rb +7 -1
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
- data/lib/secure_headers/headers/policy_management.rb +150 -66
- data/lib/secure_headers/headers/public_key_pins.rb +1 -1
- data/lib/secure_headers/headers/referrer_policy.rb +36 -0
- data/lib/secure_headers/headers/strict_transport_security.rb +1 -1
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -1
- data/lib/secure_headers/headers/x_download_options.rb +1 -1
- data/lib/secure_headers/headers/x_frame_options.rb +1 -1
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -1
- data/lib/secure_headers/headers/x_xss_protection.rb +1 -1
- data/lib/secure_headers/middleware.rb +6 -0
- data/lib/secure_headers/railtie.rb +1 -1
- data/lib/secure_headers/utils/cookies_config.rb +6 -4
- data/lib/secure_headers/view_helper.rb +13 -7
- data/lib/secure_headers.rb +138 -55
- data/lib/tasks/tasks.rake +5 -4
- data/secure_headers.gemspec +2 -2
- data/spec/lib/secure_headers/configuration_spec.rb +5 -5
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +86 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +96 -13
- data/spec/lib/secure_headers/headers/cookie_spec.rb +22 -25
- data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +42 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +59 -36
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +72 -0
- data/spec/lib/secure_headers/middleware_spec.rb +31 -1
- data/spec/lib/secure_headers/view_helpers_spec.rb +19 -7
- data/spec/lib/secure_headers_spec.rb +344 -44
- data/spec/spec_helper.rb +8 -3
- data/upgrading-to-3-0.md +13 -10
- metadata +24 -5
|
@@ -62,29 +62,21 @@ module SecureHeaders
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
context "SameSite cookies" do
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
it "flags SameSite=Lax when configured with a boolean" do
|
|
71
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: true})
|
|
72
|
-
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it "does not flag cookies as SameSite=Lax when excluded" do
|
|
76
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } })
|
|
77
|
-
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
78
|
-
end
|
|
65
|
+
%w(None Lax Strict).each do |flag|
|
|
66
|
+
it "flags SameSite=#{flag}" do
|
|
67
|
+
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { only: ["_session"] } })
|
|
68
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
|
|
69
|
+
end
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
it "flags SameSite=#{flag} when configured with a boolean" do
|
|
72
|
+
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => true })
|
|
73
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=#{flag}")
|
|
74
|
+
end
|
|
84
75
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
it "does not flag cookies as SameSite=#{flag} when excluded" do
|
|
77
|
+
cookie = Cookie.new(raw_cookie, samesite: { flag.downcase.to_sym => { except: ["_session"] } })
|
|
78
|
+
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
79
|
+
end
|
|
88
80
|
end
|
|
89
81
|
|
|
90
82
|
it "flags SameSite=Strict when configured with a boolean" do
|
|
@@ -131,10 +123,15 @@ module SecureHeaders
|
|
|
131
123
|
end.to raise_error(CookiesConfigError)
|
|
132
124
|
end
|
|
133
125
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
126
|
+
cookie_options = %w(none lax strict).map(&:to_sym)
|
|
127
|
+
cookie_options.each do |flag|
|
|
128
|
+
(cookie_options - [flag]).each do |other_flag|
|
|
129
|
+
it "raises an exception when SameSite #{flag} and #{other_flag} enforcement modes are configured with booleans" do
|
|
130
|
+
expect do
|
|
131
|
+
Cookie.validate_config!(samesite: { flag => true, other_flag => true})
|
|
132
|
+
end.to raise_error(CookiesConfigError)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
138
135
|
end
|
|
139
136
|
|
|
140
137
|
it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
module SecureHeaders
|
|
5
|
+
describe ExpectCertificateTransparency do
|
|
6
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce, max-age=1234") }
|
|
7
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
|
|
8
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: "yolocopter").value).to eq("max-age=1234") }
|
|
9
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, report_uri: "https://report-uri.io/expect-ct").value).to eq("max-age=1234, report-uri=\"https://report-uri.io/expect-ct\"") }
|
|
10
|
+
specify do
|
|
11
|
+
config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" }
|
|
12
|
+
header_value = "enforce, max-age=1234, report-uri=\"https://report-uri.io/expect-ct\""
|
|
13
|
+
expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "with an invalid configuration" do
|
|
17
|
+
it "raises an exception when configuration isn't a hash" do
|
|
18
|
+
expect do
|
|
19
|
+
ExpectCertificateTransparency.validate_config!(%w(a))
|
|
20
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "raises an exception when max-age is not provided" do
|
|
24
|
+
expect do
|
|
25
|
+
ExpectCertificateTransparency.validate_config!(foo: "bar")
|
|
26
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "raises an exception with an invalid max-age" do
|
|
30
|
+
expect do
|
|
31
|
+
ExpectCertificateTransparency.validate_config!(max_age: "abc123")
|
|
32
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "raises an exception with an invalid enforce value" do
|
|
36
|
+
expect do
|
|
37
|
+
ExpectCertificateTransparency.validate_config!(enforce: "brokenstring")
|
|
38
|
+
end.to raise_error(ExpectCertificateTransparencyConfigError)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -23,15 +23,17 @@ module SecureHeaders
|
|
|
23
23
|
# directive values: these values will directly translate into source directives
|
|
24
24
|
default_src: %w(https: 'self'),
|
|
25
25
|
frame_src: %w('self' *.twimg.com itunes.apple.com),
|
|
26
|
-
|
|
26
|
+
child_src: %w('self' *.twimg.com itunes.apple.com),
|
|
27
|
+
connect_src: %w(wss:),
|
|
27
28
|
font_src: %w('self' data:),
|
|
28
29
|
img_src: %w(mycdn.com data:),
|
|
30
|
+
manifest_src: %w(manifest.com),
|
|
29
31
|
media_src: %w(utoob.com),
|
|
30
32
|
object_src: %w('self'),
|
|
31
33
|
script_src: %w('self'),
|
|
32
34
|
style_src: %w('unsafe-inline'),
|
|
35
|
+
worker_src: %w(worker.com),
|
|
33
36
|
base_uri: %w('self'),
|
|
34
|
-
child_src: %w('self'),
|
|
35
37
|
form_action: %w('self' github.com),
|
|
36
38
|
frame_ancestors: %w('none'),
|
|
37
39
|
plugin_types: %w(application/x-shockwave-flash),
|
|
@@ -40,70 +42,105 @@ module SecureHeaders
|
|
|
40
42
|
report_uri: %w(https://example.com/uri-directive)
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
it "requires a :default_src value" do
|
|
47
49
|
expect do
|
|
48
|
-
|
|
50
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
|
|
49
51
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
it "requires :report_only to be a truthy value" do
|
|
53
55
|
expect do
|
|
54
|
-
|
|
56
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
|
|
55
57
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
it "requires :preserve_schemes to be a truthy value" do
|
|
59
61
|
expect do
|
|
60
|
-
|
|
62
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
|
|
61
63
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
it "requires :block_all_mixed_content to be a boolean value" do
|
|
65
67
|
expect do
|
|
66
|
-
|
|
68
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
|
|
67
69
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
it "requires :upgrade_insecure_requests to be a boolean value" do
|
|
71
73
|
expect do
|
|
72
|
-
|
|
74
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
|
|
73
75
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
74
76
|
end
|
|
75
77
|
|
|
76
78
|
it "requires all source lists to be an array of strings" do
|
|
77
79
|
expect do
|
|
78
|
-
|
|
80
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
|
|
79
81
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
it "allows nil values" do
|
|
83
85
|
expect do
|
|
84
|
-
|
|
86
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
|
|
85
87
|
end.to_not raise_error
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
it "rejects unknown directives / config" do
|
|
89
91
|
expect do
|
|
90
|
-
|
|
92
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
|
|
91
93
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
|
95
97
|
it "performs light validation on source lists" do
|
|
96
98
|
expect do
|
|
97
|
-
|
|
99
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
|
|
98
100
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
99
101
|
end
|
|
102
|
+
|
|
103
|
+
it "rejects anything not of the form allow-* as a sandbox value" do
|
|
104
|
+
expect do
|
|
105
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["steve"])))
|
|
106
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "accepts anything of the form allow-* as a sandbox value " do
|
|
110
|
+
expect do
|
|
111
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["allow-foo"])))
|
|
112
|
+
end.to_not raise_error
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "accepts true as a sandbox policy" do
|
|
116
|
+
expect do
|
|
117
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: true)))
|
|
118
|
+
end.to_not raise_error
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "rejects anything not of the form type/subtype as a plugin-type value" do
|
|
122
|
+
expect do
|
|
123
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["steve"])))
|
|
124
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "accepts anything of the form type/subtype as a plugin-type value " do
|
|
128
|
+
expect do
|
|
129
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["application/pdf"])))
|
|
130
|
+
end.to_not raise_error
|
|
131
|
+
end
|
|
100
132
|
end
|
|
101
133
|
|
|
102
134
|
describe "#combine_policies" do
|
|
103
135
|
it "combines the default-src value with the override if the directive was unconfigured" do
|
|
104
|
-
|
|
136
|
+
Configuration.default do |config|
|
|
137
|
+
config.csp = {
|
|
138
|
+
default_src: %w(https:)
|
|
139
|
+
}
|
|
140
|
+
end
|
|
141
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
|
|
105
142
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
106
|
-
expect(csp.name).to eq(
|
|
143
|
+
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
|
|
107
144
|
expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
|
|
108
145
|
end
|
|
109
146
|
|
|
@@ -115,7 +152,7 @@ module SecureHeaders
|
|
|
115
152
|
}.freeze
|
|
116
153
|
end
|
|
117
154
|
report_uri = "https://report-uri.io/asdf"
|
|
118
|
-
combined_config =
|
|
155
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
|
|
119
156
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
|
120
157
|
expect(csp.value).to include("report-uri #{report_uri}")
|
|
121
158
|
end
|
|
@@ -127,12 +164,12 @@ module SecureHeaders
|
|
|
127
164
|
report_only: false
|
|
128
165
|
}.freeze
|
|
129
166
|
end
|
|
130
|
-
non_default_source_additions =
|
|
167
|
+
non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
|
|
131
168
|
hash[directive] = %w("http://example.org)
|
|
132
169
|
end
|
|
133
|
-
combined_config =
|
|
170
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
|
|
134
171
|
|
|
135
|
-
|
|
172
|
+
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
|
|
136
173
|
expect(combined_config[directive]).to eq(%w("http://example.org))
|
|
137
174
|
end
|
|
138
175
|
|
|
@@ -146,9 +183,9 @@ module SecureHeaders
|
|
|
146
183
|
report_only: false
|
|
147
184
|
}
|
|
148
185
|
end
|
|
149
|
-
combined_config =
|
|
186
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
|
|
150
187
|
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
|
151
|
-
expect(csp.name).to eq(
|
|
188
|
+
expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
|
|
152
189
|
end
|
|
153
190
|
|
|
154
191
|
it "overrides the :block_all_mixed_content flag" do
|
|
@@ -158,7 +195,7 @@ module SecureHeaders
|
|
|
158
195
|
block_all_mixed_content: false
|
|
159
196
|
}
|
|
160
197
|
end
|
|
161
|
-
combined_config =
|
|
198
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
|
|
162
199
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
163
200
|
expect(csp.value).to eq("default-src https:; block-all-mixed-content")
|
|
164
201
|
end
|
|
@@ -168,23 +205,9 @@ module SecureHeaders
|
|
|
168
205
|
config.csp = OPT_OUT
|
|
169
206
|
end
|
|
170
207
|
expect do
|
|
171
|
-
|
|
208
|
+
ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
|
|
172
209
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
173
210
|
end
|
|
174
211
|
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
212
|
end
|
|
190
213
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module SecureHeaders
|
|
4
|
+
describe ReferrerPolicy do
|
|
5
|
+
specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
|
|
6
|
+
specify { expect(ReferrerPolicy.make_header('no-referrer')).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
|
|
7
|
+
|
|
8
|
+
context "valid configuration values" do
|
|
9
|
+
it "accepts 'no-referrer'" do
|
|
10
|
+
expect do
|
|
11
|
+
ReferrerPolicy.validate_config!("no-referrer")
|
|
12
|
+
end.not_to raise_error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "accepts 'no-referrer-when-downgrade'" do
|
|
16
|
+
expect do
|
|
17
|
+
ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
|
|
18
|
+
end.not_to raise_error
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "accepts 'same-origin'" do
|
|
22
|
+
expect do
|
|
23
|
+
ReferrerPolicy.validate_config!("same-origin")
|
|
24
|
+
end.not_to raise_error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "accepts 'strict-origin'" do
|
|
28
|
+
expect do
|
|
29
|
+
ReferrerPolicy.validate_config!("strict-origin")
|
|
30
|
+
end.not_to raise_error
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "accepts 'strict-origin-when-cross-origin'" do
|
|
34
|
+
expect do
|
|
35
|
+
ReferrerPolicy.validate_config!("strict-origin-when-cross-origin")
|
|
36
|
+
end.not_to raise_error
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "accepts 'origin'" do
|
|
40
|
+
expect do
|
|
41
|
+
ReferrerPolicy.validate_config!("origin")
|
|
42
|
+
end.not_to raise_error
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "accepts 'origin-when-cross-origin'" do
|
|
46
|
+
expect do
|
|
47
|
+
ReferrerPolicy.validate_config!("origin-when-cross-origin")
|
|
48
|
+
end.not_to raise_error
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "accepts 'unsafe-url'" do
|
|
52
|
+
expect do
|
|
53
|
+
ReferrerPolicy.validate_config!("unsafe-url")
|
|
54
|
+
end.not_to raise_error
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "accepts nil" do
|
|
58
|
+
expect do
|
|
59
|
+
ReferrerPolicy.validate_config!(nil)
|
|
60
|
+
end.not_to raise_error
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context 'invlaid configuration values' do
|
|
65
|
+
it "doesn't accept invalid values" do
|
|
66
|
+
expect do
|
|
67
|
+
ReferrerPolicy.validate_config!("open")
|
|
68
|
+
end.to raise_error(ReferrerPolicyConfigError)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -13,6 +13,24 @@ module SecureHeaders
|
|
|
13
13
|
Configuration.default
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
it "warns if the hpkp report-uri host is the same as the current host" do
|
|
17
|
+
report_host = "report-uri.io"
|
|
18
|
+
Configuration.default do |config|
|
|
19
|
+
config.hpkp = {
|
|
20
|
+
max_age: 10000000,
|
|
21
|
+
pins: [
|
|
22
|
+
{sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
|
|
23
|
+
{sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
|
|
24
|
+
],
|
|
25
|
+
report_uri: "https://#{report_host}/example-hpkp"
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
expect(Kernel).to receive(:warn).with(Middleware::HPKP_SAME_HOST_WARNING)
|
|
30
|
+
|
|
31
|
+
middleware.call(Rack::MockRequest.env_for("https://#{report_host}", {}))
|
|
32
|
+
end
|
|
33
|
+
|
|
16
34
|
it "sets the headers" do
|
|
17
35
|
_, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
|
|
18
36
|
expect_default_values(env)
|
|
@@ -33,7 +51,7 @@ module SecureHeaders
|
|
|
33
51
|
SecureHeaders.use_secure_headers_override(request, "my_custom_config")
|
|
34
52
|
expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
|
|
35
53
|
_, env = middleware.call request.env
|
|
36
|
-
expect(env[
|
|
54
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
|
|
37
55
|
end
|
|
38
56
|
|
|
39
57
|
context "secure_cookies" do
|
|
@@ -87,6 +105,18 @@ module SecureHeaders
|
|
|
87
105
|
_, env = cookie_middleware.call request.env
|
|
88
106
|
expect(env['Set-Cookie']).to eq("foo=bar")
|
|
89
107
|
end
|
|
108
|
+
|
|
109
|
+
it "sets the secure cookie flag correctly on interleaved http/https requests" do
|
|
110
|
+
Configuration.default { |config| config.cookies = { secure: true } }
|
|
111
|
+
|
|
112
|
+
request = Rack::Request.new("HTTPS" => "off")
|
|
113
|
+
_, env = cookie_middleware.call request.env
|
|
114
|
+
expect(env['Set-Cookie']).to eq("foo=bar")
|
|
115
|
+
|
|
116
|
+
request = Rack::Request.new("HTTPS" => "on")
|
|
117
|
+
_, env = cookie_middleware.call request.env
|
|
118
|
+
expect(env['Set-Cookie']).to eq("foo=bar; secure")
|
|
119
|
+
end
|
|
90
120
|
end
|
|
91
121
|
end
|
|
92
122
|
end
|
|
@@ -27,7 +27,16 @@ class Message < ERB
|
|
|
27
27
|
background-color: black;
|
|
28
28
|
}
|
|
29
29
|
<% end %>
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
<script nonce="<%= content_security_policy_script_nonce %>">
|
|
32
|
+
alert(1)
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style nonce="<%= content_security_policy_style_nonce %>">
|
|
36
|
+
body {
|
|
37
|
+
background-color: black;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
31
40
|
|
|
32
41
|
TEMPLATE
|
|
33
42
|
end
|
|
@@ -72,8 +81,11 @@ module SecureHeaders
|
|
|
72
81
|
|
|
73
82
|
before(:all) do
|
|
74
83
|
Configuration.default do |config|
|
|
75
|
-
config.csp
|
|
76
|
-
|
|
84
|
+
config.csp = {
|
|
85
|
+
:default_src => %w('self'),
|
|
86
|
+
:script_src => %w('self'),
|
|
87
|
+
:style_src => %w('self')
|
|
88
|
+
}
|
|
77
89
|
end
|
|
78
90
|
end
|
|
79
91
|
|
|
@@ -115,10 +127,10 @@ module SecureHeaders
|
|
|
115
127
|
Message.new(request).result
|
|
116
128
|
_, env = middleware.call request.env
|
|
117
129
|
|
|
118
|
-
expect(env[
|
|
119
|
-
expect(env[
|
|
120
|
-
expect(env[
|
|
121
|
-
expect(env[
|
|
130
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
|
|
131
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
|
|
132
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
|
|
133
|
+
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
|
|
122
134
|
end
|
|
123
135
|
end
|
|
124
136
|
end
|