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
|
@@ -1,378 +1,240 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
subject.set_x_xss_protection_header
|
|
42
|
-
subject.set_x_download_options_header
|
|
43
|
-
subject.set_x_permitted_cross_domain_policies_header
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
describe "#set_header" do
|
|
47
|
-
it "accepts name/value pairs" do
|
|
48
|
-
should_assign_header("X-Hipster-Ipsum", "kombucha")
|
|
49
|
-
subject.send(:set_header, "X-Hipster-Ipsum", "kombucha")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
it "accepts header objects" do
|
|
53
|
-
should_assign_header("Strict-Transport-Security", SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
54
|
-
subject.send(:set_header, SecureHeaders::StrictTransportSecurity.new)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
describe "#set_security_headers" do
|
|
59
|
-
USER_AGENTS.each do |name, useragent|
|
|
60
|
-
it "sets all default headers for #{name} (smoke test)" do
|
|
61
|
-
stub_user_agent(useragent)
|
|
62
|
-
number_of_headers = 7
|
|
63
|
-
expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
|
|
64
|
-
subject.set_csp_header
|
|
65
|
-
subject.set_x_frame_options_header
|
|
66
|
-
subject.set_hsts_header
|
|
67
|
-
subject.set_hpkp_header
|
|
68
|
-
subject.set_x_xss_protection_header
|
|
69
|
-
subject.set_x_content_type_options_header
|
|
70
|
-
subject.set_x_download_options_header
|
|
71
|
-
subject.set_x_permitted_cross_domain_policies_header
|
|
3
|
+
module SecureHeaders
|
|
4
|
+
describe SecureHeaders do
|
|
5
|
+
example_hpkp_config = {
|
|
6
|
+
max_age: 1_000_000,
|
|
7
|
+
include_subdomains: true,
|
|
8
|
+
report_uri: '//example.com/uri-directive',
|
|
9
|
+
pins: [
|
|
10
|
+
{ sha256: 'abc' },
|
|
11
|
+
{ sha256: '123' }
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
example_hpkp_config_value = %(max-age=1000000; pin-sha256="abc"; pin-sha256="123"; report-uri="//example.com/uri-directive"; includeSubDomains)
|
|
16
|
+
|
|
17
|
+
before(:each) do
|
|
18
|
+
reset_config
|
|
19
|
+
@request = Rack::Request.new("HTTP_X_FORWARDED_SSL" => "on")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "raises a NotYetConfiguredError if default has not been set" do
|
|
23
|
+
expect do
|
|
24
|
+
SecureHeaders.header_hash_for(@request)
|
|
25
|
+
end.to raise_error(Configuration::NotYetConfiguredError)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "raises a NotYetConfiguredError if trying to opt-out of unconfigured headers" do
|
|
29
|
+
expect do
|
|
30
|
+
SecureHeaders.opt_out_of_header(@request, CSP::CONFIG_KEY)
|
|
31
|
+
end.to raise_error(Configuration::NotYetConfiguredError)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "#header_hash_for" do
|
|
35
|
+
it "allows you to opt out of individual headers" do
|
|
36
|
+
Configuration.default
|
|
37
|
+
SecureHeaders.opt_out_of_header(@request, CSP::CONFIG_KEY)
|
|
38
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
39
|
+
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
|
|
40
|
+
expect(hash['Content-Security-Policy']).to be_nil
|
|
72
41
|
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it "does not set the X-Content-Type-Options header if disabled" do
|
|
76
|
-
stub_user_agent(USER_AGENTS[:ie])
|
|
77
|
-
should_not_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME)
|
|
78
|
-
subject.set_x_content_type_options_header(false)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it "does not set the X-XSS-Protection header if disabled" do
|
|
82
|
-
should_not_assign_header(X_XSS_PROTECTION_HEADER_NAME)
|
|
83
|
-
subject.set_x_xss_protection_header(false)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it "does not set the X-Download-Options header if disabled" do
|
|
87
|
-
should_not_assign_header(XDO_HEADER_NAME)
|
|
88
|
-
subject.set_x_download_options_header(false)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it "does not set the X-Frame-Options header if disabled" do
|
|
92
|
-
should_not_assign_header(XFO_HEADER_NAME)
|
|
93
|
-
subject.set_x_frame_options_header(false)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
|
|
97
|
-
should_not_assign_header(XPCDP_HEADER_NAME)
|
|
98
|
-
subject.set_x_permitted_cross_domain_policies_header(false)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it "does not set the HSTS header if disabled" do
|
|
102
|
-
should_not_assign_header(HSTS_HEADER_NAME)
|
|
103
|
-
subject.set_hsts_header(false)
|
|
104
|
-
end
|
|
105
42
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
it "does not set the HPKP header if disabled" do
|
|
113
|
-
should_not_assign_header(HPKP_HEADER_NAME)
|
|
114
|
-
subject.set_hpkp_header
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it "does not set the HPKP header if request is over HTTP" do
|
|
118
|
-
allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
|
|
119
|
-
should_not_assign_header(HPKP_HEADER_NAME)
|
|
120
|
-
subject.set_hpkp_header(:max_age => 1234)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
it "does not set the CSP header if disabled" do
|
|
124
|
-
stub_user_agent(USER_AGENTS[:chrome])
|
|
125
|
-
should_not_assign_header(HEADER_NAME)
|
|
126
|
-
subject.set_csp_header(false)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it "saves the options to the env when using script hashes" do
|
|
130
|
-
opts = {
|
|
131
|
-
:default_src => "'self'",
|
|
132
|
-
:script_hash_middleware => true
|
|
133
|
-
}
|
|
134
|
-
stub_user_agent(USER_AGENTS[:chrome])
|
|
135
|
-
|
|
136
|
-
expect(SecureHeaders::ContentSecurityPolicy).to receive(:add_to_env)
|
|
137
|
-
subject.set_csp_header(opts)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
context "when disabled by configuration settings" do
|
|
141
|
-
it "does not set any headers when disabled" do
|
|
142
|
-
::SecureHeaders::Configuration.default do |config|
|
|
143
|
-
config.hsts = false
|
|
144
|
-
config.hpkp = false
|
|
145
|
-
config.x_frame_options = false
|
|
146
|
-
config.x_content_type_options = false
|
|
147
|
-
config.x_xss_protection = false
|
|
148
|
-
config.csp = false
|
|
149
|
-
config.x_download_options = false
|
|
150
|
-
config.x_permitted_cross_domain_policies = false
|
|
43
|
+
it "allows you to opt out entirely" do
|
|
44
|
+
Configuration.default
|
|
45
|
+
SecureHeaders.opt_out_of_all_protection(@request)
|
|
46
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
47
|
+
ALL_HEADER_CLASSES.each do |klass|
|
|
48
|
+
expect(hash[klass::CONFIG_KEY]).to be_nil
|
|
151
49
|
end
|
|
152
|
-
expect(subject).not_to receive(:set_header)
|
|
153
|
-
set_security_headers(subject)
|
|
154
|
-
reset_config
|
|
155
50
|
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
51
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
expect(hash[X_CONTENT_TYPE_OPTIONS_HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
|
|
166
|
-
expect(hash[XPCDP_HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
|
|
167
|
-
end
|
|
52
|
+
it "allows you to override X-Frame-Options settings" do
|
|
53
|
+
Configuration.default
|
|
54
|
+
SecureHeaders.override_x_frame_options(@request, XFrameOptions::DENY)
|
|
55
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
56
|
+
expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::DENY)
|
|
57
|
+
end
|
|
168
58
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
59
|
+
it "allows you to override opting out" do
|
|
60
|
+
Configuration.default do |config|
|
|
61
|
+
config.x_frame_options = OPT_OUT
|
|
62
|
+
config.csp = OPT_OUT
|
|
63
|
+
end
|
|
174
64
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
|
|
178
|
-
expect(hash['Content-Security-Policy']).to be_nil
|
|
179
|
-
end
|
|
65
|
+
SecureHeaders.override_x_frame_options(@request, XFrameOptions::SAMEORIGIN)
|
|
66
|
+
SecureHeaders.override_content_security_policy_directives(@request, default_src: %w(https:), script_src: %w('self'))
|
|
180
67
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
config.csp = false
|
|
68
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
69
|
+
expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
|
|
70
|
+
expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::SAMEORIGIN)
|
|
185
71
|
end
|
|
186
72
|
|
|
187
|
-
hash
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
it "produces a hash with a mix of config values, override values, and default values" do
|
|
193
|
-
::SecureHeaders::Configuration.default do |config|
|
|
194
|
-
config.hsts = { :max_age => '123456'}
|
|
195
|
-
config.hpkp = {
|
|
196
|
-
:enforce => true,
|
|
197
|
-
:max_age => 1000000,
|
|
198
|
-
:include_subdomains => true,
|
|
199
|
-
:report_uri => '//example.com/uri-directive',
|
|
200
|
-
:pins => [
|
|
201
|
-
{:sha256 => 'abc'},
|
|
202
|
-
{:sha256 => '123'}
|
|
203
|
-
]
|
|
204
|
-
}
|
|
73
|
+
it "produces a hash of headers with default config" do
|
|
74
|
+
Configuration.default
|
|
75
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
76
|
+
expect_default_values(hash)
|
|
205
77
|
end
|
|
206
78
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
79
|
+
it "does not set the HSTS header if request is over HTTP" do
|
|
80
|
+
plaintext_request = Rack::Request.new({})
|
|
81
|
+
Configuration.default do |config|
|
|
82
|
+
config.hsts = "max-age=123456"
|
|
83
|
+
end
|
|
84
|
+
expect(SecureHeaders.header_hash_for(plaintext_request)[StrictTransportSecurity::HEADER_NAME]).to be_nil
|
|
211
85
|
end
|
|
212
86
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
it "produces a hash of headers with default config" do
|
|
220
|
-
hash = SecureHeaders::header_hash
|
|
221
|
-
expect(hash['Content-Security-Policy-Report-Only']).to eq(SecureHeaders::ContentSecurityPolicy::Constants::DEFAULT_CSP_HEADER)
|
|
222
|
-
expect_default_values(hash)
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
describe "#set_x_frame_options_header" do
|
|
227
|
-
it "sets the X-Frame-Options header" do
|
|
228
|
-
should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
|
|
229
|
-
subject.set_x_frame_options_header
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
it "allows a custom X-Frame-Options header" do
|
|
233
|
-
should_assign_header(XFO_HEADER_NAME, "DENY")
|
|
234
|
-
subject.set_x_frame_options_header(:value => 'DENY')
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
describe "#set_x_download_options_header" do
|
|
239
|
-
it "sets the X-Download-Options header" do
|
|
240
|
-
should_assign_header(XDO_HEADER_NAME, SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
|
|
241
|
-
subject.set_x_download_options_header
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
it "allows a custom X-Download-Options header" do
|
|
245
|
-
should_assign_header(XDO_HEADER_NAME, "noopen")
|
|
246
|
-
subject.set_x_download_options_header(:value => 'noopen')
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
describe "#set_strict_transport_security" do
|
|
251
|
-
it "sets the Strict-Transport-Security header" do
|
|
252
|
-
should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
253
|
-
subject.set_hsts_header
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
it "allows you to specific a custom max-age value" do
|
|
257
|
-
should_assign_header(HSTS_HEADER_NAME, 'max-age=1234')
|
|
258
|
-
subject.set_hsts_header(:max_age => 1234)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
it "allows you to specify includeSubdomains" do
|
|
262
|
-
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
|
|
263
|
-
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
it "allows you to specify preload" do
|
|
267
|
-
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")
|
|
268
|
-
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true)
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
describe "#set_public_key_pins" do
|
|
273
|
-
it "sets the Public-Key-Pins header" do
|
|
274
|
-
should_assign_header(HPKP_HEADER_NAME + "-Report-Only", "max-age=1234")
|
|
275
|
-
subject.set_hpkp_header(:max_age => 1234)
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
it "allows you to enforce public key pinning" do
|
|
279
|
-
should_assign_header(HPKP_HEADER_NAME, "max-age=1234")
|
|
280
|
-
subject.set_hpkp_header(:max_age => 1234, :enforce => true)
|
|
281
|
-
end
|
|
87
|
+
it "does not set the HPKP header if request is over HTTP" do
|
|
88
|
+
plaintext_request = Rack::Request.new({})
|
|
89
|
+
Configuration.default do |config|
|
|
90
|
+
config.hpkp = example_hpkp_config
|
|
91
|
+
end
|
|
282
92
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
subject.set_hpkp_header(:max_age => 1234)
|
|
286
|
-
end
|
|
93
|
+
expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
|
|
94
|
+
end
|
|
287
95
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
96
|
+
context "content security policy" do
|
|
97
|
+
it "appends a value to csp directive" do
|
|
98
|
+
Configuration.default do |config|
|
|
99
|
+
config.csp = {
|
|
100
|
+
default_src: %w('self'),
|
|
101
|
+
script_src: %w(mycdn.com 'unsafe-inline')
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
SecureHeaders.append_content_security_policy_directives(@request, script_src: %w(anothercdn.com))
|
|
106
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
107
|
+
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
|
|
108
|
+
end
|
|
292
109
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
110
|
+
it "overrides individual directives" do
|
|
111
|
+
Configuration.default do |config|
|
|
112
|
+
config.csp = {
|
|
113
|
+
default_src: %w('self')
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
SecureHeaders.override_content_security_policy_directives(@request, default_src: %w('none'))
|
|
117
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
118
|
+
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'none'")
|
|
119
|
+
end
|
|
297
120
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
121
|
+
it "overrides non-existant directives" do
|
|
122
|
+
Configuration.default
|
|
123
|
+
SecureHeaders.override_content_security_policy_directives(@request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
|
|
124
|
+
hash = SecureHeaders.header_hash_for(@request)
|
|
125
|
+
expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; img-src data:")
|
|
126
|
+
end
|
|
303
127
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
128
|
+
it "does not append a nonce when the browser does not support it" do
|
|
129
|
+
Configuration.default do |config|
|
|
130
|
+
config.csp = {
|
|
131
|
+
default_src: %w('self'),
|
|
132
|
+
script_src: %w(mycdn.com 'unsafe-inline'),
|
|
133
|
+
style_src: %w('self')
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
request = Rack::Request.new(@request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
|
|
138
|
+
nonce = SecureHeaders.content_security_policy_script_nonce(request)
|
|
139
|
+
hash = SecureHeaders.header_hash_for(request)
|
|
140
|
+
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
|
|
141
|
+
end
|
|
309
142
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
143
|
+
it "appends a nonce to the script-src when used" do
|
|
144
|
+
Configuration.default do |config|
|
|
145
|
+
config.csp = {
|
|
146
|
+
default_src: %w('self'),
|
|
147
|
+
script_src: %w(mycdn.com),
|
|
148
|
+
style_src: %w('self')
|
|
149
|
+
}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
request = Rack::Request.new(@request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
|
|
153
|
+
nonce = SecureHeaders.content_security_policy_script_nonce(request)
|
|
154
|
+
|
|
155
|
+
# simulate the nonce being used multiple times in a request:
|
|
156
|
+
SecureHeaders.content_security_policy_script_nonce(request)
|
|
157
|
+
SecureHeaders.content_security_policy_script_nonce(request)
|
|
158
|
+
SecureHeaders.content_security_policy_script_nonce(request)
|
|
159
|
+
|
|
160
|
+
hash = SecureHeaders.header_hash_for(request)
|
|
161
|
+
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
313
164
|
end
|
|
314
165
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
166
|
+
context "validation" do
|
|
167
|
+
it "validates your hsts config upon configuration" do
|
|
168
|
+
expect do
|
|
169
|
+
Configuration.default do |config|
|
|
170
|
+
config.hsts = 'lol'
|
|
171
|
+
end
|
|
172
|
+
end.to raise_error(STSConfigError)
|
|
173
|
+
end
|
|
320
174
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
end
|
|
175
|
+
it "validates your csp config upon configuration" do
|
|
176
|
+
expect do
|
|
177
|
+
Configuration.default do |config|
|
|
178
|
+
config.csp = { CSP::DEFAULT_SRC => '123456' }
|
|
179
|
+
end
|
|
180
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
181
|
+
end
|
|
327
182
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
183
|
+
it "raises errors for unknown directives" do
|
|
184
|
+
expect do
|
|
185
|
+
Configuration.default do |config|
|
|
186
|
+
config.csp = { made_up_directive: '123456' }
|
|
187
|
+
end
|
|
188
|
+
end.to raise_error(ContentSecurityPolicyConfigError)
|
|
189
|
+
end
|
|
332
190
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
191
|
+
it "validates your xfo config upon configuration" do
|
|
192
|
+
expect do
|
|
193
|
+
Configuration.default do |config|
|
|
194
|
+
config.x_frame_options = "NOPE"
|
|
195
|
+
end
|
|
196
|
+
end.to raise_error(XFOConfigError)
|
|
337
197
|
end
|
|
338
|
-
end
|
|
339
|
-
end
|
|
340
198
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
199
|
+
it "validates your xcto config upon configuration" do
|
|
200
|
+
expect do
|
|
201
|
+
Configuration.default do |config|
|
|
202
|
+
config.x_content_type_options = "lol"
|
|
203
|
+
end
|
|
204
|
+
end.to raise_error(XContentTypeOptionsConfigError)
|
|
347
205
|
end
|
|
348
|
-
end
|
|
349
206
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
207
|
+
it "validates your x_xss config upon configuration" do
|
|
208
|
+
expect do
|
|
209
|
+
Configuration.default do |config|
|
|
210
|
+
config.x_xss_protection = "lol"
|
|
211
|
+
end
|
|
212
|
+
end.to raise_error(XXssProtectionConfigError)
|
|
355
213
|
end
|
|
356
|
-
end
|
|
357
214
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
215
|
+
it "validates your xdo config upon configuration" do
|
|
216
|
+
expect do
|
|
217
|
+
Configuration.default do |config|
|
|
218
|
+
config.x_download_options = "lol"
|
|
219
|
+
end
|
|
220
|
+
end.to raise_error(XDOConfigError)
|
|
363
221
|
end
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
222
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
223
|
+
it "validates your x_permitted_cross_domain_policies config upon configuration" do
|
|
224
|
+
expect do
|
|
225
|
+
Configuration.default do |config|
|
|
226
|
+
config.x_permitted_cross_domain_policies = "lol"
|
|
227
|
+
end
|
|
228
|
+
end.to raise_error(XPCDPConfigError)
|
|
229
|
+
end
|
|
372
230
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
231
|
+
it "validates your hpkp config upon configuration" do
|
|
232
|
+
expect do
|
|
233
|
+
Configuration.default do |config|
|
|
234
|
+
config.hpkp = "lol"
|
|
235
|
+
end
|
|
236
|
+
end.to raise_error(PublicKeyPinsConfigError)
|
|
237
|
+
end
|
|
376
238
|
end
|
|
377
239
|
end
|
|
378
240
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'rspec'
|
|
3
|
+
require 'rack'
|
|
4
|
+
require 'pry-nav'
|
|
3
5
|
|
|
4
6
|
require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
require 'coveralls'
|
|
8
|
-
Coveralls.wear!
|
|
9
|
-
rescue LoadError
|
|
10
|
-
# damn you 1.8.7
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
include ::SecureHeaders::PublicKeyPins::Constants
|
|
14
|
-
include ::SecureHeaders::StrictTransportSecurity::Constants
|
|
15
|
-
include ::SecureHeaders::ContentSecurityPolicy::Constants
|
|
16
|
-
include ::SecureHeaders::XFrameOptions::Constants
|
|
17
|
-
include ::SecureHeaders::XXssProtection::Constants
|
|
18
|
-
include ::SecureHeaders::XContentTypeOptions::Constants
|
|
19
|
-
include ::SecureHeaders::XDownloadOptions::Constants
|
|
20
|
-
include ::SecureHeaders::XPermittedCrossDomainPolicies::Constants
|
|
8
|
+
ENV["RAILS_ENV"] = "test"
|
|
21
9
|
|
|
22
10
|
USER_AGENTS = {
|
|
23
|
-
:
|
|
24
|
-
:
|
|
25
|
-
:
|
|
26
|
-
:
|
|
27
|
-
:
|
|
28
|
-
:
|
|
29
|
-
:
|
|
30
|
-
:
|
|
31
|
-
:
|
|
11
|
+
firefox: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
|
|
12
|
+
chrome: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
|
|
13
|
+
ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
|
|
14
|
+
opera: 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
|
|
15
|
+
ios5: "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
|
16
|
+
ios6: "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
|
|
17
|
+
safari5: "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
|
|
18
|
+
safari5_1: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
|
|
19
|
+
safari6: "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
|
|
32
20
|
}
|
|
33
21
|
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
def expect_default_values(hash)
|
|
23
|
+
expect(hash[SecureHeaders::CSP::HEADER_NAME]).to eq(SecureHeaders::CSP::DEFAULT_VALUE)
|
|
24
|
+
expect(hash[SecureHeaders::XFrameOptions::HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::DEFAULT_VALUE)
|
|
25
|
+
expect(hash[SecureHeaders::XDownloadOptions::HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::DEFAULT_VALUE)
|
|
26
|
+
expect(hash[SecureHeaders::StrictTransportSecurity::HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::DEFAULT_VALUE)
|
|
27
|
+
expect(hash[SecureHeaders::XXssProtection::HEADER_NAME]).to eq(SecureHeaders::XXssProtection::DEFAULT_VALUE)
|
|
28
|
+
expect(hash[SecureHeaders::XContentTypeOptions::HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::DEFAULT_VALUE)
|
|
29
|
+
expect(hash[SecureHeaders::XPermittedCrossDomainPolicies::HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::DEFAULT_VALUE)
|
|
36
30
|
end
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
module SecureHeaders
|
|
33
|
+
class Configuration
|
|
34
|
+
class << self
|
|
35
|
+
def clear_configurations
|
|
36
|
+
@configurations = nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def
|
|
43
|
-
|
|
42
|
+
def reset_config
|
|
43
|
+
SecureHeaders::Configuration.clear_configurations
|
|
44
44
|
end
|