secure_headers 3.6.5 → 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 +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +8 -6
- data/CHANGELOG.md +16 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +7 -4
- data/Guardfile +1 -0
- data/README.md +14 -18
- data/Rakefile +22 -18
- data/docs/cookies.md +16 -2
- data/docs/per_action_configuration.md +28 -0
- data/lib/secure_headers/configuration.rb +9 -13
- 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 +52 -20
- data/lib/secure_headers/headers/content_security_policy_config.rb +1 -1
- data/lib/secure_headers/headers/cookie.rb +21 -3
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
- data/lib/secure_headers/headers/policy_management.rb +112 -65
- 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 +13 -12
- data/lib/secure_headers/view_helper.rb +2 -1
- data/lib/secure_headers.rb +4 -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 +42 -26
- data/spec/lib/secure_headers/headers/cookie_spec.rb +38 -20
- data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +57 -10
- 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 +10 -22
- data/upgrading-to-4-0.md +55 -0
- metadata +16 -6
data/lib/tasks/tasks.rake
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx unless defined? INLINE_SCRIPT_REGEX
|
|
2
3
|
INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx unless defined? INLINE_STYLE_REGEX
|
|
3
4
|
INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_SCRIPT_HELPER_REGEX
|
|
@@ -73,7 +74,7 @@ namespace :secure_headers do
|
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
|
76
|
-
File.open(SecureHeaders::Configuration::HASH_CONFIG_FILE,
|
|
77
|
+
File.open(SecureHeaders::Configuration::HASH_CONFIG_FILE, "w") do |file|
|
|
77
78
|
file.write(script_hashes.to_yaml)
|
|
78
79
|
end
|
|
79
80
|
|
data/secure_headers.gemspec
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
Gem::Specification.new do |gem|
|
|
3
4
|
gem.name = "secure_headers"
|
|
4
|
-
gem.version = "
|
|
5
|
+
gem.version = "4.0.0"
|
|
5
6
|
gem.authors = ["Neil Matatall"]
|
|
6
7
|
gem.email = ["neil.matatall@gmail.com"]
|
|
7
|
-
gem.description =
|
|
8
|
+
gem.description = "Manages application of security headers with many safe defaults."
|
|
8
9
|
gem.summary = 'Add easily configured security headers to responses
|
|
9
10
|
including content-security-policy, x-frame-options,
|
|
10
11
|
strict-transport-security, etc.'
|
|
@@ -15,5 +16,14 @@ Gem::Specification.new do |gem|
|
|
|
15
16
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
16
17
|
gem.require_paths = ["lib"]
|
|
17
18
|
gem.add_development_dependency "rake"
|
|
18
|
-
gem.add_dependency "useragent"
|
|
19
|
+
gem.add_dependency "useragent", ">= 0.15.0"
|
|
20
|
+
|
|
21
|
+
# TODO: delete this after 4.1 is cut or a number of 4.0.x releases have occurred
|
|
22
|
+
gem.post_install_message = <<-POST_INSTALL
|
|
23
|
+
|
|
24
|
+
**********
|
|
25
|
+
:wave: secure_headers 4.0 introduces a lot of breaking changes (in the name of security!). It's highly likely you will need to update your secure_headers cookie configuration to avoid breaking things. See the upgrade guide for details: https://github.com/twitter/secureheaders/blob/master/upgrading-to-4-0.md
|
|
26
|
+
**********
|
|
27
|
+
|
|
28
|
+
POST_INSTALL
|
|
19
29
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe Configuration do
|
|
@@ -70,7 +71,7 @@ module SecureHeaders
|
|
|
70
71
|
|
|
71
72
|
it "allows you to override an override" do
|
|
72
73
|
Configuration.override(:override) do |config|
|
|
73
|
-
config.csp = { default_src: %w('self')}
|
|
74
|
+
config.csp = { default_src: %w('self'), script_src: %w('self')}
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
Configuration.override(:second_override, :override) do |config|
|
|
@@ -78,17 +79,17 @@ module SecureHeaders
|
|
|
78
79
|
end
|
|
79
80
|
|
|
80
81
|
original_override = Configuration.get(:override)
|
|
81
|
-
expect(original_override.csp.to_h).to eq(default_src: %w('self'))
|
|
82
|
+
expect(original_override.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self'))
|
|
82
83
|
override_config = Configuration.get(:second_override)
|
|
83
84
|
expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
it "deprecates the secure_cookies configuration" do
|
|
87
|
-
expect
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
expect {
|
|
89
|
+
Configuration.default do |config|
|
|
90
|
+
config.secure_cookies = true
|
|
91
|
+
end
|
|
92
|
+
}.to raise_error(ArgumentError)
|
|
92
93
|
end
|
|
93
94
|
end
|
|
94
95
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
describe ContentSecurityPolicy do
|
|
@@ -23,6 +24,10 @@ module SecureHeaders
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
describe "#value" do
|
|
27
|
+
it "uses a safe but non-breaking default value" do
|
|
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
|
+
end
|
|
30
|
+
|
|
26
31
|
it "discards 'none' values if any other source expressions are present" do
|
|
27
32
|
csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
|
|
28
33
|
expect(csp.value).not_to include("'none'")
|
|
@@ -46,13 +51,18 @@ module SecureHeaders
|
|
|
46
51
|
expect(csp.value).to eq("default-src example.org")
|
|
47
52
|
end
|
|
48
53
|
|
|
54
|
+
it "does not build directives with a value of OPT_OUT (and bypasses directive requirements)" do
|
|
55
|
+
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), script_src: OPT_OUT)
|
|
56
|
+
expect(csp.value).to eq("default-src example.org")
|
|
57
|
+
end
|
|
58
|
+
|
|
49
59
|
it "does not remove schemes from report-uri values" do
|
|
50
60
|
csp = ContentSecurityPolicy.new(default_src: %w(https:), report_uri: %w(https://example.org))
|
|
51
61
|
expect(csp.value).to eq("default-src https:; report-uri https://example.org")
|
|
52
62
|
end
|
|
53
63
|
|
|
54
64
|
it "does not remove schemes when :preserve_schemes is true" do
|
|
55
|
-
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), :
|
|
65
|
+
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), preserve_schemes: true)
|
|
56
66
|
expect(csp.value).to eq("default-src https://example.org")
|
|
57
67
|
end
|
|
58
68
|
|
|
@@ -86,25 +96,30 @@ module SecureHeaders
|
|
|
86
96
|
expect(csp.value).to eq("default-src example.org")
|
|
87
97
|
end
|
|
88
98
|
|
|
99
|
+
it "creates maximally strict sandbox policy when passed no sandbox token values" do
|
|
100
|
+
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: [])
|
|
101
|
+
expect(csp.value).to eq("default-src example.org; sandbox")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "creates maximally strict sandbox policy when passed true" do
|
|
105
|
+
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: true)
|
|
106
|
+
expect(csp.value).to eq("default-src example.org; sandbox")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "creates sandbox policy when passed valid sandbox token values" do
|
|
110
|
+
csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: %w(allow-forms allow-scripts))
|
|
111
|
+
expect(csp.value).to eq("default-src example.org; sandbox allow-forms allow-scripts")
|
|
112
|
+
end
|
|
113
|
+
|
|
89
114
|
it "does not emit a warning when using frame-src" do
|
|
90
115
|
expect(Kernel).to_not receive(:warn)
|
|
91
116
|
ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
|
|
92
117
|
end
|
|
93
118
|
|
|
94
|
-
it "
|
|
95
|
-
expect
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
it "will still set inconsistent child/frame-src values to be less surprising" do
|
|
100
|
-
expect(Kernel).to receive(:warn).at_least(:once)
|
|
101
|
-
firefox = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox]).value
|
|
102
|
-
firefox_transitional = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox46]).value
|
|
103
|
-
expect(firefox).not_to eq(firefox_transitional)
|
|
104
|
-
expect(firefox).to match(/frame-src/)
|
|
105
|
-
expect(firefox).not_to match(/child-src/)
|
|
106
|
-
expect(firefox_transitional).to match(/child-src/)
|
|
107
|
-
expect(firefox_transitional).not_to match(/frame-src/)
|
|
119
|
+
it "raises an error when child-src and frame-src are supplied but are not equal" do
|
|
120
|
+
expect {
|
|
121
|
+
ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
|
|
122
|
+
}.to raise_error(ArgumentError)
|
|
108
123
|
end
|
|
109
124
|
|
|
110
125
|
it "supports strict-dynamic" do
|
|
@@ -119,52 +134,53 @@ module SecureHeaders
|
|
|
119
134
|
end.merge({
|
|
120
135
|
block_all_mixed_content: true,
|
|
121
136
|
upgrade_insecure_requests: true,
|
|
122
|
-
reflected_xss: "block",
|
|
123
137
|
script_src: %w(script-src.com),
|
|
124
|
-
script_nonce: 123456
|
|
138
|
+
script_nonce: 123456,
|
|
139
|
+
sandbox: %w(allow-forms),
|
|
140
|
+
plugin_types: %w(application/pdf)
|
|
125
141
|
})
|
|
126
142
|
end
|
|
127
143
|
|
|
128
144
|
it "does not filter any directives for Chrome" do
|
|
129
145
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
|
|
130
|
-
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; media-src media-src.com; object-src object-src.com; plugin-types
|
|
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'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
131
147
|
end
|
|
132
148
|
|
|
133
149
|
it "does not filter any directives for Opera" do
|
|
134
150
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
|
|
135
|
-
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; media-src media-src.com; object-src object-src.com; plugin-types
|
|
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'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
136
152
|
end
|
|
137
153
|
|
|
138
154
|
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
|
|
139
155
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
|
|
140
|
-
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; media-src media-src.com; object-src object-src.com; sandbox
|
|
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'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
141
157
|
end
|
|
142
158
|
|
|
143
159
|
it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
|
|
144
160
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
|
|
145
|
-
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; sandbox
|
|
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'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
146
162
|
end
|
|
147
163
|
|
|
148
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, nonce sources, hash sources, and plugin-types for Edge" do
|
|
149
165
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
|
|
150
|
-
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
|
|
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 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
|
|
151
167
|
end
|
|
152
168
|
|
|
153
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, nonce sources, hash sources, and plugin-types for safari" do
|
|
154
170
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
|
|
155
|
-
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
|
|
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 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
|
|
156
172
|
end
|
|
157
173
|
|
|
158
174
|
it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
|
|
159
175
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
|
|
160
|
-
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
|
|
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'; style-src style-src.com; report-uri report-uri.com")
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
it "falls back to standard Firefox defaults when the useragent version is not present" do
|
|
164
180
|
ua = USER_AGENTS[:firefox].dup
|
|
165
181
|
allow(ua).to receive(:version).and_return(nil)
|
|
166
182
|
policy = ContentSecurityPolicy.new(complex_opts, ua)
|
|
167
|
-
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; media-src media-src.com; object-src object-src.com; sandbox
|
|
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'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
168
184
|
end
|
|
169
185
|
end
|
|
170
186
|
end
|
|
@@ -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,19 +49,19 @@ 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
|
|
@@ -63,46 +69,52 @@ module SecureHeaders
|
|
|
63
69
|
|
|
64
70
|
context "SameSite cookies" do
|
|
65
71
|
it "flags SameSite=Lax" do
|
|
66
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } })
|
|
72
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
67
73
|
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
it "flags SameSite=Lax when configured with a boolean" do
|
|
71
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: true})
|
|
77
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: true}, secure: OPT_OUT, httponly: OPT_OUT)
|
|
72
78
|
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
|
|
73
79
|
end
|
|
74
80
|
|
|
75
81
|
it "does not flag cookies as SameSite=Lax when excluded" do
|
|
76
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } })
|
|
82
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
77
83
|
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
78
84
|
end
|
|
79
85
|
|
|
80
86
|
it "flags SameSite=Strict" do
|
|
81
|
-
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } })
|
|
87
|
+
cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
82
88
|
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
83
89
|
end
|
|
84
90
|
|
|
85
91
|
it "does not flag cookies as SameSite=Strict when excluded" do
|
|
86
|
-
cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] }
|
|
92
|
+
cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] }}, secure: OPT_OUT, httponly: OPT_OUT)
|
|
87
93
|
expect(cookie.to_s).to eq("_session=thisisatest")
|
|
88
94
|
end
|
|
89
95
|
|
|
90
96
|
it "flags SameSite=Strict when configured with a boolean" do
|
|
91
|
-
cookie = Cookie.new(raw_cookie, samesite: { strict: true})
|
|
97
|
+
cookie = Cookie.new(raw_cookie, {samesite: { strict: true}, secure: OPT_OUT, httponly: OPT_OUT})
|
|
92
98
|
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
93
99
|
end
|
|
94
100
|
|
|
95
101
|
it "flags properly when both lax and strict are configured" do
|
|
96
102
|
raw_cookie = "_session=thisisatest"
|
|
97
|
-
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)
|
|
98
104
|
expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
it "ignores configuration if the cookie is already flagged" do
|
|
102
108
|
raw_cookie = "_session=thisisatest; SameSite=Strict"
|
|
103
|
-
cookie = Cookie.new(raw_cookie, samesite: { lax: true })
|
|
109
|
+
cookie = Cookie.new(raw_cookie, samesite: { lax: true }, secure: OPT_OUT, httponly: OPT_OUT)
|
|
104
110
|
expect(cookie.to_s).to eq(raw_cookie)
|
|
105
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
|
|
106
118
|
end
|
|
107
119
|
end
|
|
108
120
|
|
|
@@ -113,12 +125,18 @@ module SecureHeaders
|
|
|
113
125
|
end.to raise_error(CookiesConfigError)
|
|
114
126
|
end
|
|
115
127
|
|
|
116
|
-
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
|
|
117
129
|
expect do
|
|
118
130
|
Cookie.validate_config!(secure: "true")
|
|
119
131
|
end.to raise_error(CookiesConfigError)
|
|
120
132
|
end
|
|
121
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
|
+
|
|
122
140
|
it "raises an exception when both only and except filters are provided" do
|
|
123
141
|
expect do
|
|
124
142
|
Cookie.validate_config!(secure: { only: [], except: [] })
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -27,6 +28,7 @@ module SecureHeaders
|
|
|
27
28
|
connect_src: %w(wss:),
|
|
28
29
|
font_src: %w('self' data:),
|
|
29
30
|
img_src: %w(mycdn.com data:),
|
|
31
|
+
manifest_src: %w(manifest.com),
|
|
30
32
|
media_src: %w(utoob.com),
|
|
31
33
|
object_src: %w('self'),
|
|
32
34
|
script_src: %w('self'),
|
|
@@ -45,10 +47,22 @@ module SecureHeaders
|
|
|
45
47
|
|
|
46
48
|
it "requires a :default_src value" do
|
|
47
49
|
expect do
|
|
48
|
-
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
|
|
50
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %w('self')))
|
|
49
51
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
50
52
|
end
|
|
51
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
|
+
|
|
52
66
|
it "requires :report_only to be a truthy value" do
|
|
53
67
|
expect do
|
|
54
68
|
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
|
|
@@ -94,28 +108,60 @@ module SecureHeaders
|
|
|
94
108
|
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
|
95
109
|
it "performs light validation on source lists" do
|
|
96
110
|
expect do
|
|
97
|
-
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')))
|
|
112
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "rejects anything not of the form allow-* as a sandbox value" do
|
|
116
|
+
expect do
|
|
117
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["steve"])))
|
|
118
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "accepts anything of the form allow-* as a sandbox value " do
|
|
122
|
+
expect do
|
|
123
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["allow-foo"])))
|
|
124
|
+
end.to_not raise_error
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "accepts true as a sandbox policy" do
|
|
128
|
+
expect do
|
|
129
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: true)))
|
|
130
|
+
end.to_not raise_error
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "rejects anything not of the form type/subtype as a plugin-type value" do
|
|
134
|
+
expect do
|
|
135
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["steve"])))
|
|
98
136
|
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
99
137
|
end
|
|
138
|
+
|
|
139
|
+
it "accepts anything of the form type/subtype as a plugin-type value " do
|
|
140
|
+
expect do
|
|
141
|
+
ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["application/pdf"])))
|
|
142
|
+
end.to_not raise_error
|
|
143
|
+
end
|
|
100
144
|
end
|
|
101
145
|
|
|
102
146
|
describe "#combine_policies" do
|
|
103
147
|
it "combines the default-src value with the override if the directive was unconfigured" do
|
|
104
148
|
Configuration.default do |config|
|
|
105
149
|
config.csp = {
|
|
106
|
-
default_src: %w(https:)
|
|
150
|
+
default_src: %w(https:),
|
|
151
|
+
script_src: %w('self'),
|
|
107
152
|
}
|
|
108
153
|
end
|
|
109
|
-
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))
|
|
110
155
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
111
156
|
expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
|
|
112
|
-
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")
|
|
113
158
|
end
|
|
114
159
|
|
|
115
160
|
it "combines directives where the original value is nil and the hash is frozen" do
|
|
116
161
|
Configuration.default do |config|
|
|
117
162
|
config.csp = {
|
|
118
163
|
default_src: %w('self'),
|
|
164
|
+
script_src: %w('self'),
|
|
119
165
|
report_only: false
|
|
120
166
|
}.freeze
|
|
121
167
|
end
|
|
@@ -129,6 +175,7 @@ module SecureHeaders
|
|
|
129
175
|
Configuration.default do |config|
|
|
130
176
|
config.csp = {
|
|
131
177
|
default_src: %w('self'),
|
|
178
|
+
script_src: %w('self'),
|
|
132
179
|
report_only: false
|
|
133
180
|
}.freeze
|
|
134
181
|
end
|
|
@@ -140,14 +187,13 @@ module SecureHeaders
|
|
|
140
187
|
ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
|
|
141
188
|
expect(combined_config[directive]).to eq(%w("http://example.org))
|
|
142
189
|
end
|
|
143
|
-
|
|
144
|
-
ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox]).value
|
|
145
190
|
end
|
|
146
191
|
|
|
147
192
|
it "overrides the report_only flag" do
|
|
148
193
|
Configuration.default do |config|
|
|
149
194
|
config.csp = {
|
|
150
195
|
default_src: %w('self'),
|
|
196
|
+
script_src: %w('self'),
|
|
151
197
|
report_only: false
|
|
152
198
|
}
|
|
153
199
|
end
|
|
@@ -160,12 +206,13 @@ module SecureHeaders
|
|
|
160
206
|
Configuration.default do |config|
|
|
161
207
|
config.csp = {
|
|
162
208
|
default_src: %w(https:),
|
|
209
|
+
script_src: %w('self'),
|
|
163
210
|
block_all_mixed_content: false
|
|
164
211
|
}
|
|
165
212
|
end
|
|
166
213
|
combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
|
|
167
214
|
csp = ContentSecurityPolicy.new(combined_config)
|
|
168
|
-
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'")
|
|
169
216
|
end
|
|
170
217
|
|
|
171
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
|