secure_headers 1.0.0 → 2.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.
- data/.gitignore +4 -8
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -3
- data/Gemfile +6 -8
- data/README.md +208 -134
- data/Rakefile +11 -116
- data/fixtures/rails_3_2_12/Gemfile +0 -8
- data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -1
- data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +1 -4
- data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +2 -1
- data/fixtures/rails_3_2_12/app/views/things/index.html.erb +1 -21
- data/fixtures/rails_3_2_12/config/application.rb +0 -54
- data/fixtures/rails_3_2_12/config/environments/test.rb +2 -2
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +3 -1
- data/fixtures/rails_3_2_12/config/routes.rb +0 -57
- data/fixtures/rails_3_2_12/config/script_hashes.yml +5 -0
- data/fixtures/rails_3_2_12/config.ru +3 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +57 -19
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +18 -16
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -5
- data/fixtures/rails_3_2_12_no_init/Gemfile +0 -9
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +1 -2
- data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +1 -1
- data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -2
- data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -21
- data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -51
- data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +2 -2
- data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -57
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +17 -12
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +17 -12
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -18
- data/fixtures/rails_4_1_8/Gemfile +5 -0
- data/fixtures/rails_4_1_8/README.rdoc +28 -0
- data/fixtures/rails_4_1_8/Rakefile +6 -0
- data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +4 -0
- data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +5 -0
- data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +5 -0
- 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 +11 -0
- data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +2 -0
- data/fixtures/rails_4_1_8/app/views/things/index.html.erb +1 -0
- data/fixtures/rails_4_1_8/config/application.rb +15 -0
- data/fixtures/rails_4_1_8/config/boot.rb +4 -0
- data/fixtures/rails_4_1_8/config/environment.rb +5 -0
- data/fixtures/rails_4_1_8/config/environments/test.rb +10 -0
- data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +17 -0
- data/fixtures/rails_4_1_8/config/routes.rb +4 -0
- data/fixtures/rails_4_1_8/config/script_hashes.yml +5 -0
- data/fixtures/rails_4_1_8/config/secrets.yml +22 -0
- data/fixtures/rails_4_1_8/config.ru +4 -0
- 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 +83 -0
- data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +59 -0
- data/fixtures/rails_4_1_8/spec/spec_helper.rb +15 -0
- 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 +7 -0
- data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +22 -0
- data/lib/secure_headers/headers/content_security_policy.rb +160 -161
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
- data/lib/secure_headers/headers/x_download_options.rb +39 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +40 -0
- data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
- data/lib/secure_headers/padrino.rb +14 -0
- data/lib/secure_headers/railtie.rb +19 -24
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers/view_helper.rb +68 -0
- data/lib/secure_headers.rb +70 -29
- data/lib/tasks/tasks.rake +48 -0
- data/secure_headers.gemspec +4 -5
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +47 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +127 -299
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +21 -20
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +12 -12
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +32 -0
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +12 -13
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +64 -0
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -19
- data/spec/lib/secure_headers_spec.rb +68 -83
- data/spec/spec_helper.rb +29 -18
- metadata +51 -66
- data/.rvmrc +0 -1
- data/Guardfile +0 -10
- data/HISTORY.md +0 -100
- data/app/controllers/content_security_policy_controller.rb +0 -53
- data/config/curl-ca-bundle.crt +0 -5420
- data/config/routes.rb +0 -3
- data/fixtures/rails_3_2_12/Guardfile +0 -14
- data/fixtures/rails_3_2_12/app/models/thing.rb +0 -3
- data/fixtures/rails_3_2_12/config/database.yml +0 -25
- data/fixtures/rails_3_2_12/config/environments/development.rb +0 -37
- data/fixtures/rails_3_2_12/config/environments/production.rb +0 -67
- data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +0 -7
- data/fixtures/rails_3_2_12/config/initializers/inflections.rb +0 -15
- data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +0 -5
- data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +0 -7
- data/fixtures/rails_3_2_12/config/initializers/session_store.rb +0 -8
- data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +0 -14
- data/fixtures/rails_3_2_12/config/locales/en.yml +0 -5
- data/fixtures/rails_3_2_12/db/schema.rb +0 -16
- data/fixtures/rails_3_2_12/db/seeds.rb +0 -7
- data/fixtures/rails_3_2_12_no_init/Guardfile +0 -14
- data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +0 -3
- data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +0 -17
- data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +0 -6
- data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +0 -5
- data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +0 -5
- data/fixtures/rails_3_2_12_no_init/config/database.yml +0 -25
- data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +0 -37
- data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +0 -67
- data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +0 -7
- data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +0 -15
- data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +0 -7
- data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +0 -8
- data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +0 -14
- data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +0 -5
- data/fixtures/rails_3_2_12_no_init/db/schema.rb +0 -16
- data/fixtures/rails_3_2_12_no_init/db/seeds.rb +0 -7
- data/lib/secure_headers/headers/content_security_policy/browser_strategy.rb +0 -78
- data/lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb +0 -72
- data/lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb +0 -6
- data/lib/secure_headers/headers/content_security_policy/standard_browser_strategy.rb +0 -22
- data/spec/controllers/content_security_policy_controller_spec.rb +0 -74
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
2
|
describe XContentTypeOptions do
|
|
3
|
-
specify{ XContentTypeOptions.new.name.
|
|
3
|
+
specify{ expect(XContentTypeOptions.new.name).to eq("X-Content-Type-Options") }
|
|
4
4
|
|
|
5
5
|
describe "#value" do
|
|
6
|
-
specify { XContentTypeOptions.new.value.
|
|
7
|
-
specify { XContentTypeOptions.new("nosniff").value.
|
|
8
|
-
specify { XContentTypeOptions.new(:value => 'nosniff').value.
|
|
6
|
+
specify { expect(XContentTypeOptions.new.value).to eq(XContentTypeOptions::Constants::DEFAULT_VALUE)}
|
|
7
|
+
specify { expect(XContentTypeOptions.new("nosniff").value).to eq("nosniff")}
|
|
8
|
+
specify { expect(XContentTypeOptions.new(:value => 'nosniff').value).to eq("nosniff")}
|
|
9
9
|
|
|
10
10
|
context "invalid configuration values" do
|
|
11
11
|
it "accepts nosniff" do
|
|
12
|
-
|
|
12
|
+
expect {
|
|
13
13
|
XContentTypeOptions.new("nosniff")
|
|
14
|
-
}.
|
|
14
|
+
}.not_to raise_error
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
expect {
|
|
17
17
|
XContentTypeOptions.new(:value => "nosniff")
|
|
18
|
-
}.
|
|
18
|
+
}.not_to raise_error
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it "accepts nil" do
|
|
22
|
-
|
|
22
|
+
expect {
|
|
23
23
|
XContentTypeOptions.new
|
|
24
|
-
}.
|
|
24
|
+
}.not_to raise_error
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
it "doesn't accept anything besides no-sniff" do
|
|
28
|
-
|
|
28
|
+
expect {
|
|
29
29
|
XContentTypeOptions.new("donkey")
|
|
30
|
-
}.
|
|
30
|
+
}.to raise_error
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module SecureHeaders
|
|
2
|
+
describe XDownloadOptions do
|
|
3
|
+
specify { expect(XDownloadOptions.new.name).to eq(XDO_HEADER_NAME)}
|
|
4
|
+
specify { expect(XDownloadOptions.new.value).to eq("noopen")}
|
|
5
|
+
specify { expect(XDownloadOptions.new('noopen').value).to eq('noopen')}
|
|
6
|
+
specify { expect(XDownloadOptions.new(:value => 'noopen').value).to eq('noopen') }
|
|
7
|
+
|
|
8
|
+
context "invalid configuration values" do
|
|
9
|
+
it "accepts noopen" do
|
|
10
|
+
expect {
|
|
11
|
+
XDownloadOptions.new("noopen")
|
|
12
|
+
}.not_to raise_error
|
|
13
|
+
|
|
14
|
+
expect {
|
|
15
|
+
XDownloadOptions.new(:value => "noopen")
|
|
16
|
+
}.not_to raise_error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "accepts nil" do
|
|
20
|
+
expect {
|
|
21
|
+
XDownloadOptions.new
|
|
22
|
+
}.not_to raise_error
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "doesn't accept anything besides noopen" do
|
|
26
|
+
expect {
|
|
27
|
+
XDownloadOptions.new("open")
|
|
28
|
+
}.to raise_error
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -2,35 +2,34 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module SecureHeaders
|
|
4
4
|
describe XFrameOptions do
|
|
5
|
-
specify{ XFrameOptions.new.name.
|
|
5
|
+
specify{ expect(XFrameOptions.new.name).to eq("X-Frame-Options") }
|
|
6
6
|
|
|
7
7
|
describe "#value" do
|
|
8
|
-
specify { XFrameOptions.new.value.
|
|
9
|
-
specify { XFrameOptions.new("SAMEORIGIN").value.
|
|
10
|
-
specify { XFrameOptions.new(:value => 'DENY').value.
|
|
8
|
+
specify { expect(XFrameOptions.new.value).to eq(XFrameOptions::Constants::DEFAULT_VALUE)}
|
|
9
|
+
specify { expect(XFrameOptions.new("SAMEORIGIN").value).to eq("SAMEORIGIN")}
|
|
10
|
+
specify { expect(XFrameOptions.new(:value => 'DENY').value).to eq("DENY")}
|
|
11
11
|
|
|
12
12
|
context "with invalid configuration" do
|
|
13
13
|
it "allows SAMEORIGIN" do
|
|
14
|
-
|
|
14
|
+
expect {
|
|
15
15
|
XFrameOptions.new("SAMEORIGIN").value
|
|
16
|
-
}.
|
|
16
|
+
}.not_to raise_error
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
it "allows DENY" do
|
|
20
|
-
|
|
20
|
+
expect {
|
|
21
21
|
XFrameOptions.new("DENY").value
|
|
22
|
-
}.
|
|
23
|
-
end
|
|
22
|
+
}.not_to raise_error end
|
|
24
23
|
|
|
25
24
|
it "allows ALLOW-FROM*" do
|
|
26
|
-
|
|
25
|
+
expect {
|
|
27
26
|
XFrameOptions.new("ALLOW-FROM: example.com").value
|
|
28
|
-
}.
|
|
27
|
+
}.not_to raise_error
|
|
29
28
|
end
|
|
30
29
|
it "does not allow garbage" do
|
|
31
|
-
|
|
30
|
+
expect {
|
|
32
31
|
XFrameOptions.new("I like turtles").value
|
|
33
|
-
}.
|
|
32
|
+
}.to raise_error(XFOBuildError)
|
|
34
33
|
end
|
|
35
34
|
end
|
|
36
35
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module SecureHeaders
|
|
2
|
+
describe XPermittedCrossDomainPolicies do
|
|
3
|
+
specify { expect(XPermittedCrossDomainPolicies.new.name).to eq(XPermittedCrossDomainPolicies::Constants::XPCDP_HEADER_NAME)}
|
|
4
|
+
specify { expect(XPermittedCrossDomainPolicies.new.value).to eq("none")}
|
|
5
|
+
specify { expect(XPermittedCrossDomainPolicies.new('master-only').value).to eq('master-only')}
|
|
6
|
+
specify { expect(XPermittedCrossDomainPolicies.new(:value => 'master-only').value).to eq('master-only') }
|
|
7
|
+
|
|
8
|
+
context "valid configuration values" do
|
|
9
|
+
it "accepts 'all'" do
|
|
10
|
+
expect {
|
|
11
|
+
XPermittedCrossDomainPolicies.new("all")
|
|
12
|
+
}.not_to raise_error
|
|
13
|
+
|
|
14
|
+
expect {
|
|
15
|
+
XPermittedCrossDomainPolicies.new(:value => "all")
|
|
16
|
+
}.not_to raise_error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "accepts 'by-ftp-filename'" do
|
|
20
|
+
expect {
|
|
21
|
+
XPermittedCrossDomainPolicies.new("by-ftp-filename")
|
|
22
|
+
}.not_to raise_error
|
|
23
|
+
|
|
24
|
+
expect {
|
|
25
|
+
XPermittedCrossDomainPolicies.new(:value => "by-ftp-filename")
|
|
26
|
+
}.not_to raise_error
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "accepts 'by-content-type'" do
|
|
30
|
+
expect {
|
|
31
|
+
XPermittedCrossDomainPolicies.new("by-content-type")
|
|
32
|
+
}.not_to raise_error
|
|
33
|
+
|
|
34
|
+
expect {
|
|
35
|
+
XPermittedCrossDomainPolicies.new(:value => "by-content-type")
|
|
36
|
+
}.not_to raise_error
|
|
37
|
+
end
|
|
38
|
+
it "accepts 'master-only'" do
|
|
39
|
+
expect {
|
|
40
|
+
XPermittedCrossDomainPolicies.new("master-only")
|
|
41
|
+
}.not_to raise_error
|
|
42
|
+
|
|
43
|
+
expect {
|
|
44
|
+
XPermittedCrossDomainPolicies.new(:value => "master-only")
|
|
45
|
+
}.not_to raise_error
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "accepts nil" do
|
|
49
|
+
expect {
|
|
50
|
+
XPermittedCrossDomainPolicies.new
|
|
51
|
+
}.not_to raise_error
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
context 'invlaid configuration values' do
|
|
56
|
+
|
|
57
|
+
it "doesn't accept invalid values" do
|
|
58
|
+
expect {
|
|
59
|
+
XPermittedCrossDomainPolicies.new("open")
|
|
60
|
+
}.to raise_error
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -1,53 +1,54 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
2
|
describe XXssProtection do
|
|
3
|
-
specify { XXssProtection.new.name.
|
|
4
|
-
specify { XXssProtection.new.value.
|
|
5
|
-
specify { XXssProtection.new("0").value.
|
|
6
|
-
specify { XXssProtection.new(:value => 1, :mode => 'block').value.
|
|
3
|
+
specify { expect(XXssProtection.new.name).to eq(X_XSS_PROTECTION_HEADER_NAME)}
|
|
4
|
+
specify { expect(XXssProtection.new.value).to eq("1")}
|
|
5
|
+
specify { expect(XXssProtection.new("0").value).to eq("0")}
|
|
6
|
+
specify { expect(XXssProtection.new(:value => 1, :mode => 'block').value).to eq('1; mode=block') }
|
|
7
|
+
specify { expect(XXssProtection.new(:value => 1, :mode => 'block', :report_uri => 'https://www.secure.com/reports').value).to eq('1; mode=block; report=https://www.secure.com/reports') }
|
|
7
8
|
|
|
8
9
|
context "with invalid configuration" do
|
|
9
10
|
it "should raise an error when providing a string that is not valid" do
|
|
10
|
-
|
|
11
|
+
expect {
|
|
11
12
|
XXssProtection.new("asdf")
|
|
12
|
-
}.
|
|
13
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
expect {
|
|
15
16
|
XXssProtection.new("asdf; mode=donkey")
|
|
16
|
-
}.
|
|
17
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
context "when using a hash value" do
|
|
20
21
|
it "should allow string values ('1' or '0' are the only valid strings)" do
|
|
21
|
-
|
|
22
|
+
expect {
|
|
22
23
|
XXssProtection.new(:value => '1')
|
|
23
|
-
}.
|
|
24
|
+
}.not_to raise_error
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
it "should allow integer values (1 or 0 are the only valid integers)" do
|
|
27
|
-
|
|
28
|
+
expect {
|
|
28
29
|
XXssProtection.new(:value => 1)
|
|
29
|
-
}.
|
|
30
|
+
}.not_to raise_error
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
it "should raise an error if no value key is supplied" do
|
|
33
|
-
|
|
34
|
+
expect {
|
|
34
35
|
XXssProtection.new(:mode => 'block')
|
|
35
|
-
}.
|
|
36
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
it "should raise an error if an invalid key is supplied" do
|
|
39
|
-
|
|
40
|
+
expect {
|
|
40
41
|
XXssProtection.new(:value => 123)
|
|
41
|
-
}.
|
|
42
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
it "should raise an error if mode != block" do
|
|
45
|
-
|
|
46
|
+
expect {
|
|
46
47
|
XXssProtection.new(:value => 1, :mode => "donkey")
|
|
47
|
-
}.
|
|
48
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
48
49
|
end
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
|
-
end
|
|
54
|
+
end
|
|
@@ -13,34 +13,15 @@ describe SecureHeaders do
|
|
|
13
13
|
|
|
14
14
|
before(:each) do
|
|
15
15
|
stub_user_agent(nil)
|
|
16
|
-
headers.
|
|
17
|
-
subject.
|
|
18
|
-
subject.
|
|
16
|
+
allow(headers).to receive(:[])
|
|
17
|
+
allow(subject).to receive(:response).and_return(response)
|
|
18
|
+
allow(subject).to receive(:request).and_return(request)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection].map{|header| [header, false]}]
|
|
22
|
-
USER_AGENTS = {
|
|
23
|
-
:firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
|
|
24
|
-
: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',
|
|
25
|
-
:ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
|
|
26
|
-
:opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
|
|
27
|
-
: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",
|
|
28
|
-
: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",
|
|
29
|
-
: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",
|
|
30
|
-
: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",
|
|
31
|
-
: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
|
-
}
|
|
33
|
-
|
|
34
|
-
def should_assign_header name, value
|
|
35
|
-
response.headers.should_receive(:[]=).with(name, value)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def should_not_assign_header name
|
|
39
|
-
response.headers.should_not_receive(:[]=).with(name, anything)
|
|
40
|
-
end
|
|
21
|
+
ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection, :x_permitted_cross_domain_policies].map{|header| [header, false]}]
|
|
41
22
|
|
|
42
23
|
def stub_user_agent val
|
|
43
|
-
request.
|
|
24
|
+
allow(request).to receive_message_chain(:env, :[]).and_return(val)
|
|
44
25
|
end
|
|
45
26
|
|
|
46
27
|
def options_for header
|
|
@@ -54,6 +35,8 @@ describe SecureHeaders do
|
|
|
54
35
|
config.x_content_type_options = nil
|
|
55
36
|
config.x_xss_protection = nil
|
|
56
37
|
config.csp = nil
|
|
38
|
+
config.x_download_options = nil
|
|
39
|
+
config.x_permitted_cross_domain_policies = nil
|
|
57
40
|
end
|
|
58
41
|
end
|
|
59
42
|
|
|
@@ -63,14 +46,8 @@ describe SecureHeaders do
|
|
|
63
46
|
subject.set_x_frame_options_header
|
|
64
47
|
subject.set_x_content_type_options_header
|
|
65
48
|
subject.set_x_xss_protection_header
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
describe "#ensure_security_headers" do
|
|
69
|
-
it "sets a before filter" do
|
|
70
|
-
options = {}
|
|
71
|
-
DummyClass.should_receive(:before_filter).exactly(5).times
|
|
72
|
-
DummyClass.ensure_security_headers(options)
|
|
73
|
-
end
|
|
49
|
+
subject.set_x_download_options_header
|
|
50
|
+
subject.set_x_permitted_cross_domain_policies_header
|
|
74
51
|
end
|
|
75
52
|
|
|
76
53
|
describe "#set_header" do
|
|
@@ -86,27 +63,18 @@ describe SecureHeaders do
|
|
|
86
63
|
end
|
|
87
64
|
|
|
88
65
|
describe "#set_security_headers" do
|
|
89
|
-
before(:each) do
|
|
90
|
-
SecureHeaders::ContentSecurityPolicy.stub(:new).and_return(double.as_null_object)
|
|
91
|
-
end
|
|
92
66
|
USER_AGENTS.each do |name, useragent|
|
|
93
67
|
it "sets all default headers for #{name} (smoke test)" do
|
|
94
68
|
stub_user_agent(useragent)
|
|
95
|
-
number_of_headers =
|
|
96
|
-
|
|
97
|
-
5
|
|
98
|
-
when :ios5, :safari5, :safari5_1
|
|
99
|
-
3 # csp breaks these browsers
|
|
100
|
-
else
|
|
101
|
-
4
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
subject.should_receive(:set_header).exactly(number_of_headers).times # a request for a given header
|
|
69
|
+
number_of_headers = 7
|
|
70
|
+
expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
|
|
105
71
|
subject.set_csp_header
|
|
106
72
|
subject.set_x_frame_options_header
|
|
107
73
|
subject.set_hsts_header
|
|
108
74
|
subject.set_x_xss_protection_header
|
|
109
75
|
subject.set_x_content_type_options_header
|
|
76
|
+
subject.set_x_download_options_header
|
|
77
|
+
subject.set_x_permitted_cross_domain_policies_header
|
|
110
78
|
end
|
|
111
79
|
end
|
|
112
80
|
|
|
@@ -121,34 +89,47 @@ describe SecureHeaders do
|
|
|
121
89
|
subject.set_x_xss_protection_header(false)
|
|
122
90
|
end
|
|
123
91
|
|
|
92
|
+
it "does not set the X-Download-Options header if disabled" do
|
|
93
|
+
should_not_assign_header(XDO_HEADER_NAME)
|
|
94
|
+
subject.set_x_download_options_header(false)
|
|
95
|
+
end
|
|
96
|
+
|
|
124
97
|
it "does not set the X-Frame-Options header if disabled" do
|
|
125
98
|
should_not_assign_header(XFO_HEADER_NAME)
|
|
126
99
|
subject.set_x_frame_options_header(false)
|
|
127
100
|
end
|
|
128
101
|
|
|
102
|
+
it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
|
|
103
|
+
should_not_assign_header(XPCDP_HEADER_NAME)
|
|
104
|
+
subject.set_x_permitted_cross_domain_policies_header(false)
|
|
105
|
+
end
|
|
106
|
+
|
|
129
107
|
it "does not set the HSTS header if disabled" do
|
|
130
108
|
should_not_assign_header(HSTS_HEADER_NAME)
|
|
131
109
|
subject.set_hsts_header(false)
|
|
132
110
|
end
|
|
133
111
|
|
|
134
112
|
it "does not set the HSTS header if request is over HTTP" do
|
|
135
|
-
subject.
|
|
113
|
+
allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
|
|
136
114
|
should_not_assign_header(HSTS_HEADER_NAME)
|
|
137
115
|
subject.set_hsts_header({:include_subdomains => true})
|
|
138
116
|
end
|
|
139
117
|
|
|
140
118
|
it "does not set the CSP header if disabled" do
|
|
141
119
|
stub_user_agent(USER_AGENTS[:chrome])
|
|
142
|
-
should_not_assign_header(
|
|
143
|
-
subject.set_csp_header(
|
|
120
|
+
should_not_assign_header(HEADER_NAME)
|
|
121
|
+
subject.set_csp_header(false)
|
|
144
122
|
end
|
|
145
123
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
124
|
+
it "saves the options to the env when using script hashes" do
|
|
125
|
+
opts = {
|
|
126
|
+
:default_src => 'self',
|
|
127
|
+
:script_hash_middleware => true
|
|
128
|
+
}
|
|
129
|
+
stub_user_agent(USER_AGENTS[:chrome])
|
|
130
|
+
|
|
131
|
+
expect(SecureHeaders::ContentSecurityPolicy).to receive(:add_to_env)
|
|
132
|
+
subject.set_csp_header(opts)
|
|
152
133
|
end
|
|
153
134
|
|
|
154
135
|
context "when disabled by configuration settings" do
|
|
@@ -159,8 +140,10 @@ describe SecureHeaders do
|
|
|
159
140
|
config.x_content_type_options = false
|
|
160
141
|
config.x_xss_protection = false
|
|
161
142
|
config.csp = false
|
|
143
|
+
config.x_download_options = false
|
|
144
|
+
config.x_permitted_cross_domain_policies = false
|
|
162
145
|
end
|
|
163
|
-
subject.
|
|
146
|
+
expect(subject).not_to receive(:set_header)
|
|
164
147
|
set_security_headers(subject)
|
|
165
148
|
reset_config
|
|
166
149
|
end
|
|
@@ -179,6 +162,18 @@ describe SecureHeaders do
|
|
|
179
162
|
end
|
|
180
163
|
end
|
|
181
164
|
|
|
165
|
+
describe "#set_x_download_options_header" do
|
|
166
|
+
it "sets the X-Download-Options header" do
|
|
167
|
+
should_assign_header(XDO_HEADER_NAME, SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
|
|
168
|
+
subject.set_x_download_options_header
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "allows a custom X-Download-Options header" do
|
|
172
|
+
should_assign_header(XDO_HEADER_NAME, "noopen")
|
|
173
|
+
subject.set_x_download_options_header(:value => 'noopen')
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
182
177
|
describe "#set_strict_transport_security" do
|
|
183
178
|
it "sets the Strict-Transport-Security header" do
|
|
184
179
|
should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
@@ -194,6 +189,11 @@ describe SecureHeaders do
|
|
|
194
189
|
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
|
|
195
190
|
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
|
|
196
191
|
end
|
|
192
|
+
|
|
193
|
+
it "allows you to specify preload" do
|
|
194
|
+
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")
|
|
195
|
+
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true)
|
|
196
|
+
end
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
describe "#set_x_xss_protection" do
|
|
@@ -214,7 +214,7 @@ describe SecureHeaders do
|
|
|
214
214
|
end
|
|
215
215
|
|
|
216
216
|
describe "#set_x_content_type_options" do
|
|
217
|
-
|
|
217
|
+
USER_AGENTS.each do |useragent|
|
|
218
218
|
context "when using #{useragent}" do
|
|
219
219
|
before(:each) do
|
|
220
220
|
stub_user_agent(USER_AGENTS[useragent])
|
|
@@ -237,7 +237,7 @@ describe SecureHeaders do
|
|
|
237
237
|
context "when using Firefox" do
|
|
238
238
|
it "sets CSP headers" do
|
|
239
239
|
stub_user_agent(USER_AGENTS[:firefox])
|
|
240
|
-
should_assign_header(
|
|
240
|
+
should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
|
241
241
|
subject.set_csp_header
|
|
242
242
|
end
|
|
243
243
|
end
|
|
@@ -245,7 +245,7 @@ describe SecureHeaders do
|
|
|
245
245
|
context "when using Chrome" do
|
|
246
246
|
it "sets default CSP header" do
|
|
247
247
|
stub_user_agent(USER_AGENTS[:chrome])
|
|
248
|
-
should_assign_header(
|
|
248
|
+
should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
|
249
249
|
subject.set_csp_header
|
|
250
250
|
end
|
|
251
251
|
end
|
|
@@ -253,36 +253,21 @@ describe SecureHeaders do
|
|
|
253
253
|
context "when using a browser besides chrome/firefox" do
|
|
254
254
|
it "sets the CSP header" do
|
|
255
255
|
stub_user_agent(USER_AGENTS[:opera])
|
|
256
|
-
should_assign_header(
|
|
256
|
+
should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
|
257
257
|
subject.set_csp_header
|
|
258
258
|
end
|
|
259
259
|
end
|
|
260
|
+
end
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
:default_src => 'self',
|
|
267
|
-
:script_src => 'https://mycdn.example.com',
|
|
268
|
-
:experimental => {
|
|
269
|
-
:script_src => 'self',
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
it "does not set the header in enforce mode if experimental is supplied, but enforce is disabled" do
|
|
275
|
-
opts = @opts.merge(:enforce => false)
|
|
276
|
-
should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
|
|
277
|
-
should_not_assign_header(WEBKIT_CSP_HEADER_NAME)
|
|
278
|
-
subject.set_csp_header(opts)
|
|
279
|
-
end
|
|
262
|
+
describe "#set_x_permitted_cross_domain_policies_header" do
|
|
263
|
+
it "sets the X-Permitted-Cross-Domain-Policies header" do
|
|
264
|
+
should_assign_header(XPCDP_HEADER_NAME, SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
|
|
265
|
+
subject.set_x_permitted_cross_domain_policies_header
|
|
266
|
+
end
|
|
280
267
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
subject.set_csp_header(@opts)
|
|
285
|
-
end
|
|
268
|
+
it "allows a custom X-Permitted-Cross-Domain-Policies header" do
|
|
269
|
+
should_assign_header(XPCDP_HEADER_NAME, "master-only")
|
|
270
|
+
subject.set_x_permitted_cross_domain_policies_header(:value => 'master-only')
|
|
286
271
|
end
|
|
287
272
|
end
|
|
288
273
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
|
-
require '
|
|
2
|
+
require 'rspec'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
require 'simplecov'
|
|
6
|
-
SimpleCov.start do
|
|
7
|
-
add_filter "spec"
|
|
8
|
-
end
|
|
9
|
-
end
|
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
require 'rspec'
|
|
6
|
+
if defined?(Coveralls)
|
|
7
|
+
Coveralls.wear!
|
|
14
8
|
end
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
include ::SecureHeaders::StrictTransportSecurity::Constants
|
|
11
|
+
include ::SecureHeaders::ContentSecurityPolicy::Constants
|
|
12
|
+
include ::SecureHeaders::XFrameOptions::Constants
|
|
13
|
+
include ::SecureHeaders::XXssProtection::Constants
|
|
14
|
+
include ::SecureHeaders::XContentTypeOptions::Constants
|
|
15
|
+
include ::SecureHeaders::XDownloadOptions::Constants
|
|
16
|
+
include ::SecureHeaders::XPermittedCrossDomainPolicies::Constants
|
|
17
|
+
|
|
18
|
+
USER_AGENTS = {
|
|
19
|
+
:firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
|
|
20
|
+
: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',
|
|
21
|
+
:ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
|
|
22
|
+
:opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
|
|
23
|
+
: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",
|
|
24
|
+
: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",
|
|
25
|
+
: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",
|
|
26
|
+
: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",
|
|
27
|
+
: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"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def should_assign_header name, value
|
|
31
|
+
expect(response.headers).to receive(:[]=).with(name, value)
|
|
24
32
|
end
|
|
25
33
|
|
|
34
|
+
def should_not_assign_header name
|
|
35
|
+
expect(response.headers).not_to receive(:[]=).with(name, anything)
|
|
36
|
+
end
|