secure_headers 3.9.0 → 4.0.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/.rspec +1 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +8 -6
- data/CHANGELOG.md +2 -22
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +7 -4
- data/Guardfile +1 -0
- data/README.md +7 -19
- data/Rakefile +22 -18
- data/docs/cookies.md +18 -5
- data/docs/per_action_configuration.md +28 -0
- data/lib/secure_headers/configuration.rb +5 -12
- data/lib/secure_headers/hash_helper.rb +2 -1
- data/lib/secure_headers/headers/clear_site_data.rb +4 -3
- data/lib/secure_headers/headers/content_security_policy.rb +10 -19
- data/lib/secure_headers/headers/content_security_policy_config.rb +1 -1
- data/lib/secure_headers/headers/cookie.rb +22 -10
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -2
- data/lib/secure_headers/headers/policy_management.rb +14 -8
- data/lib/secure_headers/headers/public_key_pins.rb +4 -3
- data/lib/secure_headers/headers/referrer_policy.rb +1 -0
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
- data/lib/secure_headers/headers/x_download_options.rb +2 -1
- data/lib/secure_headers/headers/x_frame_options.rb +1 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -1
- data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
- data/lib/secure_headers/middleware.rb +10 -9
- data/lib/secure_headers/railtie.rb +7 -6
- data/lib/secure_headers/utils/cookies_config.rb +17 -18
- data/lib/secure_headers/view_helper.rb +2 -1
- data/lib/secure_headers.rb +2 -1
- data/lib/tasks/tasks.rake +2 -1
- data/secure_headers.gemspec +13 -3
- data/spec/lib/secure_headers/configuration_spec.rb +9 -8
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +15 -29
- data/spec/lib/secure_headers/headers/cookie_spec.rb +58 -37
- data/spec/lib/secure_headers/headers/{expect_certificate_transparency_spec.rb → expect_certificate_spec.rb} +3 -3
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +26 -11
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
- data/spec/lib/secure_headers/middleware_spec.rb +29 -21
- data/spec/lib/secure_headers/view_helpers_spec.rb +5 -4
- data/spec/lib/secure_headers_spec.rb +92 -120
- data/spec/spec_helper.rb +9 -22
- data/upgrading-to-4-0.md +55 -0
- metadata +16 -8
|
@@ -1,40 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe Cookie do
|
|
5
6
|
let(:raw_cookie) { "_session=thisisatest" }
|
|
6
7
|
|
|
7
|
-
it "does not tamper with cookies when
|
|
8
|
-
cookie = Cookie.new(raw_cookie,
|
|
8
|
+
it "does not tamper with cookies when using OPT_OUT is used" do
|
|
9
|
+
cookie = Cookie.new(raw_cookie, OPT_OUT)
|
|
9
10
|
expect(cookie.to_s).to eq(raw_cookie)
|
|
10
11
|
end
|
|
11
12
|
|
|
13
|
+
it "applies httponly, secure, and samesite by default" do
|
|
14
|
+
cookie = Cookie.new(raw_cookie, nil)
|
|
15
|
+
expect(cookie.to_s).to eq("_session=thisisatest; secure; HttpOnly; SameSite=Lax")
|
|
16
|
+
end
|
|
17
|
+
|
|
12
18
|
it "preserves existing attributes" do
|
|
13
|
-
cookie = Cookie.new("_session=thisisatest; secure", secure: true)
|
|
19
|
+
cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
14
20
|
expect(cookie.to_s).to eq("_session=thisisatest; secure")
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
it "prevents duplicate flagging of attributes" do
|
|
18
|
-
cookie = Cookie.new("_session=thisisatest; secure", secure: true)
|
|
24
|
+
cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT)
|
|
19
25
|
expect(cookie.to_s.scan(/secure/i).count).to eq(1)
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
context "Secure cookies" do
|
|
23
29
|
context "when configured with a boolean" do
|
|
24
30
|
it "flags cookies as Secure" do
|
|
25
|
-
cookie = Cookie.new(raw_cookie, secure: true)
|
|
31
|
+
cookie = Cookie.new(raw_cookie, secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
26
32
|
expect(cookie.to_s).to eq("_session=thisisatest; secure")
|
|
27
33
|
end
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
context "when configured with a Hash" do
|
|
31
37
|
it "flags cookies as Secure when whitelisted" do
|
|
32
|
-
cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]})
|
|
38
|
+
cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]}, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
33
39
|
expect(cookie.to_s).to eq("_session=thisisatest; secure")
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
it "does not flag cookies as Secure when excluded" do
|
|
37
|
-
cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] })
|
|
43
|
+
cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] }, httponly: OPT_OUT, samesite: OPT_OUT)
|
|
38
44
|
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
39
45
|
end
|
|
40
46
|
end
|
|
@@ -43,58 +49,72 @@ module SecureHeaders
|
|
|
43
49
|
context "HttpOnly cookies" do
|
|
44
50
|
context "when configured with a boolean" do
|
|
45
51
|
it "flags cookies as HttpOnly" do
|
|
46
|
-
cookie = Cookie.new(raw_cookie, httponly: true)
|
|
52
|
+
cookie = Cookie.new(raw_cookie, httponly: true, secure: OPT_OUT, samesite: OPT_OUT)
|
|
47
53
|
expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
|
|
48
54
|
end
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
context "when configured with a Hash" do
|
|
52
58
|
it "flags cookies as HttpOnly when whitelisted" do
|
|
53
|
-
cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]})
|
|
59
|
+
cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]}, secure: OPT_OUT, samesite: OPT_OUT)
|
|
54
60
|
expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
|
|
55
61
|
end
|
|
56
62
|
|
|
57
63
|
it "does not flag cookies as HttpOnly when excluded" do
|
|
58
|
-
cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] })
|
|
64
|
+
cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] }, secure: OPT_OUT, samesite: OPT_OUT)
|
|
59
65
|
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
60
66
|
end
|
|
61
67
|
end
|
|
62
68
|
end
|
|
63
69
|
|
|
64
70
|
context "SameSite cookies" do
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
end
|
|
71
|
+
it "flags SameSite=Lax" do
|
|
72
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
73
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
|
74
|
+
end
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
it "flags SameSite=Lax when configured with a boolean" do
|
|
77
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: true}, secure: OPT_OUT, httponly: OPT_OUT)
|
|
78
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
|
79
|
+
end
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
it "does not flag cookies as SameSite=Lax when excluded" do
|
|
82
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
83
|
+
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "flags SameSite=Strict" do
|
|
87
|
+
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
88
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "does not flag cookies as SameSite=Strict when excluded" do
|
|
92
|
+
cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] }}, secure: OPT_OUT, httponly: OPT_OUT)
|
|
93
|
+
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
80
94
|
end
|
|
81
95
|
|
|
82
96
|
it "flags SameSite=Strict when configured with a boolean" do
|
|
83
|
-
cookie = Cookie.new(raw_cookie, samesite: { strict: true})
|
|
97
|
+
cookie = Cookie.new(raw_cookie, {samesite: { strict: true}, secure: OPT_OUT, httponly: OPT_OUT})
|
|
84
98
|
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
85
99
|
end
|
|
86
100
|
|
|
87
101
|
it "flags properly when both lax and strict are configured" do
|
|
88
102
|
raw_cookie = "_session=thisisatest"
|
|
89
|
-
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } })
|
|
103
|
+
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
90
104
|
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
91
105
|
end
|
|
92
106
|
|
|
93
107
|
it "ignores configuration if the cookie is already flagged" do
|
|
94
108
|
raw_cookie = "_session=thisisatest; SameSite=Strict"
|
|
95
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: true })
|
|
109
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: true }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
96
110
|
expect(cookie.to_s).to eq(raw_cookie)
|
|
97
111
|
end
|
|
112
|
+
|
|
113
|
+
it "samesite: true sets all cookies to samesite=lax" do
|
|
114
|
+
raw_cookie = "_session=thisisatest"
|
|
115
|
+
cookie = Cookie.new(raw_cookie, samesite: true, secure: OPT_OUT, httponly: OPT_OUT)
|
|
116
|
+
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
|
117
|
+
end
|
|
98
118
|
end
|
|
99
119
|
end
|
|
100
120
|
|
|
@@ -105,12 +125,18 @@ module SecureHeaders
|
|
|
105
125
|
end.to raise_error(CookiesConfigError)
|
|
106
126
|
end
|
|
107
127
|
|
|
108
|
-
it "raises an exception when configured without a boolean/Hash" do
|
|
128
|
+
it "raises an exception when configured without a boolean(true or OPT_OUT)/Hash" do
|
|
109
129
|
expect do
|
|
110
130
|
Cookie.validate_config!(secure: "true")
|
|
111
131
|
end.to raise_error(CookiesConfigError)
|
|
112
132
|
end
|
|
113
133
|
|
|
134
|
+
it "raises an exception when configured with false" do
|
|
135
|
+
expect do
|
|
136
|
+
Cookie.validate_config!(secure: false)
|
|
137
|
+
end.to raise_error(CookiesConfigError)
|
|
138
|
+
end
|
|
139
|
+
|
|
114
140
|
it "raises an exception when both only and except filters are provided" do
|
|
115
141
|
expect do
|
|
116
142
|
Cookie.validate_config!(secure: { only: [], except: [] })
|
|
@@ -123,15 +149,10 @@ module SecureHeaders
|
|
|
123
149
|
end.to raise_error(CookiesConfigError)
|
|
124
150
|
end
|
|
125
151
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
expect do
|
|
131
|
-
Cookie.validate_config!(samesite: { flag => true, other_flag => true})
|
|
132
|
-
end.to raise_error(CookiesConfigError)
|
|
133
|
-
end
|
|
134
|
-
end
|
|
152
|
+
it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
|
|
153
|
+
expect do
|
|
154
|
+
Cookie.validate_config!(samesite: { lax: true, strict: true})
|
|
155
|
+
end.to raise_error(CookiesConfigError)
|
|
135
156
|
end
|
|
136
157
|
|
|
137
158
|
it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
|
|
@@ -3,13 +3,13 @@ require "spec_helper"
|
|
|
3
3
|
|
|
4
4
|
module SecureHeaders
|
|
5
5
|
describe ExpectCertificateTransparency do
|
|
6
|
-
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce
|
|
6
|
+
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce; max-age=1234") }
|
|
7
7
|
specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
|
|
8
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
|
|
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
10
|
specify do
|
|
11
11
|
config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" }
|
|
12
|
-
header_value = "enforce
|
|
12
|
+
header_value = "enforce; max-age=1234; report-uri=\"https://report-uri.io/expect-ct\""
|
|
13
13
|
expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe PolicyManagement do
|
|
@@ -16,7 +17,7 @@ module SecureHeaders
|
|
|
16
17
|
it "accepts all keys" do
|
|
17
18
|
# (pulled from README)
|
|
18
19
|
config = {
|
|
19
|
-
# "meta" values. these will
|
|
20
|
+
# "meta" values. these will shape the header, but the values are not included in the header.
|
|
20
21
|
report_only: true, # default: false
|
|
21
22
|
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
|
|
22
23
|
|
|
@@ -32,7 +33,6 @@ module SecureHeaders
|
|
|
32
33
|
object_src: %w('self'),
|
|
33
34
|
script_src: %w('self'),
|
|
34
35
|
style_src: %w('unsafe-inline'),
|
|
35
|
-
worker_src: %w(worker.com),
|
|
36
36
|
base_uri: %w('self'),
|
|
37
37
|
form_action: %w('self' github.com),
|
|
38
38
|
frame_ancestors: %w('none'),
|
|
@@ -47,10 +47,22 @@ module SecureHeaders
|
|
|
47
47
|
|
|
48
48
|
it "requires a :default_src value" do
|
|
49
49
|
expect do
|
|
50
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
|
|
50
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %w('self')))
|
|
51
51
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
it "requires a :script_src value" do
|
|
55
|
+
expect do
|
|
56
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self')))
|
|
57
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "accepts OPT_OUT as a script-src value" do
|
|
61
|
+
expect do
|
|
62
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: OPT_OUT))
|
|
63
|
+
end.to_not raise_error
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
it "requires :report_only to be a truthy value" do
|
|
55
67
|
expect do
|
|
56
68
|
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
|
|
@@ -96,7 +108,7 @@ module SecureHeaders
|
|
|
96
108
|
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
|
97
109
|
it "performs light validation on source lists" do
|
|
98
110
|
expect do
|
|
99
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
|
|
111
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval), script_src: %w('self')))
|
|
100
112
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
101
113
|
end
|
|
102
114
|
|
|
@@ -135,19 +147,21 @@ module SecureHeaders
|
|
|
135
147
|
it "combines the default-src value with the override if the directive was unconfigured" do
|
|
136
148
|
Configuration.default do |config|
|
|
137
149
|
config.csp = {
|
|
138
|
-
default_src: %w(https:)
|
|
150
|
+
default_src: %w(https:),
|
|
151
|
+
script_src: %w('self'),
|
|
139
152
|
}
|
|
140
153
|
end
|
|
141
|
-
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h,
|
|
154
|
+
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, style_src: %w(anothercdn.com))
|
|
142
155
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
143
156
|
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
|
|
144
|
-
expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
|
|
157
|
+
expect(csp.value).to eq("default-src https:; script-src 'self'; style-src https: anothercdn.com")
|
|
145
158
|
end
|
|
146
159
|
|
|
147
160
|
it "combines directives where the original value is nil and the hash is frozen" do
|
|
148
161
|
Configuration.default do |config|
|
|
149
162
|
config.csp = {
|
|
150
163
|
default_src: %w('self'),
|
|
164
|
+
script_src: %w('self'),
|
|
151
165
|
report_only: false
|
|
152
166
|
}.freeze
|
|
153
167
|
end
|
|
@@ -161,6 +175,7 @@ module SecureHeaders
|
|
|
161
175
|
Configuration.default do |config|
|
|
162
176
|
config.csp = {
|
|
163
177
|
default_src: %w('self'),
|
|
178
|
+
script_src: %w('self'),
|
|
164
179
|
report_only: false
|
|
165
180
|
}.freeze
|
|
166
181
|
end
|
|
@@ -172,14 +187,13 @@ module SecureHeaders
|
|
|
172
187
|
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
|
|
173
188
|
expect(combined_config[directive]).to eq(%w("http://example.org))
|
|
174
189
|
end
|
|
175
|
-
|
|
176
|
-
ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox]).value
|
|
177
190
|
end
|
|
178
191
|
|
|
179
192
|
it "overrides the report_only flag" do
|
|
180
193
|
Configuration.default do |config|
|
|
181
194
|
config.csp = {
|
|
182
195
|
default_src: %w('self'),
|
|
196
|
+
script_src: %w('self'),
|
|
183
197
|
report_only: false
|
|
184
198
|
}
|
|
185
199
|
end
|
|
@@ -192,12 +206,13 @@ module SecureHeaders
|
|
|
192
206
|
Configuration.default do |config|
|
|
193
207
|
config.csp = {
|
|
194
208
|
default_src: %w(https:),
|
|
209
|
+
script_src: %w('self'),
|
|
195
210
|
block_all_mixed_content: false
|
|
196
211
|
}
|
|
197
212
|
end
|
|
198
213
|
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
|
|
199
214
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
200
|
-
expect(csp.value).to eq("default-src https:; block-all-mixed-content")
|
|
215
|
+
expect(csp.value).to eq("default-src https:; block-all-mixed-content; script-src 'self'")
|
|
201
216
|
end
|
|
202
217
|
|
|
203
218
|
it "raises an error if appending to a OPT_OUT policy" do
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe PublicKeyPins do
|
|
@@ -8,7 +9,7 @@ module SecureHeaders
|
|
|
8
9
|
specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
|
|
9
10
|
specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
|
|
10
11
|
specify do
|
|
11
|
-
config = { max_age: 1234, pins: [{ sha256:
|
|
12
|
+
config = { max_age: 1234, pins: [{ sha256: "base64encodedpin1" }, { sha256: "base64encodedpin2" }] }
|
|
12
13
|
header_value = "max-age=1234; pin-sha256=\"base64encodedpin1\"; pin-sha256=\"base64encodedpin2\""
|
|
13
14
|
expect(PublicKeyPins.new(config).value).to eq(header_value)
|
|
14
15
|
end
|
|
@@ -16,19 +17,19 @@ module SecureHeaders
|
|
|
16
17
|
context "with an invalid configuration" do
|
|
17
18
|
it "raises an exception when max-age is not provided" do
|
|
18
19
|
expect do
|
|
19
|
-
PublicKeyPins.validate_config!(foo:
|
|
20
|
+
PublicKeyPins.validate_config!(foo: "bar")
|
|
20
21
|
end.to raise_error(PublicKeyPinsConfigError)
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
it "raises an exception with an invalid max-age" do
|
|
24
25
|
expect do
|
|
25
|
-
PublicKeyPins.validate_config!(max_age:
|
|
26
|
+
PublicKeyPins.validate_config!(max_age: "abc123")
|
|
26
27
|
end.to raise_error(PublicKeyPinsConfigError)
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
it
|
|
30
|
+
it "raises an exception with less than 2 pins" do
|
|
30
31
|
expect do
|
|
31
|
-
config = { max_age: 1234, pins: [{ sha256:
|
|
32
|
+
config = { max_age: 1234, pins: [{ sha256: "base64encodedpin" }] }
|
|
32
33
|
PublicKeyPins.validate_config!(config)
|
|
33
34
|
end.to raise_error(PublicKeyPinsConfigError)
|
|
34
35
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe ReferrerPolicy do
|
|
5
6
|
specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
|
|
6
|
-
specify { expect(ReferrerPolicy.make_header(
|
|
7
|
+
specify { expect(ReferrerPolicy.make_header("no-referrer")).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
|
|
7
8
|
|
|
8
9
|
context "valid configuration values" do
|
|
9
10
|
it "accepts 'no-referrer'" do
|
|
@@ -61,7 +62,7 @@ module SecureHeaders
|
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
context
|
|
65
|
+
context "invlaid configuration values" do
|
|
65
66
|
it "doesn't accept invalid values" do
|
|
66
67
|
expect do
|
|
67
68
|
ReferrerPolicy.validate_config!("open")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe StrictTransportSecurity do
|
|
@@ -10,19 +11,19 @@ module SecureHeaders
|
|
|
10
11
|
context "with a string argument" do
|
|
11
12
|
it "raises an exception with an invalid max-age" do
|
|
12
13
|
expect do
|
|
13
|
-
StrictTransportSecurity.validate_config!(
|
|
14
|
+
StrictTransportSecurity.validate_config!("max-age=abc123")
|
|
14
15
|
end.to raise_error(STSConfigError)
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
it "raises an exception if max-age is not supplied" do
|
|
18
19
|
expect do
|
|
19
|
-
StrictTransportSecurity.validate_config!(
|
|
20
|
+
StrictTransportSecurity.validate_config!("includeSubdomains")
|
|
20
21
|
end.to raise_error(STSConfigError)
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
it "raises an exception with an invalid format" do
|
|
24
25
|
expect do
|
|
25
|
-
StrictTransportSecurity.validate_config!(
|
|
26
|
+
StrictTransportSecurity.validate_config!("max-age=123includeSubdomains")
|
|
26
27
|
end.to raise_error(STSConfigError)
|
|
27
28
|
end
|
|
28
29
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe XDownloadOptions do
|
|
5
6
|
specify { expect(XDownloadOptions.make_header).to eq([XDownloadOptions::HEADER_NAME, XDownloadOptions::DEFAULT_VALUE]) }
|
|
6
|
-
specify { expect(XDownloadOptions.make_header(
|
|
7
|
+
specify { expect(XDownloadOptions.make_header("noopen")).to eq([XDownloadOptions::HEADER_NAME, "noopen"]) }
|
|
7
8
|
|
|
8
9
|
context "invalid configuration values" do
|
|
9
10
|
it "accepts noopen" do
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe XPermittedCrossDomainPolicies do
|
|
5
6
|
specify { expect(XPermittedCrossDomainPolicies.make_header).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "none"]) }
|
|
6
|
-
specify { expect(XPermittedCrossDomainPolicies.make_header(
|
|
7
|
+
specify { expect(XPermittedCrossDomainPolicies.make_header("master-only")).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "master-only"]) }
|
|
7
8
|
|
|
8
9
|
context "valid configuration values" do
|
|
9
10
|
it "accepts 'all'" do
|
|
@@ -36,7 +37,7 @@ module SecureHeaders
|
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
context
|
|
40
|
+
context "invlaid configuration values" do
|
|
40
41
|
it "doesn't accept invalid values" do
|
|
41
42
|
expect do
|
|
42
43
|
XPermittedCrossDomainPolicies.validate_config!("open")
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe XXssProtection do
|
|
5
6
|
specify { expect(XXssProtection.make_header).to eq([XXssProtection::HEADER_NAME, XXssProtection::DEFAULT_VALUE]) }
|
|
6
|
-
specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME,
|
|
7
|
+
specify { expect(XXssProtection.make_header("1; mode=block; report=https://www.secure.com/reports")).to eq([XXssProtection::HEADER_NAME, "1; mode=block; report=https://www.secure.com/reports"]) }
|
|
7
8
|
|
|
8
9
|
context "with invalid configuration" do
|
|
9
10
|
it "should raise an error when providing a string that is not valid" do
|
|
@@ -19,7 +20,7 @@ module SecureHeaders
|
|
|
19
20
|
context "when using a hash value" do
|
|
20
21
|
it "should allow string values ('1' or '0' are the only valid strings)" do
|
|
21
22
|
expect do
|
|
22
|
-
XXssProtection.validate_config!(
|
|
23
|
+
XXssProtection.validate_config!("1")
|
|
23
24
|
end.not_to raise_error
|
|
24
25
|
end
|
|
25
26
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
@@ -19,8 +20,8 @@ module SecureHeaders
|
|
|
19
20
|
config.hpkp = {
|
|
20
21
|
max_age: 10000000,
|
|
21
22
|
pins: [
|
|
22
|
-
{sha256:
|
|
23
|
-
{sha256:
|
|
23
|
+
{sha256: "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
|
|
24
|
+
{sha256: "73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"}
|
|
24
25
|
],
|
|
25
26
|
report_uri: "https://#{report_host}/example-hpkp"
|
|
26
27
|
}
|
|
@@ -54,68 +55,75 @@ module SecureHeaders
|
|
|
54
55
|
expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
|
|
55
56
|
end
|
|
56
57
|
|
|
57
|
-
context "
|
|
58
|
+
context "cookies" do
|
|
58
59
|
context "cookies should be flagged" do
|
|
59
60
|
it "flags cookies as secure" do
|
|
60
|
-
|
|
61
|
-
Configuration.default { |config| config.secure_cookies = true }
|
|
62
|
-
end
|
|
61
|
+
Configuration.default { |config| config.cookies = {secure: true, httponly: OPT_OUT, samesite: OPT_OUT} }
|
|
63
62
|
request = Rack::Request.new("HTTPS" => "on")
|
|
64
63
|
_, env = cookie_middleware.call request.env
|
|
65
|
-
expect(env[
|
|
64
|
+
expect(env["Set-Cookie"]).to eq("foo=bar; secure")
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
67
|
|
|
68
|
+
it "allows opting out of cookie protection with OPT_OUT alone" do
|
|
69
|
+
Configuration.default { |config| config.cookies = OPT_OUT}
|
|
70
|
+
|
|
71
|
+
# do NOT make this request https. non-https requests modify a config,
|
|
72
|
+
# causing an exception when operating on OPT_OUT. This ensures we don't
|
|
73
|
+
# try to modify the config.
|
|
74
|
+
request = Rack::Request.new({})
|
|
75
|
+
_, env = cookie_middleware.call request.env
|
|
76
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
77
|
+
end
|
|
78
|
+
|
|
69
79
|
context "cookies should not be flagged" do
|
|
70
80
|
it "does not flags cookies as secure" do
|
|
71
|
-
|
|
72
|
-
Configuration.default { |config| config.secure_cookies = false }
|
|
73
|
-
end
|
|
81
|
+
Configuration.default { |config| config.cookies = {secure: OPT_OUT, httponly: OPT_OUT, samesite: OPT_OUT} }
|
|
74
82
|
request = Rack::Request.new("HTTPS" => "on")
|
|
75
83
|
_, env = cookie_middleware.call request.env
|
|
76
|
-
expect(env[
|
|
84
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
77
85
|
end
|
|
78
86
|
end
|
|
79
87
|
end
|
|
80
88
|
|
|
81
89
|
context "cookies" do
|
|
82
90
|
it "flags cookies from configuration" do
|
|
83
|
-
Configuration.default { |config| config.cookies = { secure: true, httponly: true } }
|
|
91
|
+
Configuration.default { |config| config.cookies = { secure: true, httponly: true, samesite: { lax: true} } }
|
|
84
92
|
request = Rack::Request.new("HTTPS" => "on")
|
|
85
93
|
_, env = cookie_middleware.call request.env
|
|
86
94
|
|
|
87
|
-
expect(env[
|
|
95
|
+
expect(env["Set-Cookie"]).to eq("foo=bar; secure; HttpOnly; SameSite=Lax")
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
it "flags cookies with a combination of SameSite configurations" do
|
|
91
99
|
cookie_middleware = Middleware.new(lambda { |env| [200, env.merge("Set-Cookie" => ["_session=foobar", "_guest=true"]), "app"] })
|
|
92
100
|
|
|
93
|
-
Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } } } }
|
|
101
|
+
Configuration.default { |config| config.cookies = { samesite: { lax: { except: ["_session"] }, strict: { only: ["_session"] } }, httponly: OPT_OUT, secure: OPT_OUT} }
|
|
94
102
|
request = Rack::Request.new("HTTPS" => "on")
|
|
95
103
|
_, env = cookie_middleware.call request.env
|
|
96
104
|
|
|
97
|
-
expect(env[
|
|
98
|
-
expect(env[
|
|
105
|
+
expect(env["Set-Cookie"]).to match("_session=foobar; SameSite=Strict")
|
|
106
|
+
expect(env["Set-Cookie"]).to match("_guest=true; SameSite=Lax")
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
it "disables secure cookies for non-https requests" do
|
|
102
|
-
Configuration.default { |config| config.cookies = { secure: true } }
|
|
110
|
+
Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
|
|
103
111
|
|
|
104
112
|
request = Rack::Request.new("HTTPS" => "off")
|
|
105
113
|
_, env = cookie_middleware.call request.env
|
|
106
|
-
expect(env[
|
|
114
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
107
115
|
end
|
|
108
116
|
|
|
109
117
|
it "sets the secure cookie flag correctly on interleaved http/https requests" do
|
|
110
|
-
Configuration.default { |config| config.cookies = { secure: true } }
|
|
118
|
+
Configuration.default { |config| config.cookies = { secure: true, httponly: OPT_OUT, samesite: OPT_OUT } }
|
|
111
119
|
|
|
112
120
|
request = Rack::Request.new("HTTPS" => "off")
|
|
113
121
|
_, env = cookie_middleware.call request.env
|
|
114
|
-
expect(env[
|
|
122
|
+
expect(env["Set-Cookie"]).to eq("foo=bar")
|
|
115
123
|
|
|
116
124
|
request = Rack::Request.new("HTTPS" => "on")
|
|
117
125
|
_, env = cookie_middleware.call request.env
|
|
118
|
-
expect(env[
|
|
126
|
+
expect(env["Set-Cookie"]).to eq("foo=bar; secure")
|
|
119
127
|
end
|
|
120
128
|
end
|
|
121
129
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require "spec_helper"
|
|
2
3
|
require "erb"
|
|
3
4
|
|
|
@@ -58,7 +59,7 @@ TEMPLATE
|
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
if options.is_a?(Hash)
|
|
61
|
-
options = options.map {|k,v| " #{k}=#{v}"}
|
|
62
|
+
options = options.map {|k, v| " #{k}=#{v}"}
|
|
62
63
|
end
|
|
63
64
|
"<#{type}#{options}>#{content}</#{type}>"
|
|
64
65
|
end
|
|
@@ -82,9 +83,9 @@ module SecureHeaders
|
|
|
82
83
|
before(:all) do
|
|
83
84
|
Configuration.default do |config|
|
|
84
85
|
config.csp = {
|
|
85
|
-
:
|
|
86
|
-
:
|
|
87
|
-
:
|
|
86
|
+
default_src: %w('self'),
|
|
87
|
+
script_src: %w('self'),
|
|
88
|
+
style_src: %w('self')
|
|
88
89
|
}
|
|
89
90
|
end
|
|
90
91
|
end
|