secure_headers 3.0.3 → 3.1.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.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +0 -9
- data/.travis.yml +13 -5
- data/CHANGELOG.md +11 -0
- data/Gemfile +5 -2
- data/README.md +7 -42
- data/lib/secure_headers.rb +37 -63
- data/lib/secure_headers/configuration.rb +85 -54
- data/lib/secure_headers/headers/content_security_policy.rb +31 -309
- data/lib/secure_headers/headers/policy_management.rb +319 -0
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -1
- data/lib/secure_headers/middleware.rb +23 -0
- data/lib/secure_headers/railtie.rb +1 -1
- data/secure_headers.gemspec +1 -1
- data/spec/lib/secure_headers/configuration_spec.rb +2 -4
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +0 -175
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +190 -0
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +1 -1
- data/spec/lib/secure_headers/middleware_spec.rb +23 -4
- data/spec/lib/secure_headers_spec.rb +100 -41
- data/spec/spec_helper.rb +4 -1
- metadata +5 -4
- data/lib/secure_headers/padrino.rb +0 -13
- data/travis.sh +0 -10
@@ -4,7 +4,7 @@ module SecureHeaders
|
|
4
4
|
describe StrictTransportSecurity do
|
5
5
|
describe "#value" do
|
6
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"]) }
|
7
|
+
specify { expect(StrictTransportSecurity.make_header("max-age=1234; includeSubdomains; preload")).to eq([StrictTransportSecurity::HEADER_NAME, "max-age=1234; includeSubdomains; preload"]) }
|
8
8
|
|
9
9
|
context "with an invalid configuration" do
|
10
10
|
context "with a string argument" do
|
@@ -2,11 +2,11 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
module SecureHeaders
|
4
4
|
describe Middleware do
|
5
|
-
let(:app) {
|
5
|
+
let(:app) { lambda { |env| [200, env, "app"] } }
|
6
|
+
let(:cookie_app) { lambda { |env| [200, env.merge("Set-Cookie" => "foo=bar"), "app"] } }
|
6
7
|
|
7
|
-
let
|
8
|
-
|
9
|
-
end
|
8
|
+
let(:middleware) { Middleware.new(app) }
|
9
|
+
let(:cookie_middleware) { Middleware.new(cookie_app) }
|
10
10
|
|
11
11
|
before(:each) do
|
12
12
|
reset_config
|
@@ -33,8 +33,27 @@ module SecureHeaders
|
|
33
33
|
end
|
34
34
|
request = Rack::Request.new({})
|
35
35
|
SecureHeaders.use_secure_headers_override(request, "my_custom_config")
|
36
|
+
expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
|
36
37
|
_, env = middleware.call request.env
|
37
38
|
expect(env[CSP::HEADER_NAME]).to match("example.org")
|
38
39
|
end
|
40
|
+
|
41
|
+
context "cookies should be flagged" do
|
42
|
+
it "flags cookies as secure" do
|
43
|
+
Configuration.default { |config| config.secure_cookies = true }
|
44
|
+
request = Rack::MockRequest.new(cookie_middleware)
|
45
|
+
response = request.get '/'
|
46
|
+
expect(response.headers['Set-Cookie']).to match(Middleware::SECURE_COOKIE_REGEXP)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "cookies should not be flagged" do
|
51
|
+
it "does not flags cookies as secure" do
|
52
|
+
Configuration.default { |config| config.secure_cookies = false }
|
53
|
+
request = Rack::MockRequest.new(cookie_middleware)
|
54
|
+
response = request.get '/'
|
55
|
+
expect(response.headers['Set-Cookie']).not_to match(Middleware::SECURE_COOKIE_REGEXP)
|
56
|
+
end
|
57
|
+
end
|
39
58
|
end
|
40
59
|
end
|
@@ -2,48 +2,39 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module SecureHeaders
|
4
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
5
|
before(:each) do
|
18
6
|
reset_config
|
19
|
-
@request = Rack::Request.new("HTTP_X_FORWARDED_SSL" => "on")
|
20
7
|
end
|
21
8
|
|
9
|
+
let(:request) { Rack::Request.new("HTTP_X_FORWARDED_SSL" => "on") }
|
10
|
+
|
22
11
|
it "raises a NotYetConfiguredError if default has not been set" do
|
23
12
|
expect do
|
24
|
-
SecureHeaders.header_hash_for(
|
13
|
+
SecureHeaders.header_hash_for(request)
|
25
14
|
end.to raise_error(Configuration::NotYetConfiguredError)
|
26
15
|
end
|
27
16
|
|
28
17
|
it "raises a NotYetConfiguredError if trying to opt-out of unconfigured headers" do
|
29
18
|
expect do
|
30
|
-
SecureHeaders.opt_out_of_header(
|
19
|
+
SecureHeaders.opt_out_of_header(request, CSP::CONFIG_KEY)
|
31
20
|
end.to raise_error(Configuration::NotYetConfiguredError)
|
32
21
|
end
|
33
22
|
|
34
23
|
describe "#header_hash_for" do
|
35
24
|
it "allows you to opt out of individual headers" do
|
36
25
|
Configuration.default
|
37
|
-
SecureHeaders.opt_out_of_header(
|
38
|
-
|
26
|
+
SecureHeaders.opt_out_of_header(request, CSP::CONFIG_KEY)
|
27
|
+
SecureHeaders.opt_out_of_header(request, XContentTypeOptions::CONFIG_KEY)
|
28
|
+
hash = SecureHeaders.header_hash_for(request)
|
39
29
|
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
|
40
30
|
expect(hash['Content-Security-Policy']).to be_nil
|
31
|
+
expect(hash['X-Content-Type-Options']).to be_nil
|
41
32
|
end
|
42
33
|
|
43
34
|
it "allows you to opt out entirely" do
|
44
35
|
Configuration.default
|
45
|
-
SecureHeaders.opt_out_of_all_protection(
|
46
|
-
hash = SecureHeaders.header_hash_for(
|
36
|
+
SecureHeaders.opt_out_of_all_protection(request)
|
37
|
+
hash = SecureHeaders.header_hash_for(request)
|
47
38
|
ALL_HEADER_CLASSES.each do |klass|
|
48
39
|
expect(hash[klass::CONFIG_KEY]).to be_nil
|
49
40
|
end
|
@@ -51,8 +42,8 @@ module SecureHeaders
|
|
51
42
|
|
52
43
|
it "allows you to override X-Frame-Options settings" do
|
53
44
|
Configuration.default
|
54
|
-
SecureHeaders.override_x_frame_options(
|
55
|
-
hash = SecureHeaders.header_hash_for(
|
45
|
+
SecureHeaders.override_x_frame_options(request, XFrameOptions::DENY)
|
46
|
+
hash = SecureHeaders.header_hash_for(request)
|
56
47
|
expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::DENY)
|
57
48
|
end
|
58
49
|
|
@@ -62,17 +53,36 @@ module SecureHeaders
|
|
62
53
|
config.csp = OPT_OUT
|
63
54
|
end
|
64
55
|
|
65
|
-
SecureHeaders.override_x_frame_options(
|
66
|
-
SecureHeaders.override_content_security_policy_directives(
|
56
|
+
SecureHeaders.override_x_frame_options(request, XFrameOptions::SAMEORIGIN)
|
57
|
+
SecureHeaders.override_content_security_policy_directives(request, default_src: %w(https:), script_src: %w('self'))
|
67
58
|
|
68
|
-
hash = SecureHeaders.header_hash_for(
|
59
|
+
hash = SecureHeaders.header_hash_for(request)
|
69
60
|
expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; script-src 'self'")
|
70
61
|
expect(hash[XFrameOptions::HEADER_NAME]).to eq(XFrameOptions::SAMEORIGIN)
|
71
62
|
end
|
72
63
|
|
64
|
+
it "produces a UA-specific CSP when overriding (and busting the cache)" do
|
65
|
+
config = Configuration.default do |config|
|
66
|
+
config.csp = {
|
67
|
+
default_src: %w('self'),
|
68
|
+
child_src: %w('self'), #unsupported by firefox
|
69
|
+
frame_src: %w('self')
|
70
|
+
}
|
71
|
+
end
|
72
|
+
firefox_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:firefox]))
|
73
|
+
|
74
|
+
# append an unsupported directive
|
75
|
+
SecureHeaders.override_content_security_policy_directives(firefox_request, plugin_types: %w(flash))
|
76
|
+
# append a supported directive
|
77
|
+
SecureHeaders.override_content_security_policy_directives(firefox_request, script_src: %w('self'))
|
78
|
+
|
79
|
+
hash = SecureHeaders.header_hash_for(firefox_request)
|
80
|
+
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
|
81
|
+
end
|
82
|
+
|
73
83
|
it "produces a hash of headers with default config" do
|
74
84
|
Configuration.default
|
75
|
-
hash = SecureHeaders.header_hash_for(
|
85
|
+
hash = SecureHeaders.header_hash_for(request)
|
76
86
|
expect_default_values(hash)
|
77
87
|
end
|
78
88
|
|
@@ -87,7 +97,15 @@ module SecureHeaders
|
|
87
97
|
it "does not set the HPKP header if request is over HTTP" do
|
88
98
|
plaintext_request = Rack::Request.new({})
|
89
99
|
Configuration.default do |config|
|
90
|
-
config.hpkp =
|
100
|
+
config.hpkp = {
|
101
|
+
max_age: 1_000_000,
|
102
|
+
include_subdomains: true,
|
103
|
+
report_uri: '//example.com/uri-directive',
|
104
|
+
pins: [
|
105
|
+
{ sha256: 'abc' },
|
106
|
+
{ sha256: '123' }
|
107
|
+
]
|
108
|
+
}
|
91
109
|
end
|
92
110
|
|
93
111
|
expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
|
@@ -102,26 +120,67 @@ module SecureHeaders
|
|
102
120
|
}
|
103
121
|
end
|
104
122
|
|
105
|
-
SecureHeaders.append_content_security_policy_directives(
|
106
|
-
hash = SecureHeaders.header_hash_for(
|
123
|
+
SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
|
124
|
+
hash = SecureHeaders.header_hash_for(request)
|
107
125
|
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
|
108
126
|
end
|
109
127
|
|
128
|
+
it "dups global configuration just once when overriding n times and only calls idempotent_additions? once" do
|
129
|
+
Configuration.default do |config|
|
130
|
+
config.csp = {
|
131
|
+
default_src: %w('self')
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
expect(CSP).to receive(:idempotent_additions?).once
|
136
|
+
|
137
|
+
# before an override occurs, the env is empty
|
138
|
+
expect(request.env[SECURE_HEADERS_CONFIG]).to be_nil
|
139
|
+
|
140
|
+
SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
|
141
|
+
new_config = SecureHeaders.config_for(request)
|
142
|
+
expect(new_config).to_not be(Configuration.get)
|
143
|
+
|
144
|
+
SecureHeaders.override_content_security_policy_directives(request, script_src: %w(yet.anothercdn.com))
|
145
|
+
current_config = SecureHeaders.config_for(request)
|
146
|
+
expect(current_config).to be(new_config)
|
147
|
+
|
148
|
+
SecureHeaders.header_hash_for(request)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "doesn't allow you to muck with csp configs when a dynamic policy is in use" do
|
152
|
+
default_config = Configuration.default
|
153
|
+
expect { default_config.csp = {} }.to raise_error(NoMethodError)
|
154
|
+
|
155
|
+
# config is frozen
|
156
|
+
expect { default_config.send(:csp=, {}) }.to raise_error(RuntimeError)
|
157
|
+
|
158
|
+
SecureHeaders.append_content_security_policy_directives(request, script_src: %w(anothercdn.com))
|
159
|
+
new_config = SecureHeaders.config_for(request)
|
160
|
+
expect { new_config.send(:csp=, {}) }.to raise_error(Configuration::IllegalPolicyModificationError)
|
161
|
+
|
162
|
+
expect do
|
163
|
+
new_config.instance_eval do
|
164
|
+
new_config.csp = {}
|
165
|
+
end
|
166
|
+
end.to raise_error(Configuration::IllegalPolicyModificationError)
|
167
|
+
end
|
168
|
+
|
110
169
|
it "overrides individual directives" do
|
111
170
|
Configuration.default do |config|
|
112
171
|
config.csp = {
|
113
172
|
default_src: %w('self')
|
114
173
|
}
|
115
174
|
end
|
116
|
-
SecureHeaders.override_content_security_policy_directives(
|
117
|
-
hash = SecureHeaders.header_hash_for(
|
175
|
+
SecureHeaders.override_content_security_policy_directives(request, default_src: %w('none'))
|
176
|
+
hash = SecureHeaders.header_hash_for(request)
|
118
177
|
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'none'")
|
119
178
|
end
|
120
179
|
|
121
180
|
it "overrides non-existant directives" do
|
122
181
|
Configuration.default
|
123
|
-
SecureHeaders.override_content_security_policy_directives(
|
124
|
-
hash = SecureHeaders.header_hash_for(
|
182
|
+
SecureHeaders.override_content_security_policy_directives(request, img_src: [ContentSecurityPolicy::DATA_PROTOCOL])
|
183
|
+
hash = SecureHeaders.header_hash_for(request)
|
125
184
|
expect(hash[CSP::HEADER_NAME]).to eq("default-src https:; img-src data:")
|
126
185
|
end
|
127
186
|
|
@@ -134,9 +193,9 @@ module SecureHeaders
|
|
134
193
|
}
|
135
194
|
end
|
136
195
|
|
137
|
-
|
138
|
-
nonce = SecureHeaders.content_security_policy_script_nonce(
|
139
|
-
hash = SecureHeaders.header_hash_for(
|
196
|
+
safari_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:safari5]))
|
197
|
+
nonce = SecureHeaders.content_security_policy_script_nonce(safari_request)
|
198
|
+
hash = SecureHeaders.header_hash_for(safari_request)
|
140
199
|
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline'; style-src 'self'")
|
141
200
|
end
|
142
201
|
|
@@ -149,15 +208,15 @@ module SecureHeaders
|
|
149
208
|
}
|
150
209
|
end
|
151
210
|
|
152
|
-
|
153
|
-
nonce = SecureHeaders.content_security_policy_script_nonce(
|
211
|
+
chrome_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
|
212
|
+
nonce = SecureHeaders.content_security_policy_script_nonce(chrome_request)
|
154
213
|
|
155
214
|
# simulate the nonce being used multiple times in a request:
|
156
|
-
SecureHeaders.content_security_policy_script_nonce(
|
157
|
-
SecureHeaders.content_security_policy_script_nonce(
|
158
|
-
SecureHeaders.content_security_policy_script_nonce(
|
215
|
+
SecureHeaders.content_security_policy_script_nonce(chrome_request)
|
216
|
+
SecureHeaders.content_security_policy_script_nonce(chrome_request)
|
217
|
+
SecureHeaders.content_security_policy_script_nonce(chrome_request)
|
159
218
|
|
160
|
-
hash = SecureHeaders.header_hash_for(
|
219
|
+
hash = SecureHeaders.header_hash_for(chrome_request)
|
161
220
|
expect(hash['Content-Security-Policy']).to eq("default-src 'self'; script-src mycdn.com 'nonce-#{nonce}'; style-src 'self'")
|
162
221
|
end
|
163
222
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,9 +3,12 @@ require 'rspec'
|
|
3
3
|
require 'rack'
|
4
4
|
require 'pry-nav'
|
5
5
|
|
6
|
+
require 'coveralls'
|
7
|
+
Coveralls.wear!
|
8
|
+
|
6
9
|
require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
|
7
10
|
|
8
|
-
|
11
|
+
|
9
12
|
|
10
13
|
USER_AGENTS = {
|
11
14
|
firefox: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secure_headers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Matatall
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- lib/secure_headers.rb
|
60
60
|
- lib/secure_headers/configuration.rb
|
61
61
|
- lib/secure_headers/headers/content_security_policy.rb
|
62
|
+
- lib/secure_headers/headers/policy_management.rb
|
62
63
|
- lib/secure_headers/headers/public_key_pins.rb
|
63
64
|
- lib/secure_headers/headers/strict_transport_security.rb
|
64
65
|
- lib/secure_headers/headers/x_content_type_options.rb
|
@@ -67,12 +68,12 @@ files:
|
|
67
68
|
- lib/secure_headers/headers/x_permitted_cross_domain_policies.rb
|
68
69
|
- lib/secure_headers/headers/x_xss_protection.rb
|
69
70
|
- lib/secure_headers/middleware.rb
|
70
|
-
- lib/secure_headers/padrino.rb
|
71
71
|
- lib/secure_headers/railtie.rb
|
72
72
|
- lib/secure_headers/view_helper.rb
|
73
73
|
- secure_headers.gemspec
|
74
74
|
- spec/lib/secure_headers/configuration_spec.rb
|
75
75
|
- spec/lib/secure_headers/headers/content_security_policy_spec.rb
|
76
|
+
- spec/lib/secure_headers/headers/policy_management_spec.rb
|
76
77
|
- spec/lib/secure_headers/headers/public_key_pins_spec.rb
|
77
78
|
- spec/lib/secure_headers/headers/strict_transport_security_spec.rb
|
78
79
|
- spec/lib/secure_headers/headers/x_content_type_options_spec.rb
|
@@ -83,7 +84,6 @@ files:
|
|
83
84
|
- spec/lib/secure_headers/middleware_spec.rb
|
84
85
|
- spec/lib/secure_headers_spec.rb
|
85
86
|
- spec/spec_helper.rb
|
86
|
-
- travis.sh
|
87
87
|
- upgrading-to-3-0.md
|
88
88
|
homepage: https://github.com/twitter/secureheaders
|
89
89
|
licenses:
|
@@ -113,6 +113,7 @@ summary: Add easily configured security headers to responses including content-s
|
|
113
113
|
test_files:
|
114
114
|
- spec/lib/secure_headers/configuration_spec.rb
|
115
115
|
- spec/lib/secure_headers/headers/content_security_policy_spec.rb
|
116
|
+
- spec/lib/secure_headers/headers/policy_management_spec.rb
|
116
117
|
- spec/lib/secure_headers/headers/public_key_pins_spec.rb
|
117
118
|
- spec/lib/secure_headers/headers/strict_transport_security_spec.rb
|
118
119
|
- spec/lib/secure_headers/headers/x_content_type_options_spec.rb
|
data/travis.sh
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
#! /bin/sh
|
2
|
-
|
3
|
-
bundle install >> /dev/null &&
|
4
|
-
bundle exec rspec --format progress spec &&
|
5
|
-
cd fixtures/rails_3_2_12 &&
|
6
|
-
bundle install >> /dev/null &&
|
7
|
-
bundle exec rspec --format progress spec &&
|
8
|
-
cd ../../fixtures/rails_3_2_12_no_init &&
|
9
|
-
bundle install >> /dev/null &&
|
10
|
-
bundle exec rspec spec
|