secure_headers 1.2.0 → 2.0.2
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/.travis.yml +7 -4
- data/Gemfile +6 -9
- data/README.md +185 -105
- 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 +59 -16
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +11 -1
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -4
- 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 +11 -1
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +10 -0
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -17
- 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 -124
- 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 +65 -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 +119 -204
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +1 -0
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +32 -0
- 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 +2 -1
- data/spec/lib/secure_headers_spec.rb +61 -66
- data/spec/spec_helper.rb +29 -17
- metadata +55 -47
- checksums.yaml +0 -15
- data/Guardfile +0 -6
- data/HISTORY.md +0 -131
- 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
|
@@ -4,12 +4,11 @@ module SecureHeaders
|
|
|
4
4
|
describe ContentSecurityPolicy do
|
|
5
5
|
let(:default_opts) do
|
|
6
6
|
{
|
|
7
|
-
:disable_chrome_extension => true,
|
|
8
7
|
:disable_fill_missing => true,
|
|
9
|
-
:default_src => 'https
|
|
8
|
+
:default_src => 'https:',
|
|
10
9
|
:report_uri => '/csp_report',
|
|
11
|
-
:script_src => 'inline eval https
|
|
12
|
-
:style_src => "inline https
|
|
10
|
+
:script_src => 'inline eval https: data:',
|
|
11
|
+
:style_src => "inline https: about:"
|
|
13
12
|
}
|
|
14
13
|
end
|
|
15
14
|
|
|
@@ -30,44 +29,41 @@ module SecureHeaders
|
|
|
30
29
|
|
|
31
30
|
describe "#name" do
|
|
32
31
|
context "when supplying options to override request" do
|
|
33
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => IE).name).to eq(
|
|
34
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name).to eq(
|
|
35
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name).to eq(
|
|
36
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME).name).to eq(
|
|
37
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name).to eq(
|
|
32
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => IE).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
33
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
34
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
35
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
36
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
context "when in report-only mode" do
|
|
41
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name).to eq(
|
|
42
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name).to eq(
|
|
43
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name).to eq(
|
|
44
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name).to eq(
|
|
45
|
-
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name).to eq(
|
|
40
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
41
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
42
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
43
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
44
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name).to eq(HEADER_NAME + "-Report-Only")}
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
context "when in enforce mode" do
|
|
49
48
|
let(:opts) { default_opts.merge(:enforce => true)}
|
|
50
49
|
|
|
51
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(IE)).name).to eq(
|
|
52
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name).to eq(
|
|
53
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name).to eq(
|
|
54
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name).to eq(
|
|
55
|
-
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name).to eq(
|
|
50
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(IE)).name).to eq(HEADER_NAME)}
|
|
51
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name).to eq(HEADER_NAME)}
|
|
52
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name).to eq(HEADER_NAME)}
|
|
53
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name).to eq(HEADER_NAME)}
|
|
54
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name).to eq(HEADER_NAME)}
|
|
56
55
|
end
|
|
56
|
+
end
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
63
|
-
specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
64
|
-
specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
58
|
+
context "when using hash sources" do
|
|
59
|
+
it "adds hashes and unsafe-inline to the script-src" do
|
|
60
|
+
policy = ContentSecurityPolicy.new(default_opts.merge(:script_hashes => ['sha256-abc123']))
|
|
61
|
+
expect(policy.value).to match /script-src[^;]*'sha256-abc123'/
|
|
65
62
|
end
|
|
66
63
|
end
|
|
67
64
|
|
|
68
65
|
describe "#normalize_csp_options" do
|
|
69
66
|
before(:each) do
|
|
70
|
-
default_opts.delete(:disable_chrome_extension)
|
|
71
67
|
default_opts.delete(:disable_fill_missing)
|
|
72
68
|
default_opts[:script_src] << ' self none'
|
|
73
69
|
@opts = default_opts
|
|
@@ -76,7 +72,19 @@ module SecureHeaders
|
|
|
76
72
|
context "Content-Security-Policy" do
|
|
77
73
|
it "converts the script values to their equivilents" do
|
|
78
74
|
csp = ContentSecurityPolicy.new(@opts, :request => request_for(CHROME))
|
|
79
|
-
expect(csp.value).to include("script-src 'unsafe-inline' 'unsafe-eval' https
|
|
75
|
+
expect(csp.value).to include("script-src 'unsafe-inline' 'unsafe-eval' https: data: 'self' 'none'")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "adds a @enforce and @app_name variables to the report uri" do
|
|
79
|
+
opts = @opts.merge(:tag_report_uri => true, :enforce => true, :app_name => lambda { 'twitter' })
|
|
80
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
81
|
+
expect(csp.value).to include("/csp_report?enforce=true&app_name=twitter")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "does not add an empty @app_name variable to the report uri" do
|
|
85
|
+
opts = @opts.merge(:tag_report_uri => true, :enforce => true)
|
|
86
|
+
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
87
|
+
expect(csp.value).to include("/csp_report?enforce=true")
|
|
80
88
|
end
|
|
81
89
|
|
|
82
90
|
it "accepts procs for report-uris" do
|
|
@@ -86,124 +94,19 @@ module SecureHeaders
|
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
csp = ContentSecurityPolicy.new(opts)
|
|
89
|
-
expect(csp.
|
|
97
|
+
expect(csp.value).to match("report-uri http://lambda/result")
|
|
90
98
|
end
|
|
91
99
|
|
|
92
100
|
it "accepts procs for other fields" do
|
|
93
101
|
opts = {
|
|
94
|
-
:default_src => lambda { "http://lambda/result" }
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
csp = ContentSecurityPolicy.new(opts).value
|
|
98
|
-
expect(csp).to match("default-src http://lambda/result")
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
describe "#same_origin?" do
|
|
104
|
-
let(:origin) {"https://example.com:123"}
|
|
105
|
-
|
|
106
|
-
it "matches when host, scheme, and port match" do
|
|
107
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com"))
|
|
108
|
-
expect(csp.send(:same_origin?)).to be true
|
|
109
|
-
|
|
110
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com:443"))
|
|
111
|
-
expect(csp.send(:same_origin?)).to be true
|
|
112
|
-
|
|
113
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com:123'}, :request => request_for(FIREFOX, "https://example.com:123"))
|
|
114
|
-
expect(csp.send(:same_origin?)).to be true
|
|
115
|
-
|
|
116
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
117
|
-
expect(csp.send(:same_origin?)).to be true
|
|
118
|
-
|
|
119
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com:80'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
120
|
-
expect(csp.send(:same_origin?)).to be true
|
|
121
|
-
|
|
122
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:80"))
|
|
123
|
-
expect(csp.send(:same_origin?)).to be true
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it "does not match port mismatches" do
|
|
127
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:81"))
|
|
128
|
-
expect(csp.send(:same_origin?)).to be false
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
it "does not match host mismatches" do
|
|
132
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://twitter.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
133
|
-
expect(csp.send(:same_origin?)).to be false
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
it "does not match host mismatches because of subdomains" do
|
|
137
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://sub.example.com"))
|
|
138
|
-
expect(csp.send(:same_origin?)).to be false
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
it "does not match scheme mismatches" do
|
|
142
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "ftp://example.com"))
|
|
143
|
-
expect(csp.send(:same_origin?)).to be false
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
it "does not match on substring collisions" do
|
|
147
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://anotherexample.com"))
|
|
148
|
-
expect(csp.send(:same_origin?)).to be false
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
describe "#normalize_reporting_endpoint" do
|
|
153
|
-
let(:opts) {{:report_uri => 'https://example.com/csp', :forward_endpoint => anything}}
|
|
154
|
-
|
|
155
|
-
context "when using firefox" do
|
|
156
|
-
it "updates the report-uri when posting to a different host" do
|
|
157
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
158
|
-
expect(csp.report_uri).to eq(FF_CSP_ENDPOINT)
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
it "doesn't change report-uri if a path supplied" do
|
|
162
|
-
csp = ContentSecurityPolicy.new({:report_uri => "/csp_reports"}, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
163
|
-
expect(csp.report_uri).to eq("/csp_reports")
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
it "forwards if the request_uri is set to a non-matching value" do
|
|
167
|
-
csp = ContentSecurityPolicy.new({:report_uri => "https://another.example.com", :forward_endpoint => '/somewhere'}, :ua => "Firefox", :request_uri => "https://anexample.com")
|
|
168
|
-
expect(csp.report_uri).to eq(FF_CSP_ENDPOINT)
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
it "does not update the URI is the report_uri is on the same origin" do
|
|
173
|
-
opts = {:report_uri => 'https://example.com/csp', :forward_endpoint => 'https://anotherexample.com'}
|
|
174
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://example.com/somewhere"))
|
|
175
|
-
expect(csp.report_uri).to eq('https://example.com/csp')
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
it "does not update the report-uri when using a non-firefox browser" do
|
|
179
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
180
|
-
expect(csp.report_uri).to eq('https://example.com/csp')
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
context "when using a protocol-relative value for report-uri" do
|
|
184
|
-
let(:opts) {
|
|
185
|
-
{
|
|
186
|
-
:default_src => 'self',
|
|
187
|
-
:report_uri => '//example.com/csp'
|
|
102
|
+
:default_src => lambda { "http://lambda/result" },
|
|
103
|
+
:enforce => lambda { true },
|
|
104
|
+
:disable_fill_missing => lambda { true }
|
|
188
105
|
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
it "uses the current protocol" do
|
|
192
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, '/', :ssl => true))
|
|
193
|
-
expect(csp.value).to match(%r{report-uri https://example.com/csp;})
|
|
194
|
-
|
|
195
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
196
|
-
expect(csp.value).to match(%r{report-uri http://example.com/csp;})
|
|
197
|
-
end
|
|
198
106
|
|
|
199
|
-
|
|
200
|
-
csp
|
|
201
|
-
expect(csp.
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
it "uses the pre-configured http protocol" do
|
|
205
|
-
csp = ContentSecurityPolicy.new(opts, :ua => "Firefox", :ssl => false)
|
|
206
|
-
expect(csp.value).to match(%r{report-uri http://example.com/csp;})
|
|
107
|
+
csp = ContentSecurityPolicy.new(opts)
|
|
108
|
+
expect(csp.value).to eq("default-src http://lambda/result; img-src http://lambda/result data:;")
|
|
109
|
+
expect(csp.name).to match("Content-Security-Policy")
|
|
207
110
|
end
|
|
208
111
|
end
|
|
209
112
|
end
|
|
@@ -213,17 +116,29 @@ module SecureHeaders
|
|
|
213
116
|
csp = ContentSecurityPolicy.new({:script_src => 'anything'}, :request => request_for(CHROME))
|
|
214
117
|
expect {
|
|
215
118
|
csp.value
|
|
216
|
-
}.to raise_error(
|
|
119
|
+
}.to raise_error(RuntimeError)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context "CSP level 2 directives" do
|
|
123
|
+
let(:config) { {:default_src => 'self'} }
|
|
124
|
+
::SecureHeaders::ContentSecurityPolicy::Constants::NON_DEFAULT_SOURCES.each do |non_default_source|
|
|
125
|
+
it "supports all level 2 directives" do
|
|
126
|
+
directive_name = ::SecureHeaders::ContentSecurityPolicy.send(:symbol_to_hyphen_case, non_default_source)
|
|
127
|
+
config.merge!({ non_default_source => "value" })
|
|
128
|
+
csp = ContentSecurityPolicy.new(config, :request => request_for(CHROME))
|
|
129
|
+
expect(csp.value).to match(/#{directive_name} value;/)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
217
132
|
end
|
|
218
133
|
|
|
219
134
|
context "auto-whitelists data: uris for img-src" do
|
|
220
135
|
it "sets the value if no img-src specified" do
|
|
221
|
-
csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true
|
|
222
|
-
expect(csp.value).to eq("default-src 'self'; img-src data:;")
|
|
136
|
+
csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
|
|
137
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
223
138
|
end
|
|
224
139
|
|
|
225
140
|
it "appends the value if img-src is specified" do
|
|
226
|
-
csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true
|
|
141
|
+
csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
|
|
227
142
|
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
228
143
|
end
|
|
229
144
|
end
|
|
@@ -231,7 +146,7 @@ module SecureHeaders
|
|
|
231
146
|
it "fills in directives without values with default-src value" do
|
|
232
147
|
options = default_opts.merge(:disable_fill_missing => false)
|
|
233
148
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
234
|
-
value = "default-src https
|
|
149
|
+
value = "default-src https:; connect-src https:; font-src https:; frame-src https:; img-src https: data:; media-src https:; object-src https:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;"
|
|
235
150
|
expect(csp.value).to eq(value)
|
|
236
151
|
end
|
|
237
152
|
|
|
@@ -243,41 +158,39 @@ module SecureHeaders
|
|
|
243
158
|
context "Firefox" do
|
|
244
159
|
it "builds a csp header for firefox" do
|
|
245
160
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
|
|
246
|
-
expect(csp.value).to eq("default-src https
|
|
161
|
+
expect(csp.value).to eq("default-src https:; img-src https: data:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;")
|
|
247
162
|
end
|
|
248
163
|
end
|
|
249
164
|
|
|
250
165
|
context "Chrome" do
|
|
251
166
|
it "builds a csp header for chrome" do
|
|
252
167
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME))
|
|
253
|
-
expect(csp.value).to eq("default-src https
|
|
168
|
+
expect(csp.value).to eq("default-src https:; img-src https: data:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;")
|
|
254
169
|
end
|
|
170
|
+
end
|
|
255
171
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
172
|
+
context "when using a nonce" do
|
|
173
|
+
it "adds a nonce and unsafe-inline to the script-src value" do
|
|
174
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
|
|
175
|
+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
259
176
|
end
|
|
260
|
-
end
|
|
261
177
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
:default_src => 'self',
|
|
267
|
-
:script_src => 'https://*',
|
|
268
|
-
:experimental => {
|
|
269
|
-
:script_src => 'self'
|
|
270
|
-
}
|
|
271
|
-
}}
|
|
178
|
+
it "adds a nonce and unsafe-inline to the style-src value" do
|
|
179
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
|
|
180
|
+
expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
181
|
+
end
|
|
272
182
|
|
|
273
|
-
it "
|
|
274
|
-
header = ContentSecurityPolicy.new(
|
|
275
|
-
|
|
183
|
+
it "adds an identical nonce to the style and script-src directives" do
|
|
184
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce", :script_src => "self nonce"), :request => request_for(CHROME))
|
|
185
|
+
nonce = header.nonce
|
|
186
|
+
value = header.value
|
|
187
|
+
expect(value).to include("style-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
|
|
188
|
+
expect(value).to include("script-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
|
|
276
189
|
end
|
|
277
190
|
|
|
278
|
-
it "
|
|
279
|
-
header = ContentSecurityPolicy.new(
|
|
280
|
-
expect(header.value).
|
|
191
|
+
it "does not add 'unsafe-inline' twice" do
|
|
192
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce inline"), :request => request_for(CHROME))
|
|
193
|
+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline';")
|
|
281
194
|
end
|
|
282
195
|
end
|
|
283
196
|
|
|
@@ -285,15 +198,15 @@ module SecureHeaders
|
|
|
285
198
|
let(:options) {
|
|
286
199
|
default_opts.merge({
|
|
287
200
|
:http_additions => {
|
|
288
|
-
:frame_src => "http
|
|
289
|
-
:img_src => "http
|
|
201
|
+
:frame_src => "http:",
|
|
202
|
+
:img_src => "http:"
|
|
290
203
|
}
|
|
291
204
|
})
|
|
292
205
|
}
|
|
293
206
|
|
|
294
207
|
it "adds directive values for headers on http" do
|
|
295
208
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
296
|
-
expect(csp.value).to eq("default-src https
|
|
209
|
+
expect(csp.value).to eq("default-src https:; frame-src http:; img-src http: data:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;")
|
|
297
210
|
end
|
|
298
211
|
|
|
299
212
|
it "does not add the directive values if requesting https" do
|
|
@@ -305,45 +218,47 @@ module SecureHeaders
|
|
|
305
218
|
csp = ContentSecurityPolicy.new(options, :ua => "Chrome", :ssl => true)
|
|
306
219
|
expect(csp.value).not_to match(/http:/)
|
|
307
220
|
end
|
|
221
|
+
end
|
|
308
222
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
let(:options) {{
|
|
315
|
-
:disable_chrome_extension => true,
|
|
316
|
-
:disable_fill_missing => true,
|
|
317
|
-
:default_src => 'self',
|
|
318
|
-
:script_src => 'https://*',
|
|
319
|
-
:http_additions => {
|
|
320
|
-
:script_src => 'http://*'
|
|
321
|
-
},
|
|
322
|
-
:experimental => {
|
|
323
|
-
:script_src => 'self',
|
|
324
|
-
:http_additions => {
|
|
325
|
-
:script_src => 'https://mycdn.example.com'
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}}
|
|
329
|
-
# for comparison purposes, if not using the experimental header this would produce
|
|
330
|
-
# "allow 'self'; script-src https://*" for https requests
|
|
331
|
-
# and
|
|
332
|
-
# "allow 'self'; script-src https://* http://*" for http requests
|
|
333
|
-
|
|
334
|
-
it "uses the value in the experimental block over SSL" do
|
|
335
|
-
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX, '/', :ssl => true))
|
|
336
|
-
expect(csp.value).to eq("default-src 'self'; img-src data:; script-src 'self';")
|
|
223
|
+
describe "class methods" do
|
|
224
|
+
let(:ua) { CHROME }
|
|
225
|
+
let(:env) do
|
|
226
|
+
double.tap do |env|
|
|
227
|
+
allow(env).to receive(:[]).with('HTTP_USER_AGENT').and_return(ua)
|
|
337
228
|
end
|
|
229
|
+
end
|
|
230
|
+
let(:request) do
|
|
231
|
+
double(
|
|
232
|
+
:ssl? => true,
|
|
233
|
+
:url => 'https://example.com',
|
|
234
|
+
:env => env
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
describe ".add_to_env" do
|
|
239
|
+
let(:controller) { double }
|
|
240
|
+
let(:config) { {:default_src => 'self'} }
|
|
241
|
+
let(:options) { {:controller => controller} }
|
|
338
242
|
|
|
339
|
-
it "
|
|
340
|
-
|
|
341
|
-
|
|
243
|
+
it "adds metadata to env" do
|
|
244
|
+
metadata = {
|
|
245
|
+
:config => config,
|
|
246
|
+
:options => options
|
|
247
|
+
}
|
|
248
|
+
expect(ContentSecurityPolicy).to receive(:options_from_request).and_return(options)
|
|
249
|
+
expect(env).to receive(:[]=).with(ContentSecurityPolicy::ENV_KEY, metadata)
|
|
250
|
+
ContentSecurityPolicy.add_to_env(request, controller, config)
|
|
342
251
|
end
|
|
252
|
+
end
|
|
343
253
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
254
|
+
describe ".options_from_request" do
|
|
255
|
+
it "extracts options from request" do
|
|
256
|
+
options = ContentSecurityPolicy.options_from_request(request)
|
|
257
|
+
expect(options).to eql({
|
|
258
|
+
:ua => ua,
|
|
259
|
+
:ssl => true,
|
|
260
|
+
:request_uri => 'https://example.com'
|
|
261
|
+
})
|
|
347
262
|
end
|
|
348
263
|
end
|
|
349
264
|
end
|
|
@@ -10,6 +10,7 @@ module SecureHeaders
|
|
|
10
10
|
specify { expect(StrictTransportSecurity.new(:max_age => '1234').value).to eq("max-age=1234")}
|
|
11
11
|
specify { expect(StrictTransportSecurity.new(:max_age => 1234).value).to eq("max-age=1234")}
|
|
12
12
|
specify { expect(StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true).value).to eq("max-age=#{HSTS_MAX_AGE}; includeSubdomains")}
|
|
13
|
+
specify { expect(StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true, :preload => true).value).to eq("max-age=#{HSTS_MAX_AGE}; includeSubdomains; preload")}
|
|
13
14
|
|
|
14
15
|
context "with an invalid configuration" do
|
|
15
16
|
context "with a hash argument" do
|
|
@@ -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
|
|
@@ -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
|
|
@@ -4,6 +4,7 @@ module SecureHeaders
|
|
|
4
4
|
specify { expect(XXssProtection.new.value).to eq("1")}
|
|
5
5
|
specify { expect(XXssProtection.new("0").value).to eq("0")}
|
|
6
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
|
|
@@ -50,4 +51,4 @@ module SecureHeaders
|
|
|
50
51
|
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
|
-
end
|
|
54
|
+
end
|