secure_headers 1.1.1 → 2.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -8
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -3
- data/Gemfile +6 -11
- data/README.md +219 -104
- 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 -13
- 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 -129
- data/lib/secure_headers/headers/public_key_pins.rb +95 -0
- 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 +78 -16
- data/lib/tasks/tasks.rake +48 -0
- data/secure_headers.gemspec +4 -4
- 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 -220
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +37 -0
- 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 -12
- 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 +115 -73
- data/spec/spec_helper.rb +30 -18
- metadata +62 -58
- data/.rvmrc +0 -1
- data/Guardfile +0 -10
- data/HISTORY.md +0 -115
- data/app/controllers/content_security_policy_controller.rb +0 -75
- 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/spec/controllers/content_security_policy_controller_spec.rb +0 -90
|
@@ -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,34 +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
|
-
}.
|
|
22
|
+
}.not_to raise_error end
|
|
23
23
|
|
|
24
24
|
it "allows ALLOW-FROM*" do
|
|
25
|
-
|
|
25
|
+
expect {
|
|
26
26
|
XFrameOptions.new("ALLOW-FROM: example.com").value
|
|
27
|
-
}.
|
|
27
|
+
}.not_to raise_error
|
|
28
28
|
end
|
|
29
29
|
it "does not allow garbage" do
|
|
30
|
-
|
|
30
|
+
expect {
|
|
31
31
|
XFrameOptions.new("I like turtles").value
|
|
32
|
-
}.
|
|
32
|
+
}.to raise_error(XFOBuildError)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
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,64 +13,37 @@ describe SecureHeaders do
|
|
|
13
13
|
|
|
14
14
|
before(:each) do
|
|
15
15
|
stub_user_agent(nil)
|
|
16
|
-
headers.
|
|
17
|
-
subject.
|
|
18
|
-
subject.
|
|
19
|
-
end
|
|
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)
|
|
16
|
+
allow(headers).to receive(:[])
|
|
17
|
+
allow(subject).to receive(:response).and_return(response)
|
|
18
|
+
allow(subject).to receive(:request).and_return(request)
|
|
40
19
|
end
|
|
41
20
|
|
|
42
21
|
def stub_user_agent val
|
|
43
|
-
request.
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def options_for header
|
|
47
|
-
ALL_HEADERS.reject{|k,v| k == header}
|
|
22
|
+
allow(request).to receive_message_chain(:env, :[]).and_return(val)
|
|
48
23
|
end
|
|
49
24
|
|
|
50
25
|
def reset_config
|
|
51
26
|
::SecureHeaders::Configuration.configure do |config|
|
|
27
|
+
config.hpkp = nil
|
|
52
28
|
config.hsts = nil
|
|
53
29
|
config.x_frame_options = nil
|
|
54
30
|
config.x_content_type_options = nil
|
|
55
31
|
config.x_xss_protection = nil
|
|
56
32
|
config.csp = nil
|
|
33
|
+
config.x_download_options = nil
|
|
34
|
+
config.x_permitted_cross_domain_policies = nil
|
|
57
35
|
end
|
|
58
36
|
end
|
|
59
37
|
|
|
60
38
|
def set_security_headers(subject)
|
|
61
39
|
subject.set_csp_header
|
|
40
|
+
subject.set_hpkp_header
|
|
62
41
|
subject.set_hsts_header
|
|
63
42
|
subject.set_x_frame_options_header
|
|
64
43
|
subject.set_x_content_type_options_header
|
|
65
44
|
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
|
|
45
|
+
subject.set_x_download_options_header
|
|
46
|
+
subject.set_x_permitted_cross_domain_policies_header
|
|
74
47
|
end
|
|
75
48
|
|
|
76
49
|
describe "#set_header" do
|
|
@@ -86,19 +59,19 @@ describe SecureHeaders do
|
|
|
86
59
|
end
|
|
87
60
|
|
|
88
61
|
describe "#set_security_headers" do
|
|
89
|
-
before(:each) do
|
|
90
|
-
SecureHeaders::ContentSecurityPolicy.stub(:new).and_return(double.as_null_object)
|
|
91
|
-
end
|
|
92
62
|
USER_AGENTS.each do |name, useragent|
|
|
93
63
|
it "sets all default headers for #{name} (smoke test)" do
|
|
94
64
|
stub_user_agent(useragent)
|
|
95
|
-
number_of_headers =
|
|
96
|
-
subject.
|
|
65
|
+
number_of_headers = 7
|
|
66
|
+
expect(subject).to receive(:set_header).exactly(number_of_headers).times # a request for a given header
|
|
97
67
|
subject.set_csp_header
|
|
98
68
|
subject.set_x_frame_options_header
|
|
99
69
|
subject.set_hsts_header
|
|
70
|
+
subject.set_hpkp_header
|
|
100
71
|
subject.set_x_xss_protection_header
|
|
101
72
|
subject.set_x_content_type_options_header
|
|
73
|
+
subject.set_x_download_options_header
|
|
74
|
+
subject.set_x_permitted_cross_domain_policies_header
|
|
102
75
|
end
|
|
103
76
|
end
|
|
104
77
|
|
|
@@ -113,38 +86,73 @@ describe SecureHeaders do
|
|
|
113
86
|
subject.set_x_xss_protection_header(false)
|
|
114
87
|
end
|
|
115
88
|
|
|
89
|
+
it "does not set the X-Download-Options header if disabled" do
|
|
90
|
+
should_not_assign_header(XDO_HEADER_NAME)
|
|
91
|
+
subject.set_x_download_options_header(false)
|
|
92
|
+
end
|
|
93
|
+
|
|
116
94
|
it "does not set the X-Frame-Options header if disabled" do
|
|
117
95
|
should_not_assign_header(XFO_HEADER_NAME)
|
|
118
96
|
subject.set_x_frame_options_header(false)
|
|
119
97
|
end
|
|
120
98
|
|
|
99
|
+
it "does not set the X-Permitted-Cross-Domain-Policies header if disabled" do
|
|
100
|
+
should_not_assign_header(XPCDP_HEADER_NAME)
|
|
101
|
+
subject.set_x_permitted_cross_domain_policies_header(false)
|
|
102
|
+
end
|
|
103
|
+
|
|
121
104
|
it "does not set the HSTS header if disabled" do
|
|
122
105
|
should_not_assign_header(HSTS_HEADER_NAME)
|
|
123
106
|
subject.set_hsts_header(false)
|
|
124
107
|
end
|
|
125
108
|
|
|
126
109
|
it "does not set the HSTS header if request is over HTTP" do
|
|
127
|
-
subject.
|
|
110
|
+
allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
|
|
128
111
|
should_not_assign_header(HSTS_HEADER_NAME)
|
|
129
112
|
subject.set_hsts_header({:include_subdomains => true})
|
|
130
113
|
end
|
|
131
114
|
|
|
115
|
+
it "does not set the HPKP header if disabled" do
|
|
116
|
+
should_not_assign_header(HPKP_HEADER_NAME)
|
|
117
|
+
subject.set_hpkp_header
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "does not set the HPKP header if request is over HTTP" do
|
|
121
|
+
allow(subject).to receive_message_chain(:request, :ssl?).and_return(false)
|
|
122
|
+
should_not_assign_header(HPKP_HEADER_NAME)
|
|
123
|
+
subject.set_hpkp_header(:max_age => 1234)
|
|
124
|
+
end
|
|
125
|
+
|
|
132
126
|
it "does not set the CSP header if disabled" do
|
|
133
127
|
stub_user_agent(USER_AGENTS[:chrome])
|
|
134
|
-
should_not_assign_header(
|
|
135
|
-
subject.set_csp_header(
|
|
128
|
+
should_not_assign_header(HEADER_NAME)
|
|
129
|
+
subject.set_csp_header(false)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "saves the options to the env when using script hashes" do
|
|
133
|
+
opts = {
|
|
134
|
+
:default_src => 'self',
|
|
135
|
+
:script_hash_middleware => true
|
|
136
|
+
}
|
|
137
|
+
stub_user_agent(USER_AGENTS[:chrome])
|
|
138
|
+
|
|
139
|
+
expect(SecureHeaders::ContentSecurityPolicy).to receive(:add_to_env)
|
|
140
|
+
subject.set_csp_header(opts)
|
|
136
141
|
end
|
|
137
142
|
|
|
138
143
|
context "when disabled by configuration settings" do
|
|
139
144
|
it "does not set any headers when disabled" do
|
|
140
145
|
::SecureHeaders::Configuration.configure do |config|
|
|
141
146
|
config.hsts = false
|
|
147
|
+
config.hpkp = false
|
|
142
148
|
config.x_frame_options = false
|
|
143
149
|
config.x_content_type_options = false
|
|
144
150
|
config.x_xss_protection = false
|
|
145
151
|
config.csp = false
|
|
152
|
+
config.x_download_options = false
|
|
153
|
+
config.x_permitted_cross_domain_policies = false
|
|
146
154
|
end
|
|
147
|
-
subject.
|
|
155
|
+
expect(subject).not_to receive(:set_header)
|
|
148
156
|
set_security_headers(subject)
|
|
149
157
|
reset_config
|
|
150
158
|
end
|
|
@@ -163,6 +171,18 @@ describe SecureHeaders do
|
|
|
163
171
|
end
|
|
164
172
|
end
|
|
165
173
|
|
|
174
|
+
describe "#set_x_download_options_header" do
|
|
175
|
+
it "sets the X-Download-Options header" do
|
|
176
|
+
should_assign_header(XDO_HEADER_NAME, SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
|
|
177
|
+
subject.set_x_download_options_header
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it "allows a custom X-Download-Options header" do
|
|
181
|
+
should_assign_header(XDO_HEADER_NAME, "noopen")
|
|
182
|
+
subject.set_x_download_options_header(:value => 'noopen')
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
166
186
|
describe "#set_strict_transport_security" do
|
|
167
187
|
it "sets the Strict-Transport-Security header" do
|
|
168
188
|
should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
@@ -178,6 +198,43 @@ describe SecureHeaders do
|
|
|
178
198
|
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
|
|
179
199
|
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
|
|
180
200
|
end
|
|
201
|
+
|
|
202
|
+
it "allows you to specify preload" do
|
|
203
|
+
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")
|
|
204
|
+
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
describe "#set_public_key_pins" do
|
|
209
|
+
it "sets the Public-Key-Pins header" do
|
|
210
|
+
should_assign_header(HPKP_HEADER_NAME + "-Report-Only", "max-age=1234")
|
|
211
|
+
subject.set_hpkp_header(:max_age => 1234)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "allows you to enforce public key pinning" do
|
|
215
|
+
should_assign_header(HPKP_HEADER_NAME, "max-age=1234")
|
|
216
|
+
subject.set_hpkp_header(:max_age => 1234, :enforce => true)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "allows you to specific a custom max-age value" do
|
|
220
|
+
should_assign_header(HPKP_HEADER_NAME + "-Report-Only", 'max-age=1234')
|
|
221
|
+
subject.set_hpkp_header(:max_age => 1234)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "allows you to specify includeSubdomains" do
|
|
225
|
+
should_assign_header(HPKP_HEADER_NAME, "max-age=1234; includeSubDomains")
|
|
226
|
+
subject.set_hpkp_header(:max_age => 1234, :include_subdomains => true, :enforce => true)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it "allows you to specify a report-uri" do
|
|
230
|
+
should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com\"")
|
|
231
|
+
subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :enforce => true)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it "allows you to specify a report-uri with app_name" do
|
|
235
|
+
should_assign_header(HPKP_HEADER_NAME, "max-age=1234; report-uri=\"https://foobar.com?enforce=true&app_name=my_app\"")
|
|
236
|
+
subject.set_hpkp_header(:max_age => 1234, :report_uri => "https://foobar.com", :app_name => "my_app", :tag_report_uri => true, :enforce => true)
|
|
237
|
+
end
|
|
181
238
|
end
|
|
182
239
|
|
|
183
240
|
describe "#set_x_xss_protection" do
|
|
@@ -221,7 +278,7 @@ describe SecureHeaders do
|
|
|
221
278
|
context "when using Firefox" do
|
|
222
279
|
it "sets CSP headers" do
|
|
223
280
|
stub_user_agent(USER_AGENTS[:firefox])
|
|
224
|
-
should_assign_header(
|
|
281
|
+
should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
|
225
282
|
subject.set_csp_header
|
|
226
283
|
end
|
|
227
284
|
end
|
|
@@ -229,7 +286,7 @@ describe SecureHeaders do
|
|
|
229
286
|
context "when using Chrome" do
|
|
230
287
|
it "sets default CSP header" do
|
|
231
288
|
stub_user_agent(USER_AGENTS[:chrome])
|
|
232
|
-
should_assign_header(
|
|
289
|
+
should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
|
233
290
|
subject.set_csp_header
|
|
234
291
|
end
|
|
235
292
|
end
|
|
@@ -237,36 +294,21 @@ describe SecureHeaders do
|
|
|
237
294
|
context "when using a browser besides chrome/firefox" do
|
|
238
295
|
it "sets the CSP header" do
|
|
239
296
|
stub_user_agent(USER_AGENTS[:opera])
|
|
240
|
-
should_assign_header(
|
|
297
|
+
should_assign_header(HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
|
241
298
|
subject.set_csp_header
|
|
242
299
|
end
|
|
243
300
|
end
|
|
301
|
+
end
|
|
244
302
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
:default_src => 'self',
|
|
251
|
-
:script_src => 'https://mycdn.example.com',
|
|
252
|
-
:experimental => {
|
|
253
|
-
:script_src => 'self',
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
it "does not set the header in enforce mode if experimental is supplied, but enforce is disabled" do
|
|
259
|
-
opts = @opts.merge(:enforce => false)
|
|
260
|
-
should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", anything)
|
|
261
|
-
should_not_assign_header(STANDARD_HEADER_NAME)
|
|
262
|
-
subject.set_csp_header(opts)
|
|
263
|
-
end
|
|
303
|
+
describe "#set_x_permitted_cross_domain_policies_header" do
|
|
304
|
+
it "sets the X-Permitted-Cross-Domain-Policies header" do
|
|
305
|
+
should_assign_header(XPCDP_HEADER_NAME, SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
|
|
306
|
+
subject.set_x_permitted_cross_domain_policies_header
|
|
307
|
+
end
|
|
264
308
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
subject.set_csp_header(@opts)
|
|
269
|
-
end
|
|
309
|
+
it "allows a custom X-Permitted-Cross-Domain-Policies header" do
|
|
310
|
+
should_assign_header(XPCDP_HEADER_NAME, "master-only")
|
|
311
|
+
subject.set_x_permitted_cross_domain_policies_header(:value => 'master-only')
|
|
270
312
|
end
|
|
271
313
|
end
|
|
272
314
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
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::PublicKeyPins::Constants
|
|
11
|
+
include ::SecureHeaders::StrictTransportSecurity::Constants
|
|
12
|
+
include ::SecureHeaders::ContentSecurityPolicy::Constants
|
|
13
|
+
include ::SecureHeaders::XFrameOptions::Constants
|
|
14
|
+
include ::SecureHeaders::XXssProtection::Constants
|
|
15
|
+
include ::SecureHeaders::XContentTypeOptions::Constants
|
|
16
|
+
include ::SecureHeaders::XDownloadOptions::Constants
|
|
17
|
+
include ::SecureHeaders::XPermittedCrossDomainPolicies::Constants
|
|
18
|
+
|
|
19
|
+
USER_AGENTS = {
|
|
20
|
+
:firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
|
|
21
|
+
: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',
|
|
22
|
+
:ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
|
|
23
|
+
:opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
|
|
24
|
+
: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",
|
|
25
|
+
: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",
|
|
26
|
+
: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",
|
|
27
|
+
: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",
|
|
28
|
+
: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"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def should_assign_header name, value
|
|
32
|
+
expect(response.headers).to receive(:[]=).with(name, value)
|
|
24
33
|
end
|
|
25
34
|
|
|
35
|
+
def should_not_assign_header name
|
|
36
|
+
expect(response.headers).not_to receive(:[]=).with(name, anything)
|
|
37
|
+
end
|