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,16 +1,14 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
-
require 'brwsr'
|
|
3
2
|
|
|
4
3
|
module SecureHeaders
|
|
5
4
|
describe ContentSecurityPolicy do
|
|
6
5
|
let(:default_opts) do
|
|
7
6
|
{
|
|
8
|
-
:disable_chrome_extension => true,
|
|
9
7
|
:disable_fill_missing => true,
|
|
10
|
-
:default_src => 'https
|
|
8
|
+
:default_src => 'https:',
|
|
11
9
|
:report_uri => '/csp_report',
|
|
12
|
-
:script_src => 'inline eval https
|
|
13
|
-
:style_src => "inline https
|
|
10
|
+
:script_src => 'inline eval https: data:',
|
|
11
|
+
:style_src => "inline https: about:"
|
|
14
12
|
}
|
|
15
13
|
end
|
|
16
14
|
|
|
@@ -31,196 +29,84 @@ module SecureHeaders
|
|
|
31
29
|
|
|
32
30
|
describe "#name" do
|
|
33
31
|
context "when supplying options to override request" do
|
|
34
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => IE).name.
|
|
35
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.
|
|
36
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name.
|
|
37
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.
|
|
38
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name.
|
|
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")}
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
context "when in report-only mode" do
|
|
42
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name.
|
|
43
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.
|
|
44
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name.
|
|
45
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.
|
|
46
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name.
|
|
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")}
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
context "when in enforce mode" do
|
|
50
48
|
let(:opts) { default_opts.merge(:enforce => true)}
|
|
51
49
|
|
|
52
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(IE)).name.
|
|
53
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.
|
|
54
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name.
|
|
55
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.
|
|
56
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name.
|
|
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)}
|
|
57
55
|
end
|
|
56
|
+
end
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
|
64
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
|
|
65
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name.should == 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'/
|
|
66
62
|
end
|
|
67
63
|
end
|
|
68
64
|
|
|
69
65
|
describe "#normalize_csp_options" do
|
|
70
66
|
before(:each) do
|
|
71
|
-
default_opts.delete(:disable_chrome_extension)
|
|
72
67
|
default_opts.delete(:disable_fill_missing)
|
|
73
68
|
default_opts[:script_src] << ' self none'
|
|
74
69
|
@opts = default_opts
|
|
75
70
|
end
|
|
76
71
|
|
|
77
|
-
context "
|
|
78
|
-
it "converts the script values to their equivilents" do
|
|
79
|
-
csp = ContentSecurityPolicy.new(@opts, :request => request_for(FIREFOX))
|
|
80
|
-
csp.value.should include("script-src https://* data: 'self' 'none'")
|
|
81
|
-
csp.value.should include('options inline-script eval-script')
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
context "X-Webkit-CSP" do
|
|
72
|
+
context "Content-Security-Policy" do
|
|
86
73
|
it "converts the script values to their equivilents" do
|
|
87
74
|
csp = ContentSecurityPolicy.new(@opts, :request => request_for(CHROME))
|
|
88
|
-
csp.value.
|
|
75
|
+
expect(csp.value).to include("script-src 'unsafe-inline' 'unsafe-eval' https: data: 'self' 'none'")
|
|
89
76
|
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
describe "#build_impl_specific_directives" do
|
|
94
|
-
context "X-Content-Security-Policy" do
|
|
95
|
-
it "moves script-src inline and eval values to the options directive" do
|
|
96
|
-
opts = {
|
|
97
|
-
:default_src => 'https://*',
|
|
98
|
-
:script_src => "inline eval https://*"
|
|
99
|
-
}
|
|
100
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
101
|
-
browser_specific = csp.send :build_impl_specific_directives
|
|
102
|
-
browser_specific.should include('options inline-script eval-script;')
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
it "does not move values from style-src into options" do
|
|
106
|
-
opts = {
|
|
107
|
-
:default_src => 'https://*',
|
|
108
|
-
:style_src => "inline eval https://*"
|
|
109
|
-
}
|
|
110
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
111
|
-
browser_specific = csp.send :build_impl_specific_directives
|
|
112
|
-
browser_specific.should_not include('inline-script')
|
|
113
|
-
browser_specific.should_not include('eval-script')
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
describe "#same_origin?" do
|
|
119
|
-
let(:origin) {"https://example.com:123"}
|
|
120
|
-
|
|
121
|
-
it "matches when host, scheme, and port match" do
|
|
122
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com"))
|
|
123
|
-
csp.send(:same_origin?).should be_true
|
|
124
|
-
|
|
125
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com:443"))
|
|
126
|
-
csp.send(:same_origin?).should be_true
|
|
127
|
-
|
|
128
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com:123'}, :request => request_for(FIREFOX, "https://example.com:123"))
|
|
129
|
-
csp.send(:same_origin?).should be_true
|
|
130
|
-
|
|
131
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
132
|
-
csp.send(:same_origin?).should be_true
|
|
133
|
-
|
|
134
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com:80'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
135
|
-
csp.send(:same_origin?).should be_true
|
|
136
|
-
|
|
137
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:80"))
|
|
138
|
-
csp.send(:same_origin?).should be_true
|
|
139
|
-
end
|
|
140
77
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
it "does not match host mismatches" do
|
|
147
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://twitter.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
148
|
-
csp.send(:same_origin?).should be_false
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
it "does not match host mismatches because of subdomains" do
|
|
152
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://sub.example.com"))
|
|
153
|
-
csp.send(:same_origin?).should be_false
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
it "does not match scheme mismatches" do
|
|
157
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "ftp://example.com"))
|
|
158
|
-
csp.send(:same_origin?).should be_false
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
it "does not match on substring collisions" do
|
|
162
|
-
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://anotherexample.com"))
|
|
163
|
-
csp.send(:same_origin?).should be_false
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
describe "#normalize_reporting_endpoint" do
|
|
170
|
-
let(:opts) {{:report_uri => 'https://example.com/csp', :forward_endpoint => anything}}
|
|
171
|
-
|
|
172
|
-
context "when using firefox" do
|
|
173
|
-
it "updates the report-uri when posting to a different host" do
|
|
174
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
175
|
-
csp.report_uri.should == FF_CSP_ENDPOINT
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
it "doesn't change report-uri if a path supplied" do
|
|
179
|
-
csp = ContentSecurityPolicy.new({:report_uri => "/csp_reports"}, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
180
|
-
csp.report_uri.should == "/csp_reports"
|
|
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")
|
|
181
82
|
end
|
|
182
83
|
|
|
183
|
-
it "
|
|
184
|
-
|
|
185
|
-
csp.
|
|
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")
|
|
186
88
|
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
it "does not update the URI is the report_uri is on the same origin" do
|
|
190
|
-
opts = {:report_uri => 'https://example.com/csp', :forward_endpoint => 'https://anotherexample.com'}
|
|
191
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://example.com/somewhere"))
|
|
192
|
-
csp.report_uri.should == 'https://example.com/csp'
|
|
193
|
-
end
|
|
194
89
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
csp.report_uri.should == 'https://example.com/csp'
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
context "when using a protocol-relative value for report-uri" do
|
|
201
|
-
let(:opts) {
|
|
202
|
-
{
|
|
90
|
+
it "accepts procs for report-uris" do
|
|
91
|
+
opts = {
|
|
203
92
|
:default_src => 'self',
|
|
204
|
-
:report_uri =>
|
|
93
|
+
:report_uri => lambda { "http://lambda/result" }
|
|
205
94
|
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
it "uses the current protocol" do
|
|
209
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, '/', :ssl => true))
|
|
210
|
-
csp.value.should =~ %r{report-uri https://example.com/csp;}
|
|
211
95
|
|
|
212
|
-
csp = ContentSecurityPolicy.new(opts
|
|
213
|
-
csp.value.
|
|
96
|
+
csp = ContentSecurityPolicy.new(opts)
|
|
97
|
+
expect(csp.value).to match("report-uri http://lambda/result")
|
|
214
98
|
end
|
|
215
99
|
|
|
216
|
-
it "
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
100
|
+
it "accepts procs for other fields" do
|
|
101
|
+
opts = {
|
|
102
|
+
:default_src => lambda { "http://lambda/result" },
|
|
103
|
+
:enforce => lambda { true },
|
|
104
|
+
:disable_fill_missing => lambda { true }
|
|
105
|
+
}
|
|
220
106
|
|
|
221
|
-
|
|
222
|
-
csp
|
|
223
|
-
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")
|
|
224
110
|
end
|
|
225
111
|
end
|
|
226
112
|
end
|
|
@@ -228,111 +114,83 @@ module SecureHeaders
|
|
|
228
114
|
describe "#value" do
|
|
229
115
|
it "raises an exception when default-src is missing" do
|
|
230
116
|
csp = ContentSecurityPolicy.new({:script_src => 'anything'}, :request => request_for(CHROME))
|
|
231
|
-
|
|
117
|
+
expect {
|
|
232
118
|
csp.value
|
|
233
|
-
}.
|
|
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
|
|
234
132
|
end
|
|
235
133
|
|
|
236
134
|
context "auto-whitelists data: uris for img-src" do
|
|
237
135
|
it "sets the value if no img-src specified" do
|
|
238
|
-
csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true
|
|
239
|
-
csp.value.
|
|
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:;")
|
|
240
138
|
end
|
|
241
139
|
|
|
242
140
|
it "appends the value if img-src is specified" do
|
|
243
|
-
csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true
|
|
244
|
-
csp.value.
|
|
141
|
+
csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
|
|
142
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
245
143
|
end
|
|
246
144
|
end
|
|
247
145
|
|
|
248
146
|
it "fills in directives without values with default-src value" do
|
|
249
147
|
options = default_opts.merge(:disable_fill_missing => false)
|
|
250
148
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
251
|
-
value = "default-src https
|
|
252
|
-
csp.value.
|
|
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;"
|
|
150
|
+
expect(csp.value).to eq(value)
|
|
253
151
|
end
|
|
254
152
|
|
|
255
|
-
it "sends the
|
|
153
|
+
it "sends the standard csp header if an unknown browser is supplied" do
|
|
256
154
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(IE))
|
|
257
|
-
csp.value.
|
|
155
|
+
expect(csp.value).to match "default-src"
|
|
258
156
|
end
|
|
259
157
|
|
|
260
158
|
context "Firefox" do
|
|
261
159
|
it "builds a csp header for firefox" do
|
|
262
160
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
|
|
263
|
-
csp.value.
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
it "does not append chrome-extension to directives" do
|
|
267
|
-
csp = ContentSecurityPolicy.new(default_opts.merge(:disable_chrome_extension => false), :request => request_for(FIREFOX))
|
|
268
|
-
csp.value.should_not match "chrome-extension:"
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
it "copies connect-src values to xhr_src values" do
|
|
272
|
-
opts = {
|
|
273
|
-
:default_src => 'http://twitter.com',
|
|
274
|
-
:connect_src => 'self http://*.localhost.com:*',
|
|
275
|
-
:disable_chrome_extension => true,
|
|
276
|
-
:disable_fill_missing => true
|
|
277
|
-
}
|
|
278
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
279
|
-
csp.value.should =~ /xhr-src 'self' http:/
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
context "Firefox >= 23" do
|
|
283
|
-
it "builds a csp header for firefox" do
|
|
284
|
-
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23))
|
|
285
|
-
csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
|
|
286
|
-
end
|
|
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;")
|
|
287
162
|
end
|
|
288
163
|
end
|
|
289
164
|
|
|
290
165
|
context "Chrome" do
|
|
291
166
|
it "builds a csp header for chrome" do
|
|
292
167
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME))
|
|
293
|
-
csp.value.
|
|
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;")
|
|
294
169
|
end
|
|
170
|
+
end
|
|
295
171
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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'")
|
|
299
176
|
end
|
|
300
177
|
|
|
301
|
-
it "
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
:report_uri => '/csp_report',
|
|
305
|
-
:script_src => 'inline eval https://* data:',
|
|
306
|
-
:style_src => "inline https://* chrome-extension: about:"
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
310
|
-
|
|
311
|
-
# ignore the report-uri directive
|
|
312
|
-
csp.value.split(';')[0...-1].each{|directive| directive.should =~ /chrome-extension:/}
|
|
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'")
|
|
313
181
|
end
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
context "when supplying a experimental values" do
|
|
317
|
-
let(:options) {{
|
|
318
|
-
:disable_chrome_extension => true,
|
|
319
|
-
:disable_fill_missing => true,
|
|
320
|
-
:default_src => 'self',
|
|
321
|
-
:script_src => 'https://*',
|
|
322
|
-
:experimental => {
|
|
323
|
-
:script_src => 'self'
|
|
324
|
-
}
|
|
325
|
-
}}
|
|
326
182
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
header.value
|
|
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'")
|
|
331
189
|
end
|
|
332
190
|
|
|
333
|
-
it "
|
|
334
|
-
header = ContentSecurityPolicy.new(
|
|
335
|
-
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';")
|
|
336
194
|
end
|
|
337
195
|
end
|
|
338
196
|
|
|
@@ -340,98 +198,68 @@ module SecureHeaders
|
|
|
340
198
|
let(:options) {
|
|
341
199
|
default_opts.merge({
|
|
342
200
|
:http_additions => {
|
|
343
|
-
:frame_src => "http
|
|
344
|
-
:img_src => "http
|
|
201
|
+
:frame_src => "http:",
|
|
202
|
+
:img_src => "http:"
|
|
345
203
|
}
|
|
346
204
|
})
|
|
347
205
|
}
|
|
348
206
|
|
|
349
207
|
it "adds directive values for headers on http" do
|
|
350
208
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
351
|
-
csp.value.
|
|
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;")
|
|
352
210
|
end
|
|
353
211
|
|
|
354
212
|
it "does not add the directive values if requesting https" do
|
|
355
213
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME, '/', :ssl => true))
|
|
356
|
-
csp.value.
|
|
214
|
+
expect(csp.value).not_to match(/http:/)
|
|
357
215
|
end
|
|
358
216
|
|
|
359
217
|
it "does not add the directive values if requesting https" do
|
|
360
218
|
csp = ContentSecurityPolicy.new(options, :ua => "Chrome", :ssl => true)
|
|
361
|
-
csp.value.
|
|
219
|
+
expect(csp.value).not_to match(/http:/)
|
|
362
220
|
end
|
|
221
|
+
end
|
|
363
222
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
let(:options) {{
|
|
370
|
-
:disable_chrome_extension => true,
|
|
371
|
-
:disable_fill_missing => true,
|
|
372
|
-
:default_src => 'self',
|
|
373
|
-
:script_src => 'https://*',
|
|
374
|
-
:http_additions => {
|
|
375
|
-
:script_src => 'http://*'
|
|
376
|
-
},
|
|
377
|
-
:experimental => {
|
|
378
|
-
:script_src => 'self',
|
|
379
|
-
:http_additions => {
|
|
380
|
-
:script_src => 'https://mycdn.example.com'
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}}
|
|
384
|
-
# for comparison purposes, if not using the experimental header this would produce
|
|
385
|
-
# "allow 'self'; script-src https://*" for https requests
|
|
386
|
-
# and
|
|
387
|
-
# "allow 'self; script-src https://* http://*" for http requests
|
|
388
|
-
|
|
389
|
-
it "uses the value in the experimental block over SSL" do
|
|
390
|
-
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX, '/', :ssl => true))
|
|
391
|
-
csp.value.should == "allow 'self'; img-src data:; script-src 'self';"
|
|
392
|
-
end
|
|
393
|
-
|
|
394
|
-
it "detects the :ssl => true option" do
|
|
395
|
-
csp = ContentSecurityPolicy.new(options, :experimental => true, :ua => FIREFOX, :ssl => true)
|
|
396
|
-
csp.value.should == "allow 'self'; img-src data:; script-src 'self';"
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
it "merges the values from experimental/http_additions when not over SSL" do
|
|
400
|
-
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX))
|
|
401
|
-
csp.value.should == "allow 'self'; img-src data:; script-src 'self' https://mycdn.example.com;"
|
|
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)
|
|
402
228
|
end
|
|
403
229
|
end
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
})
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
it "uses the value in the X-Webkit-CSP" do
|
|
414
|
-
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
415
|
-
csp.value.should match "script-nonce random;"
|
|
230
|
+
let(:request) do
|
|
231
|
+
double(
|
|
232
|
+
:ssl? => true,
|
|
233
|
+
:url => 'https://example.com',
|
|
234
|
+
:env => env
|
|
235
|
+
)
|
|
416
236
|
end
|
|
417
237
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
238
|
+
describe ".add_to_env" do
|
|
239
|
+
let(:controller) { double }
|
|
240
|
+
let(:config) { {:default_src => 'self'} }
|
|
241
|
+
let(:options) { {:controller => controller} }
|
|
422
242
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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)
|
|
251
|
+
end
|
|
427
252
|
end
|
|
428
253
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
+
})
|
|
262
|
+
end
|
|
435
263
|
end
|
|
436
264
|
end
|
|
437
265
|
end
|
|
@@ -2,59 +2,60 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module SecureHeaders
|
|
4
4
|
describe StrictTransportSecurity do
|
|
5
|
-
specify{ StrictTransportSecurity.new.name.
|
|
5
|
+
specify{ expect(StrictTransportSecurity.new.name).to eq("Strict-Transport-Security") }
|
|
6
6
|
|
|
7
7
|
describe "#value" do
|
|
8
|
-
specify { StrictTransportSecurity.new.value.
|
|
9
|
-
specify { StrictTransportSecurity.new("max-age=1234").value.
|
|
10
|
-
specify { StrictTransportSecurity.new(:max_age => '1234').value.
|
|
11
|
-
specify { StrictTransportSecurity.new(:max_age => 1234).value.
|
|
12
|
-
specify { StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true).value.
|
|
8
|
+
specify { expect(StrictTransportSecurity.new.value).to eq(StrictTransportSecurity::Constants::DEFAULT_VALUE)}
|
|
9
|
+
specify { expect(StrictTransportSecurity.new("max-age=1234").value).to eq("max-age=1234")}
|
|
10
|
+
specify { expect(StrictTransportSecurity.new(:max_age => '1234').value).to eq("max-age=1234")}
|
|
11
|
+
specify { expect(StrictTransportSecurity.new(:max_age => 1234).value).to eq("max-age=1234")}
|
|
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
|
|
16
17
|
it "should allow string values for max-age" do
|
|
17
|
-
|
|
18
|
+
expect {
|
|
18
19
|
StrictTransportSecurity.new(:max_age => '1234')
|
|
19
|
-
}.
|
|
20
|
+
}.not_to raise_error
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
it "should allow integer values for max-age" do
|
|
23
|
-
|
|
24
|
+
expect {
|
|
24
25
|
StrictTransportSecurity.new(:max_age => 1234)
|
|
25
|
-
}.
|
|
26
|
+
}.not_to raise_error
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
it "raises an exception with an invalid max-age" do
|
|
29
|
-
|
|
30
|
+
expect {
|
|
30
31
|
StrictTransportSecurity.new(:max_age => 'abc123')
|
|
31
|
-
}.
|
|
32
|
+
}.to raise_error(STSBuildError)
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
it "raises an exception if max-age is not supplied" do
|
|
35
|
-
|
|
36
|
+
expect {
|
|
36
37
|
StrictTransportSecurity.new(:includeSubdomains => true)
|
|
37
|
-
}.
|
|
38
|
+
}.to raise_error(STSBuildError)
|
|
38
39
|
end
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
context "with a string argument" do
|
|
42
43
|
it "raises an exception with an invalid max-age" do
|
|
43
|
-
|
|
44
|
+
expect {
|
|
44
45
|
StrictTransportSecurity.new('max-age=abc123')
|
|
45
|
-
}.
|
|
46
|
+
}.to raise_error(STSBuildError)
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
it "raises an exception if max-age is not supplied" do
|
|
49
|
-
|
|
50
|
+
expect {
|
|
50
51
|
StrictTransportSecurity.new('includeSubdomains')
|
|
51
|
-
}.
|
|
52
|
+
}.to raise_error(STSBuildError)
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
it "raises an exception with an invalid format" do
|
|
55
|
-
|
|
56
|
+
expect {
|
|
56
57
|
StrictTransportSecurity.new('max-age=123includeSubdomains')
|
|
57
|
-
}.
|
|
58
|
+
}.to raise_error(STSBuildError)
|
|
58
59
|
end
|
|
59
60
|
end
|
|
60
61
|
end
|