secure_headers 2.3.0 → 3.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/.ruby-version +1 -1
- data/.travis.yml +3 -0
- data/CHANGELOG.md +205 -0
- data/Gemfile +9 -10
- data/Guardfile +12 -0
- data/README.md +169 -301
- data/Rakefile +2 -36
- data/lib/secure_headers/configuration.rb +189 -0
- data/lib/secure_headers/headers/content_security_policy.rb +362 -213
- data/lib/secure_headers/headers/public_key_pins.rb +43 -58
- data/lib/secure_headers/headers/strict_transport_security.rb +21 -48
- data/lib/secure_headers/headers/x_content_type_options.rb +18 -32
- data/lib/secure_headers/headers/x_download_options.rb +18 -32
- data/lib/secure_headers/headers/x_frame_options.rb +24 -33
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +19 -33
- data/lib/secure_headers/headers/x_xss_protection.rb +17 -47
- 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 -44
- data/lib/secure_headers.rb +229 -162
- data/secure_headers.gemspec +7 -11
- data/spec/lib/secure_headers/configuration_spec.rb +80 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +175 -262
- 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 +13 -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 +22 -30
- data/spec/lib/secure_headers/middleware_spec.rb +40 -0
- data/spec/lib/secure_headers_spec.rb +202 -326
- data/spec/spec_helper.rb +32 -28
- data/upgrading-to-3-0.md +37 -0
- metadata +14 -94
- data/fixtures/rails_3_2_12/.rspec +0 -1
- data/fixtures/rails_3_2_12/Gemfile +0 -6
- data/fixtures/rails_3_2_12/README.rdoc +0 -261
- data/fixtures/rails_3_2_12/Rakefile +0 -7
- data/fixtures/rails_3_2_12/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_12/app/controllers/other_things_controller.rb +0 -5
- data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_12/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +0 -11
- data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +0 -2
- data/fixtures/rails_3_2_12/app/views/things/index.html.erb +0 -1
- data/fixtures/rails_3_2_12/config/application.rb +0 -14
- data/fixtures/rails_3_2_12/config/boot.rb +0 -6
- data/fixtures/rails_3_2_12/config/environment.rb +0 -5
- data/fixtures/rails_3_2_12/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +0 -17
- data/fixtures/rails_3_2_12/config/routes.rb +0 -4
- data/fixtures/rails_3_2_12/config/script_hashes.yml +0 -5
- data/fixtures/rails_3_2_12/config.ru +0 -7
- data/fixtures/rails_3_2_12/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +0 -83
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +0 -15
- data/fixtures/rails_3_2_12/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12/vendor/plugins/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/.rspec +0 -1
- data/fixtures/rails_3_2_12_no_init/Gemfile +0 -5
- data/fixtures/rails_3_2_12_no_init/README.rdoc +0 -261
- data/fixtures/rails_3_2_12_no_init/Rakefile +0 -7
- data/fixtures/rails_3_2_12_no_init/app/controllers/application_controller.rb +0 -4
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -20
- data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/app/models/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -12
- data/fixtures/rails_3_2_12_no_init/app/views/other_things/index.html.erb +0 -1
- data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -0
- data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -17
- data/fixtures/rails_3_2_12_no_init/config/boot.rb +0 -6
- data/fixtures/rails_3_2_12_no_init/config/environment.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +0 -37
- data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -4
- data/fixtures/rails_3_2_12_no_init/config.ru +0 -4
- data/fixtures/rails_3_2_12_no_init/lib/assets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/lib/tasks/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/log/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +0 -56
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +0 -54
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/vendor/assets/javascripts/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep +0 -0
- data/fixtures/rails_3_2_12_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 -17
- 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/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 -47
|
@@ -1,364 +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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
subject.set_x_frame_options_header
|
|
43
|
-
subject.set_x_content_type_options_header
|
|
44
|
-
subject.set_x_xss_protection_header
|
|
45
|
-
subject.set_x_download_options_header
|
|
46
|
-
subject.set_x_permitted_cross_domain_policies_header
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
describe "#set_header" do
|
|
50
|
-
it "accepts name/value pairs" do
|
|
51
|
-
should_assign_header("X-Hipster-Ipsum", "kombucha")
|
|
52
|
-
subject.send(:set_header, "X-Hipster-Ipsum", "kombucha")
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it "accepts header objects" do
|
|
56
|
-
should_assign_header("Strict-Transport-Security", SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
57
|
-
subject.send(:set_header, SecureHeaders::StrictTransportSecurity.new)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
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
|
|
41
|
+
end
|
|
60
42
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
subject.set_x_frame_options_header
|
|
69
|
-
subject.set_hsts_header
|
|
70
|
-
subject.set_hpkp_header
|
|
71
|
-
subject.set_x_xss_protection_header
|
|
72
|
-
subject.set_x_content_type_options_header
|
|
73
|
-
subject.set_x_download_options_header
|
|
74
|
-
subject.set_x_permitted_cross_domain_policies_header
|
|
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
|
|
49
|
+
end
|
|
75
50
|
end
|
|
76
|
-
end
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
83
58
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
88
64
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
subject.set_x_download_options_header(false)
|
|
92
|
-
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'))
|
|
93
67
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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)
|
|
71
|
+
end
|
|
98
72
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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)
|
|
77
|
+
end
|
|
103
78
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
85
|
+
end
|
|
108
86
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
114
92
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
subject.set_hpkp_header
|
|
118
|
-
end
|
|
93
|
+
expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
|
|
94
|
+
end
|
|
119
95
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
125
109
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
131
120
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
138
127
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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'")
|
|
154
162
|
end
|
|
155
|
-
expect(subject).not_to receive(:set_header)
|
|
156
|
-
set_security_headers(subject)
|
|
157
|
-
reset_config
|
|
158
163
|
end
|
|
159
164
|
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
describe "SecureHeaders#header_hash" do
|
|
163
|
-
def expect_default_values(hash)
|
|
164
|
-
expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
|
|
165
|
-
expect(hash[XDO_HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
|
|
166
|
-
expect(hash[HSTS_HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
167
|
-
expect(hash[X_XSS_PROTECTION_HEADER_NAME]).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
|
|
168
|
-
expect(hash[X_CONTENT_TYPE_OPTIONS_HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
|
|
169
|
-
expect(hash[XPCDP_HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
it "produces a hash of headers given a hash as config" do
|
|
173
|
-
hash = SecureHeaders::header_hash(:csp => {:default_src => 'none', :img_src => "data:", :disable_fill_missing => true})
|
|
174
|
-
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
|
|
175
|
-
expect_default_values(hash)
|
|
176
|
-
end
|
|
177
165
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
:report_uri => '//example.com/uri-directive',
|
|
186
|
-
:pins => [
|
|
187
|
-
{:sha256 => 'abc'},
|
|
188
|
-
{:sha256 => '123'}
|
|
189
|
-
]
|
|
190
|
-
}
|
|
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)
|
|
191
173
|
end
|
|
192
174
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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)
|
|
197
181
|
end
|
|
198
182
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
hash = SecureHeaders::header_hash
|
|
207
|
-
expect(hash['Content-Security-Policy-Report-Only']).to eq(SecureHeaders::ContentSecurityPolicy::Constants::DEFAULT_CSP_HEADER)
|
|
208
|
-
expect_default_values(hash)
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
describe "#set_x_frame_options_header" do
|
|
213
|
-
it "sets the X-Frame-Options header" do
|
|
214
|
-
should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
|
|
215
|
-
subject.set_x_frame_options_header
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
it "allows a custom X-Frame-Options header" do
|
|
219
|
-
should_assign_header(XFO_HEADER_NAME, "DENY")
|
|
220
|
-
subject.set_x_frame_options_header(:value => 'DENY')
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
describe "#set_x_download_options_header" do
|
|
225
|
-
it "sets the X-Download-Options header" do
|
|
226
|
-
should_assign_header(XDO_HEADER_NAME, SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
|
|
227
|
-
subject.set_x_download_options_header
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
it "allows a custom X-Download-Options header" do
|
|
231
|
-
should_assign_header(XDO_HEADER_NAME, "noopen")
|
|
232
|
-
subject.set_x_download_options_header(:value => 'noopen')
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
describe "#set_strict_transport_security" do
|
|
237
|
-
it "sets the Strict-Transport-Security header" do
|
|
238
|
-
should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
239
|
-
subject.set_hsts_header
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
it "allows you to specific a custom max-age value" do
|
|
243
|
-
should_assign_header(HSTS_HEADER_NAME, 'max-age=1234')
|
|
244
|
-
subject.set_hsts_header(:max_age => 1234)
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
it "allows you to specify includeSubdomains" do
|
|
248
|
-
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
|
|
249
|
-
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
it "allows you to specify preload" do
|
|
253
|
-
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")
|
|
254
|
-
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true)
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
describe "#set_public_key_pins" do
|
|
259
|
-
it "sets the Public-Key-Pins header" do
|
|
260
|
-
should_assign_header(HPKP_HEADER_NAME + "-Report-Only", "max-age=1234")
|
|
261
|
-
subject.set_hpkp_header(:max_age => 1234)
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
it "allows you to enforce public key pinning" do
|
|
265
|
-
should_assign_header(HPKP_HEADER_NAME, "max-age=1234")
|
|
266
|
-
subject.set_hpkp_header(:max_age => 1234, :enforce => true)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
it "allows you to specific a custom max-age value" do
|
|
270
|
-
should_assign_header(HPKP_HEADER_NAME + "-Report-Only", 'max-age=1234')
|
|
271
|
-
subject.set_hpkp_header(:max_age => 1234)
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
it "allows you to specify includeSubdomains" do
|
|
275
|
-
should_assign_header(HPKP_HEADER_NAME, "max-age=1234; includeSubDomains")
|
|
276
|
-
subject.set_hpkp_header(:max_age => 1234, :include_subdomains => true, :enforce => true)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
it "allows you to specify a report-uri" do
|
|
280
|
-
should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com\"")
|
|
281
|
-
subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :enforce => true)
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
it "allows you to specify a report-uri with app_name" do
|
|
285
|
-
should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com?enforce=true&app_name=my_app\"")
|
|
286
|
-
subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :app_name => "my_app", :tag_report_uri => true, :enforce => true)
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
describe "#set_x_xss_protection" do
|
|
291
|
-
it "sets the X-XSS-Protection header" do
|
|
292
|
-
should_assign_header(X_XSS_PROTECTION_HEADER_NAME, SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
|
|
293
|
-
subject.set_x_xss_protection_header
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
it "sets a custom X-XSS-Protection header" do
|
|
297
|
-
should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '0')
|
|
298
|
-
subject.set_x_xss_protection_header("0")
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
it "sets the block flag" do
|
|
302
|
-
should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '1; mode=block')
|
|
303
|
-
subject.set_x_xss_protection_header(:mode => 'block', :value => 1)
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
describe "#set_x_content_type_options" do
|
|
308
|
-
USER_AGENTS.each do |useragent|
|
|
309
|
-
context "when using #{useragent}" do
|
|
310
|
-
before(:each) do
|
|
311
|
-
stub_user_agent(USER_AGENTS[useragent])
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
it "sets the X-Content-Type-Options header" do
|
|
315
|
-
should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
|
|
316
|
-
subject.set_x_content_type_options_header
|
|
317
|
-
end
|
|
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
|
|
318
190
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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)
|
|
323
197
|
end
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
198
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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)
|
|
333
205
|
end
|
|
334
|
-
end
|
|
335
206
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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)
|
|
341
213
|
end
|
|
342
|
-
end
|
|
343
214
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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)
|
|
349
221
|
end
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
222
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
358
230
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
362
238
|
end
|
|
363
239
|
end
|
|
364
240
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,40 +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
|
-
def
|
|
35
|
-
expect(
|
|
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)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module SecureHeaders
|
|
33
|
+
class Configuration
|
|
34
|
+
class << self
|
|
35
|
+
def clear_configurations
|
|
36
|
+
@configurations = nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
36
40
|
end
|
|
37
41
|
|
|
38
|
-
def
|
|
39
|
-
|
|
42
|
+
def reset_config
|
|
43
|
+
SecureHeaders::Configuration.clear_configurations
|
|
40
44
|
end
|