secure_headers 2.5.3 → 3.0.0.pre
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/.travis.yml +2 -1
- data/Gemfile +9 -16
- data/README.md +154 -331
- data/Rakefile +2 -36
- data/lib/secure_headers/configuration.rb +189 -0
- data/lib/secure_headers/headers/content_security_policy.rb +341 -254
- data/lib/secure_headers/headers/public_key_pins.rb +43 -58
- data/lib/secure_headers/headers/strict_transport_security.rb +21 -49
- data/lib/secure_headers/headers/x_content_type_options.rb +18 -33
- data/lib/secure_headers/headers/x_download_options.rb +18 -33
- data/lib/secure_headers/headers/x_frame_options.rb +24 -34
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -34
- data/lib/secure_headers/headers/x_xss_protection.rb +17 -48
- data/lib/secure_headers/middleware.rb +15 -0
- data/lib/secure_headers/padrino.rb +1 -2
- data/lib/secure_headers/railtie.rb +9 -6
- data/lib/secure_headers/view_helper.rb +27 -43
- data/lib/secure_headers.rb +254 -61
- data/secure_headers.gemspec +7 -12
- data/spec/lib/secure_headers/configuration_spec.rb +80 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +111 -276
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +17 -17
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +11 -43
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +11 -18
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +13 -17
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +15 -17
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +22 -39
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -30
- data/spec/lib/secure_headers/middleware_spec.rb +40 -0
- data/spec/lib/secure_headers_spec.rb +201 -339
- data/spec/spec_helper.rb +30 -30
- data/upgrading-to-3-0.md +35 -0
- metadata +14 -100
- data/fixtures/rails_3_2_22/.rspec +0 -1
- data/fixtures/rails_3_2_22/Gemfile +0 -6
- data/fixtures/rails_3_2_22/README.rdoc +0 -261
- data/fixtures/rails_3_2_22/Rakefile +0 -7
- data/fixtures/rails_3_2_22/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_22/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_3_2_22/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_22/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_3_2_22/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_3_2_22/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_3_2_22/config/application.rb +0 -14
- data/fixtures/rails_3_2_22/config/boot.rb +0 -6
- data/fixtures/rails_3_2_22/config/environment.rb +0 -5
- data/fixtures/rails_3_2_22/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_22/config/initializers/secure_headers.rb +0 -16
- data/fixtures/rails_3_2_22/config/routes.rb +0 -4
- data/fixtures/rails_3_2_22/config/script_hashes.yml +0 -5
- data/fixtures/rails_3_2_22/config.ru +0 -7
- data/fixtures/rails_3_2_22/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_22/spec/spec_helper.rb +0 -15
- data/fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/.rspec +0 -1
- data/fixtures/rails_3_2_22_no_init/Gemfile +0 -6
- data/fixtures/rails_3_2_22_no_init/README.rdoc +0 -261
- data/fixtures/rails_3_2_22_no_init/Rakefile +0 -7
- data/fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +0 -20
- data/fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb +0 -12
- data/fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb +0 -1
- data/fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb +0 -0
- data/fixtures/rails_3_2_22_no_init/config/application.rb +0 -17
- data/fixtures/rails_3_2_22_no_init/config/boot.rb +0 -6
- data/fixtures/rails_3_2_22_no_init/config/environment.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_22_no_init/config/routes.rb +0 -4
- data/fixtures/rails_3_2_22_no_init/config.ru +0 -4
- data/fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
- data/fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_22_no_init/spec/spec_helper.rb +0 -5
- data/fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_4_1_8/Gemfile +0 -5
- data/fixtures/rails_4_1_8/README.rdoc +0 -28
- data/fixtures/rails_4_1_8/Rakefile +0 -6
- data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_4_1_8/app/models/.keep +0 -0
- data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_4_1_8/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_4_1_8/config/application.rb +0 -15
- data/fixtures/rails_4_1_8/config/boot.rb +0 -4
- data/fixtures/rails_4_1_8/config/environment.rb +0 -5
- data/fixtures/rails_4_1_8/config/environments/test.rb +0 -10
- data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +0 -16
- data/fixtures/rails_4_1_8/config/routes.rb +0 -4
- data/fixtures/rails_4_1_8/config/script_hashes.yml +0 -5
- data/fixtures/rails_4_1_8/config/secrets.yml +0 -22
- data/fixtures/rails_4_1_8/config.ru +0 -4
- data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
- data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
- data/fixtures/rails_4_1_8/log/.keep +0 -0
- data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +0 -59
- data/fixtures/rails_4_1_8/spec/spec_helper.rb +0 -15
- data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
- data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
- data/lib/secure_headers/controller_extension.rb +0 -158
- data/lib/secure_headers/hash_helper.rb +0 -7
- data/lib/secure_headers/header.rb +0 -5
- data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +0 -22
- data/lib/secure_headers/version.rb +0 -3
- data/lib/tasks/tasks.rake +0 -48
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -46
|
@@ -2,336 +2,171 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module SecureHeaders
|
|
4
4
|
describe ContentSecurityPolicy do
|
|
5
|
-
let(:default_opts) do
|
|
5
|
+
let (:default_opts) do
|
|
6
6
|
{
|
|
7
|
-
:
|
|
8
|
-
:
|
|
9
|
-
:
|
|
10
|
-
:
|
|
11
|
-
:
|
|
7
|
+
default_src: %w(https:),
|
|
8
|
+
img_src: %w(https: data:),
|
|
9
|
+
script_src: %w('unsafe-inline' 'unsafe-eval' https: data:),
|
|
10
|
+
style_src: %w('unsafe-inline' https: about:),
|
|
11
|
+
report_uri: %w(/csp_report)
|
|
12
12
|
}
|
|
13
13
|
end
|
|
14
|
-
let(:controller) { DummyClass.new }
|
|
15
|
-
|
|
16
|
-
IE = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"
|
|
17
|
-
FIREFOX = "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8"
|
|
18
|
-
FIREFOX_23 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0"
|
|
19
|
-
CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4"
|
|
20
|
-
CHROME_25 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
|
|
21
|
-
SAFARI = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"
|
|
22
|
-
OPERA = "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
|
|
23
|
-
|
|
24
|
-
def request_for user_agent, request_uri=nil, options={:ssl => false}
|
|
25
|
-
double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://areallylongdomainexample.com') )
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
before(:each) do
|
|
29
|
-
@options_with_forwarding = default_opts.merge(:report_uri => 'https://example.com/csp', :forward_endpoint => 'https://anotherexample.com')
|
|
30
|
-
end
|
|
31
14
|
|
|
32
15
|
describe "#name" do
|
|
33
|
-
context "when supplying options to override request" do
|
|
34
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => IE).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
35
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
36
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
37
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
38
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
39
|
-
end
|
|
40
|
-
|
|
41
16
|
context "when in report-only mode" do
|
|
42
|
-
specify { expect(ContentSecurityPolicy.new(default_opts
|
|
43
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
44
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
45
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
46
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
17
|
+
specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicy::HEADER_NAME + "-Report-Only") }
|
|
47
18
|
end
|
|
48
19
|
|
|
49
20
|
context "when in enforce mode" do
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(IE)).name).to eq(HEADER_NAME)}
|
|
53
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name).to eq(HEADER_NAME)}
|
|
54
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name).to eq(HEADER_NAME)}
|
|
55
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name).to eq(HEADER_NAME)}
|
|
56
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name).to eq(HEADER_NAME)}
|
|
21
|
+
specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicy::HEADER_NAME) }
|
|
57
22
|
end
|
|
58
23
|
end
|
|
59
24
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
it "imports JSON to build a policy" do
|
|
67
|
-
json1 = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"]})
|
|
68
|
-
json2 = %({"style-src":["'unsafe-inline'"],"img-src":["https:","data:"]})
|
|
69
|
-
json3 = %({"style-src":["https:","about:"]})
|
|
70
|
-
config = ContentSecurityPolicy.from_json(json1, json2, json3)
|
|
71
|
-
policy = ContentSecurityPolicy.new(config)
|
|
25
|
+
describe "#validate_config!" do
|
|
26
|
+
it "requires a :default_src value" do
|
|
27
|
+
expect do
|
|
28
|
+
CSP.validate_config!(script_src: %('self'))
|
|
29
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
30
|
+
end
|
|
72
31
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
32
|
+
it "requires :report_only to be a truthy value" do
|
|
33
|
+
expect do
|
|
34
|
+
CSP.validate_config!(default_opts.merge(report_only: "steve"))
|
|
35
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
36
|
+
end
|
|
76
37
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
38
|
+
it "requires :block_all_mixed_content to be a boolean value" do
|
|
39
|
+
expect do
|
|
40
|
+
CSP.validate_config!(default_opts.merge(block_all_mixed_content: "steve"))
|
|
41
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
81
42
|
end
|
|
82
|
-
end
|
|
83
43
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
44
|
+
it "requires all source lists to be an array of strings" do
|
|
45
|
+
expect do
|
|
46
|
+
CSP.validate_config!(default_src: "steve")
|
|
47
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
88
48
|
end
|
|
89
49
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
50
|
+
it "rejects unknown directives / config" do
|
|
51
|
+
expect do
|
|
52
|
+
CSP.validate_config!(default_src: %w('self'), default_src_totally_mispelled: "steve")
|
|
53
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
54
|
+
end
|
|
95
55
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
end
|
|
56
|
+
# this is mostly to ensure people don't use the antiquated shorthands common in other configs
|
|
57
|
+
it "performs light validation on source lists" do
|
|
58
|
+
expect do
|
|
59
|
+
CSP.validate_config!(default_src: %w(self none inline eval))
|
|
60
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
101
63
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
64
|
+
describe "#combine_policies" do
|
|
65
|
+
it "combines the default-src value with the override if the directive was unconfigured" do
|
|
66
|
+
combined_config = CSP.combine_policies(Configuration.default.csp, script_src: %w(anothercdn.com))
|
|
67
|
+
csp = ContentSecurityPolicy.new(combined_config)
|
|
68
|
+
expect(csp.name).to eq(CSP::HEADER_NAME)
|
|
69
|
+
expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
|
|
70
|
+
end
|
|
107
71
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
:
|
|
72
|
+
it "overrides the report_only flag" do
|
|
73
|
+
Configuration.default do |config|
|
|
74
|
+
config.csp = {
|
|
75
|
+
default_src: %w('self'),
|
|
76
|
+
report_only: false
|
|
112
77
|
}
|
|
113
|
-
|
|
114
|
-
csp = ContentSecurityPolicy.new(opts)
|
|
115
|
-
expect(csp.value).to match("report-uri http://lambda/result")
|
|
116
78
|
end
|
|
79
|
+
combined_config = CSP.combine_policies(Configuration.get.csp, report_only: true)
|
|
80
|
+
csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
|
|
81
|
+
expect(csp.name).to eq(CSP::REPORT_ONLY)
|
|
82
|
+
end
|
|
117
83
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
:
|
|
84
|
+
it "overrides the :block_all_mixed_content flag" do
|
|
85
|
+
Configuration.default do |config|
|
|
86
|
+
config.csp = {
|
|
87
|
+
default_src: %w(https:),
|
|
88
|
+
block_all_mixed_content: false
|
|
122
89
|
}
|
|
123
|
-
|
|
124
|
-
csp = ContentSecurityPolicy.new(opts)
|
|
125
|
-
expect(csp.value).to eq("default-src http://lambda/result; img-src http://lambda/result data:;")
|
|
126
|
-
expect(csp.name).to match("Content-Security-Policy")
|
|
127
90
|
end
|
|
91
|
+
combined_config = CSP.combine_policies(Configuration.get.csp, block_all_mixed_content: true)
|
|
92
|
+
csp = ContentSecurityPolicy.new(combined_config)
|
|
93
|
+
expect(csp.value).to eq("default-src https:; block-all-mixed-content")
|
|
94
|
+
end
|
|
128
95
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
allow(controller).to receive(:current_user).and_return(user)
|
|
134
|
-
opts = {
|
|
135
|
-
:default_src => "'self'",
|
|
136
|
-
:enforce => lambda { |c| c.current_user.beta_testing? }
|
|
137
|
-
}
|
|
138
|
-
csp = ContentSecurityPolicy.new(opts, :controller => controller)
|
|
139
|
-
expect(csp.name).to match("Content-Security-Policy")
|
|
96
|
+
it "raises an error if appending to a OPT_OUT policy" do
|
|
97
|
+
Configuration.default do |config|
|
|
98
|
+
config.csp = OPT_OUT
|
|
140
99
|
end
|
|
100
|
+
expect do
|
|
101
|
+
CSP.combine_policies(Configuration.get.csp, script_src: %w(anothercdn.com))
|
|
102
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
141
103
|
end
|
|
142
104
|
end
|
|
143
105
|
|
|
144
106
|
describe "#value" do
|
|
145
|
-
it "
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
expect(policy.name).to eq("Content-Security-Policy")
|
|
149
|
-
policy = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
150
|
-
expect(policy.name).to eq("Content-Security-Policy")
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
context "browser sniffing" do
|
|
154
|
-
let(:complex_opts) do
|
|
155
|
-
ALL_DIRECTIVES.inject({}) { |memo, directive| memo[directive] = "'self'"; memo }.merge(:block_all_mixed_content => '')
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
it "does not filter any directives for Chrome" do
|
|
159
|
-
policy = ContentSecurityPolicy.new(complex_opts, :request => request_for(CHROME))
|
|
160
|
-
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content ; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' data:; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self'; style-src 'self'; report-uri 'self';")
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
|
|
164
|
-
policy = ContentSecurityPolicy.new(complex_opts, :request => request_for(FIREFOX))
|
|
165
|
-
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' data:; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self'; style-src 'self'; report-uri 'self';")
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
it "filters base-uri, blocked-all-mixed-content, child-src, form-action, frame-ancestors, and plugin-types for safari" do
|
|
169
|
-
policy = ContentSecurityPolicy.new(complex_opts, :request => request_for(SAFARI))
|
|
170
|
-
expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self' data:; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self'; style-src 'self'; report-uri 'self';")
|
|
171
|
-
end
|
|
107
|
+
it "discards 'none' values if any other source expressions are present" do
|
|
108
|
+
csp = ContentSecurityPolicy.new(default_opts.merge(frame_src: %w('self' 'none')))
|
|
109
|
+
expect(csp.value).not_to include("'none'")
|
|
172
110
|
end
|
|
173
111
|
|
|
174
|
-
it "
|
|
175
|
-
csp = ContentSecurityPolicy.new(
|
|
176
|
-
expect
|
|
177
|
-
csp.value
|
|
178
|
-
}.to raise_error(RuntimeError)
|
|
112
|
+
it "discards source expressions besides unsafe-* expressions when * is present" do
|
|
113
|
+
csp = ContentSecurityPolicy.new(default_src: %w(* 'unsafe-inline' 'unsafe-eval' http: https: example.org))
|
|
114
|
+
expect(csp.value).to eq("default-src * 'unsafe-inline' 'unsafe-eval'")
|
|
179
115
|
end
|
|
180
116
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
it "appends the value if img-src is specified" do
|
|
188
|
-
csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self'"}, :request => request_for(CHROME))
|
|
189
|
-
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
it "doesn't add a duplicate data uri if img-src specifies it already" do
|
|
193
|
-
csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self' data:"}, :request => request_for(CHROME))
|
|
194
|
-
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
it "allows the user to disable img-src data: uris auto-whitelisting" do
|
|
198
|
-
csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self'", :disable_img_src_data_uri => true}, :request => request_for(CHROME))
|
|
199
|
-
expect(csp.value).to eq("default-src 'self'; img-src 'self';")
|
|
200
|
-
end
|
|
117
|
+
it "minifies source expressions based on overlapping wildcards" do
|
|
118
|
+
config = {
|
|
119
|
+
default_src: %w(a.example.org b.example.org *.example.org https://*.example.org)
|
|
120
|
+
}
|
|
121
|
+
csp = ContentSecurityPolicy.new(config)
|
|
122
|
+
expect(csp.value).to eq("default-src *.example.org")
|
|
201
123
|
end
|
|
202
124
|
|
|
203
|
-
it "
|
|
204
|
-
csp = ContentSecurityPolicy.new(
|
|
205
|
-
expect(csp.value).to
|
|
125
|
+
it "removes http/s schemes from hosts" do
|
|
126
|
+
csp = ContentSecurityPolicy.new(default_src: %w(https://example.org))
|
|
127
|
+
expect(csp.value).to eq("default-src example.org")
|
|
206
128
|
end
|
|
207
129
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
expect(csp.value).to eq("default-src https:; img-src https: data:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;")
|
|
212
|
-
end
|
|
130
|
+
it "does not remove schemes from report-uri values" do
|
|
131
|
+
csp = ContentSecurityPolicy.new(default_src: %w(https:), report_uri: %w(https://example.org))
|
|
132
|
+
expect(csp.value).to eq("default-src https:; report-uri https://example.org")
|
|
213
133
|
end
|
|
214
134
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
expect(csp.value).to eq("default-src https:; img-src https: data:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;")
|
|
219
|
-
end
|
|
135
|
+
it "removes nil from source lists" do
|
|
136
|
+
csp = ContentSecurityPolicy.new(default_src: ["https://example.org", nil])
|
|
137
|
+
expect(csp.value).to eq("default-src example.org")
|
|
220
138
|
end
|
|
221
139
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
it "adds a nonce and unsafe-inline to the script-src value when using firefox" do
|
|
229
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(FIREFOX), :controller => controller)
|
|
230
|
-
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
it "adds a nonce and unsafe-inline to the script-src value when using opera" do
|
|
234
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(OPERA), :controller => controller)
|
|
235
|
-
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
it "does not add a nonce and unsafe-inline to the script-src value when using Safari" do
|
|
239
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(SAFARI), :controller => controller)
|
|
240
|
-
expect(header.value).to include("script-src 'self' 'unsafe-inline'")
|
|
241
|
-
expect(header.value).not_to include("nonce")
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
it "does not add a nonce and unsafe-inline to the script-src value when using IE" do
|
|
245
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(IE), :controller => controller)
|
|
246
|
-
expect(header.value).to include("script-src 'self' 'unsafe-inline'")
|
|
247
|
-
expect(header.value).not_to include("nonce")
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
it "adds a nonce and unsafe-inline to the style-src value" do
|
|
251
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
|
|
252
|
-
expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
it "adds an identical nonce to the style and script-src directives" do
|
|
256
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "'self' nonce", :script_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
|
|
257
|
-
nonce = header.nonce
|
|
258
|
-
value = header.value
|
|
259
|
-
expect(value).to include("style-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
|
|
260
|
-
expect(value).to include("script-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
it "does not add 'unsafe-inline' twice" do
|
|
264
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce 'unsafe-inline'"), :request => request_for(CHROME), :controller => controller)
|
|
265
|
-
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline';")
|
|
266
|
-
end
|
|
140
|
+
it "deduplicates any source expressions" do
|
|
141
|
+
csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
|
|
142
|
+
expect(csp.value).to eq("default-src example.org")
|
|
267
143
|
end
|
|
268
144
|
|
|
269
|
-
context "
|
|
270
|
-
let(:
|
|
271
|
-
|
|
272
|
-
:
|
|
273
|
-
|
|
274
|
-
:img_src => "http:"
|
|
275
|
-
}
|
|
276
|
-
})
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
it "adds directive values for headers on http" do
|
|
280
|
-
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
281
|
-
expect(csp.value).to eq("default-src https:; frame-src http:; img-src https: data: http:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;")
|
|
145
|
+
context "browser sniffing" do
|
|
146
|
+
let (:complex_opts) do
|
|
147
|
+
ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) { |directive, hash| hash[directive] = %w('self') }
|
|
148
|
+
.merge(block_all_mixed_content: true, reflected_xss: "block")
|
|
149
|
+
.merge(script_src: %w('self'), script_nonce: 123456)
|
|
282
150
|
end
|
|
283
151
|
|
|
284
|
-
it "does not
|
|
285
|
-
|
|
286
|
-
expect(
|
|
152
|
+
it "does not filter any directives for Chrome" do
|
|
153
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
|
|
154
|
+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; report-uri 'self'")
|
|
287
155
|
end
|
|
288
156
|
|
|
289
|
-
it "does not
|
|
290
|
-
|
|
291
|
-
expect(
|
|
157
|
+
it "does not filter any directives for Opera" do
|
|
158
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
|
|
159
|
+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; report-uri 'self'")
|
|
292
160
|
end
|
|
293
|
-
end
|
|
294
161
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
double.tap do |env|
|
|
299
|
-
allow(env).to receive(:[]).with('HTTP_USER_AGENT').and_return(ua)
|
|
300
|
-
end
|
|
301
|
-
end
|
|
302
|
-
let(:request) do
|
|
303
|
-
double(
|
|
304
|
-
:ssl? => true,
|
|
305
|
-
:url => 'https://example.com',
|
|
306
|
-
:env => env
|
|
307
|
-
)
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
describe ".add_to_env" do
|
|
311
|
-
let(:controller) { double }
|
|
312
|
-
let(:config) { {:default_src => "'self'"} }
|
|
313
|
-
let(:options) { {:controller => controller} }
|
|
314
|
-
|
|
315
|
-
it "adds metadata to env" do
|
|
316
|
-
metadata = {
|
|
317
|
-
:config => config,
|
|
318
|
-
:options => options
|
|
319
|
-
}
|
|
320
|
-
expect(ContentSecurityPolicy).to receive(:options_from_request).and_return(options)
|
|
321
|
-
expect(env).to receive(:[]=).with(ContentSecurityPolicy::ENV_KEY, metadata)
|
|
322
|
-
ContentSecurityPolicy.add_to_env(request, controller, config)
|
|
323
|
-
end
|
|
162
|
+
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
|
|
163
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
|
|
164
|
+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; report-uri 'self'")
|
|
324
165
|
end
|
|
325
166
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
expect(options).to eql({
|
|
330
|
-
:ua => ua,
|
|
331
|
-
:ssl => true,
|
|
332
|
-
:request_uri => 'https://example.com'
|
|
333
|
-
})
|
|
334
|
-
end
|
|
167
|
+
it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
|
|
168
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
|
|
169
|
+
expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; report-uri 'self'")
|
|
335
170
|
end
|
|
336
171
|
end
|
|
337
172
|
end
|
|
@@ -2,35 +2,35 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module SecureHeaders
|
|
4
4
|
describe PublicKeyPins do
|
|
5
|
-
specify{ expect(PublicKeyPins.new(:
|
|
6
|
-
specify{ expect(PublicKeyPins.new(:
|
|
5
|
+
specify { expect(PublicKeyPins.new(max_age: 1234, report_only: true).name).to eq("Public-Key-Pins-Report-Only") }
|
|
6
|
+
specify { expect(PublicKeyPins.new(max_age: 1234).name).to eq("Public-Key-Pins") }
|
|
7
7
|
|
|
8
|
-
specify { expect(PublicKeyPins.new(
|
|
9
|
-
specify { expect(PublicKeyPins.new(:
|
|
10
|
-
specify
|
|
11
|
-
config = {:
|
|
8
|
+
specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
|
|
9
|
+
specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
|
|
10
|
+
specify do
|
|
11
|
+
config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin1' }, { sha256: 'base64encodedpin2' }] }
|
|
12
12
|
header_value = "max-age=1234; pin-sha256=\"base64encodedpin1\"; pin-sha256=\"base64encodedpin2\""
|
|
13
13
|
expect(PublicKeyPins.new(config).value).to eq(header_value)
|
|
14
|
-
|
|
14
|
+
end
|
|
15
15
|
|
|
16
16
|
context "with an invalid configuration" do
|
|
17
17
|
it "raises an exception when max-age is not provided" do
|
|
18
|
-
expect
|
|
19
|
-
PublicKeyPins.
|
|
20
|
-
|
|
18
|
+
expect do
|
|
19
|
+
PublicKeyPins.validate_config!(foo: 'bar')
|
|
20
|
+
end.to raise_error(PublicKeyPinsConfigError)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it "raises an exception with an invalid max-age" do
|
|
24
|
-
expect
|
|
25
|
-
PublicKeyPins.
|
|
26
|
-
|
|
24
|
+
expect do
|
|
25
|
+
PublicKeyPins.validate_config!(max_age: 'abc123')
|
|
26
|
+
end.to raise_error(PublicKeyPinsConfigError)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
it 'raises an exception with less than 2 pins' do
|
|
30
|
-
expect
|
|
31
|
-
config = {:
|
|
32
|
-
PublicKeyPins.
|
|
33
|
-
|
|
30
|
+
expect do
|
|
31
|
+
config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin' }] }
|
|
32
|
+
PublicKeyPins.validate_config!(config)
|
|
33
|
+
end.to raise_error(PublicKeyPinsConfigError)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -2,60 +2,28 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module SecureHeaders
|
|
4
4
|
describe StrictTransportSecurity do
|
|
5
|
-
specify{ expect(StrictTransportSecurity.new.name).to eq("Strict-Transport-Security") }
|
|
6
|
-
|
|
7
5
|
describe "#value" do
|
|
8
|
-
specify { expect(StrictTransportSecurity.
|
|
9
|
-
specify { expect(StrictTransportSecurity.
|
|
10
|
-
specify { expect(StrictTransportSecurity.new(:max_age => '1234').value).to eq("max-age=1234")}
|
|
11
|
-
specify { expect(StrictTransportSecurity.new(:max_age => 1234).value).to eq("max-age=1234")}
|
|
12
|
-
specify { expect(StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true).value).to eq("max-age=#{HSTS_MAX_AGE}; includeSubdomains")}
|
|
13
|
-
specify { expect(StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true).value).to eq("max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")}
|
|
6
|
+
specify { expect(StrictTransportSecurity.make_header).to eq([StrictTransportSecurity::HEADER_NAME, StrictTransportSecurity::DEFAULT_VALUE]) }
|
|
7
|
+
specify { expect(StrictTransportSecurity.make_header("max-age=1234")).to eq([StrictTransportSecurity::HEADER_NAME, "max-age=1234"]) }
|
|
14
8
|
|
|
15
9
|
context "with an invalid configuration" do
|
|
16
|
-
context "with a hash argument" do
|
|
17
|
-
it "should allow string values for max-age" do
|
|
18
|
-
expect {
|
|
19
|
-
StrictTransportSecurity.new(:max_age => '1234')
|
|
20
|
-
}.not_to raise_error
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "should allow integer values for max-age" do
|
|
24
|
-
expect {
|
|
25
|
-
StrictTransportSecurity.new(:max_age => 1234)
|
|
26
|
-
}.not_to raise_error
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "raises an exception with an invalid max-age" do
|
|
30
|
-
expect {
|
|
31
|
-
StrictTransportSecurity.new(:max_age => 'abc123')
|
|
32
|
-
}.to raise_error(STSBuildError)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "raises an exception if max-age is not supplied" do
|
|
36
|
-
expect {
|
|
37
|
-
StrictTransportSecurity.new(:includeSubdomains => true)
|
|
38
|
-
}.to raise_error(STSBuildError)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
10
|
context "with a string argument" do
|
|
43
11
|
it "raises an exception with an invalid max-age" do
|
|
44
|
-
expect
|
|
45
|
-
StrictTransportSecurity.
|
|
46
|
-
|
|
12
|
+
expect do
|
|
13
|
+
StrictTransportSecurity.validate_config!('max-age=abc123')
|
|
14
|
+
end.to raise_error(STSConfigError)
|
|
47
15
|
end
|
|
48
16
|
|
|
49
17
|
it "raises an exception if max-age is not supplied" do
|
|
50
|
-
expect
|
|
51
|
-
StrictTransportSecurity.
|
|
52
|
-
|
|
18
|
+
expect do
|
|
19
|
+
StrictTransportSecurity.validate_config!('includeSubdomains')
|
|
20
|
+
end.to raise_error(STSConfigError)
|
|
53
21
|
end
|
|
54
22
|
|
|
55
23
|
it "raises an exception with an invalid format" do
|
|
56
|
-
expect
|
|
57
|
-
StrictTransportSecurity.
|
|
58
|
-
|
|
24
|
+
expect do
|
|
25
|
+
StrictTransportSecurity.validate_config!('max-age=123includeSubdomains')
|
|
26
|
+
end.to raise_error(STSConfigError)
|
|
59
27
|
end
|
|
60
28
|
end
|
|
61
29
|
end
|
|
@@ -2,34 +2,27 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module SecureHeaders
|
|
4
4
|
describe XContentTypeOptions do
|
|
5
|
-
specify{ expect(XContentTypeOptions.new.name).to eq("X-Content-Type-Options") }
|
|
6
|
-
|
|
7
5
|
describe "#value" do
|
|
8
|
-
specify { expect(XContentTypeOptions.
|
|
9
|
-
specify { expect(XContentTypeOptions.
|
|
10
|
-
specify { expect(XContentTypeOptions.new(:value => 'nosniff').value).to eq("nosniff")}
|
|
6
|
+
specify { expect(XContentTypeOptions.make_header).to eq([XContentTypeOptions::HEADER_NAME, XContentTypeOptions::DEFAULT_VALUE]) }
|
|
7
|
+
specify { expect(XContentTypeOptions.make_header("nosniff")).to eq([XContentTypeOptions::HEADER_NAME, "nosniff"]) }
|
|
11
8
|
|
|
12
9
|
context "invalid configuration values" do
|
|
13
10
|
it "accepts nosniff" do
|
|
14
|
-
expect
|
|
15
|
-
XContentTypeOptions.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
expect {
|
|
19
|
-
XContentTypeOptions.new(:value => "nosniff")
|
|
20
|
-
}.not_to raise_error
|
|
11
|
+
expect do
|
|
12
|
+
XContentTypeOptions.validate_config!("nosniff")
|
|
13
|
+
end.not_to raise_error
|
|
21
14
|
end
|
|
22
15
|
|
|
23
16
|
it "accepts nil" do
|
|
24
|
-
expect
|
|
25
|
-
XContentTypeOptions.
|
|
26
|
-
|
|
17
|
+
expect do
|
|
18
|
+
XContentTypeOptions.validate_config!(nil)
|
|
19
|
+
end.not_to raise_error
|
|
27
20
|
end
|
|
28
21
|
|
|
29
22
|
it "doesn't accept anything besides no-sniff" do
|
|
30
|
-
expect
|
|
31
|
-
XContentTypeOptions.
|
|
32
|
-
|
|
23
|
+
expect do
|
|
24
|
+
XContentTypeOptions.validate_config!("donkey")
|
|
25
|
+
end.to raise_error(XContentTypeOptionsConfigError)
|
|
33
26
|
end
|
|
34
27
|
end
|
|
35
28
|
end
|