secure_headers 1.1.1 → 1.3.1
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 +15 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -0
- data/Gemfile +0 -4
- data/Guardfile +1 -5
- data/HISTORY.md +17 -0
- data/README.md +55 -26
- data/fixtures/rails_3_2_12/Gemfile +0 -2
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +1 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +10 -14
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +7 -12
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -5
- data/fixtures/rails_3_2_12_no_init/Gemfile +0 -2
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +7 -12
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +7 -12
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -18
- data/lib/secure_headers/headers/content_security_policy.rb +46 -36
- data/lib/secure_headers/padrino.rb +14 -0
- data/lib/secure_headers/version.rb +1 -1
- data/spec/controllers/content_security_policy_controller_spec.rb +19 -19
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +105 -85
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +20 -20
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +12 -12
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +12 -12
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +18 -18
- data/spec/lib/secure_headers_spec.rb +11 -11
- data/spec/spec_helper.rb +9 -24
- metadata +8 -12
- data/.rvmrc +0 -1
- data/fixtures/rails_3_2_12/Guardfile +0 -14
- data/fixtures/rails_3_2_12_no_init/Guardfile +0 -14
|
@@ -30,38 +30,38 @@ module SecureHeaders
|
|
|
30
30
|
|
|
31
31
|
describe "#name" do
|
|
32
32
|
context "when supplying options to override request" do
|
|
33
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => IE).name.
|
|
34
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.
|
|
35
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name.
|
|
36
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.
|
|
37
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name.
|
|
33
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => IE).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
34
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
35
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
36
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
37
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
context "when in report-only mode" do
|
|
41
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name.
|
|
42
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.
|
|
43
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name.
|
|
44
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.
|
|
45
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name.
|
|
41
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
42
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
43
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
44
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
45
|
+
specify { expect(ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
context "when in enforce mode" do
|
|
49
49
|
let(:opts) { default_opts.merge(:enforce => true)}
|
|
50
50
|
|
|
51
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(IE)).name.
|
|
52
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.
|
|
53
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name.
|
|
54
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.
|
|
55
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name.
|
|
51
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(IE)).name).to eq(STANDARD_HEADER_NAME)}
|
|
52
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name).to eq(STANDARD_HEADER_NAME)}
|
|
53
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name).to eq(STANDARD_HEADER_NAME)}
|
|
54
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name).to eq(STANDARD_HEADER_NAME)}
|
|
55
|
+
specify { expect(ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name).to eq(STANDARD_HEADER_NAME)}
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
context "when in experimental mode" do
|
|
59
59
|
let(:opts) { default_opts.merge(:enforce => true).merge(:experimental => {})}
|
|
60
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name.
|
|
61
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name.
|
|
62
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name.
|
|
63
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.
|
|
64
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name.
|
|
60
|
+
specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
61
|
+
specify { expect(ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name).to eq(STANDARD_HEADER_NAME + "-Report-Only")}
|
|
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")}
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -76,7 +76,29 @@ module SecureHeaders
|
|
|
76
76
|
context "Content-Security-Policy" do
|
|
77
77
|
it "converts the script values to their equivilents" do
|
|
78
78
|
csp = ContentSecurityPolicy.new(@opts, :request => request_for(CHROME))
|
|
79
|
-
csp.value.
|
|
79
|
+
expect(csp.value).to include("script-src 'unsafe-inline' 'unsafe-eval' https://* data: 'self' 'none'")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "accepts procs for report-uris" do
|
|
83
|
+
opts = {
|
|
84
|
+
:default_src => 'self',
|
|
85
|
+
:report_uri => lambda { "http://lambda/result" }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
csp = ContentSecurityPolicy.new(opts)
|
|
89
|
+
expect(csp.report_uri).to eq("http://lambda/result")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "accepts procs for other fields" do
|
|
93
|
+
opts = {
|
|
94
|
+
:default_src => lambda { "http://lambda/result" },
|
|
95
|
+
:enforce => lambda { true },
|
|
96
|
+
:disable_fill_missing => lambda { true }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
csp = ContentSecurityPolicy.new(opts)
|
|
100
|
+
expect(csp.value).to eq("default-src http://lambda/result; img-src http://lambda/result data:;")
|
|
101
|
+
expect(csp.name).to match("Content-Security-Policy")
|
|
80
102
|
end
|
|
81
103
|
end
|
|
82
104
|
end
|
|
@@ -86,47 +108,47 @@ module SecureHeaders
|
|
|
86
108
|
|
|
87
109
|
it "matches when host, scheme, and port match" do
|
|
88
110
|
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com"))
|
|
89
|
-
csp.send(:same_origin?).
|
|
111
|
+
expect(csp.send(:same_origin?)).to be true
|
|
90
112
|
|
|
91
113
|
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://example.com:443"))
|
|
92
|
-
csp.send(:same_origin?).
|
|
114
|
+
expect(csp.send(:same_origin?)).to be true
|
|
93
115
|
|
|
94
116
|
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com:123'}, :request => request_for(FIREFOX, "https://example.com:123"))
|
|
95
|
-
csp.send(:same_origin?).
|
|
117
|
+
expect(csp.send(:same_origin?)).to be true
|
|
96
118
|
|
|
97
119
|
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
98
|
-
csp.send(:same_origin?).
|
|
120
|
+
expect(csp.send(:same_origin?)).to be true
|
|
99
121
|
|
|
100
122
|
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com:80'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
101
|
-
csp.send(:same_origin?).
|
|
123
|
+
expect(csp.send(:same_origin?)).to be true
|
|
102
124
|
|
|
103
125
|
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:80"))
|
|
104
|
-
csp.send(:same_origin?).
|
|
126
|
+
expect(csp.send(:same_origin?)).to be true
|
|
105
127
|
end
|
|
106
128
|
|
|
107
129
|
it "does not match port mismatches" do
|
|
108
130
|
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://example.com:81"))
|
|
109
|
-
csp.send(:same_origin?).
|
|
131
|
+
expect(csp.send(:same_origin?)).to be false
|
|
110
132
|
end
|
|
111
133
|
|
|
112
134
|
it "does not match host mismatches" do
|
|
113
135
|
csp = ContentSecurityPolicy.new({:report_uri => 'http://twitter.com'}, :request => request_for(FIREFOX, "http://example.com"))
|
|
114
|
-
csp.send(:same_origin?).
|
|
136
|
+
expect(csp.send(:same_origin?)).to be false
|
|
115
137
|
end
|
|
116
138
|
|
|
117
139
|
it "does not match host mismatches because of subdomains" do
|
|
118
140
|
csp = ContentSecurityPolicy.new({:report_uri => 'http://example.com'}, :request => request_for(FIREFOX, "http://sub.example.com"))
|
|
119
|
-
csp.send(:same_origin?).
|
|
141
|
+
expect(csp.send(:same_origin?)).to be false
|
|
120
142
|
end
|
|
121
143
|
|
|
122
144
|
it "does not match scheme mismatches" do
|
|
123
145
|
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "ftp://example.com"))
|
|
124
|
-
csp.send(:same_origin?).
|
|
146
|
+
expect(csp.send(:same_origin?)).to be false
|
|
125
147
|
end
|
|
126
148
|
|
|
127
149
|
it "does not match on substring collisions" do
|
|
128
150
|
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://anotherexample.com"))
|
|
129
|
-
csp.send(:same_origin?).
|
|
151
|
+
expect(csp.send(:same_origin?)).to be false
|
|
130
152
|
end
|
|
131
153
|
end
|
|
132
154
|
|
|
@@ -136,29 +158,29 @@ module SecureHeaders
|
|
|
136
158
|
context "when using firefox" do
|
|
137
159
|
it "updates the report-uri when posting to a different host" do
|
|
138
160
|
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
139
|
-
csp.report_uri.
|
|
161
|
+
expect(csp.report_uri).to eq(FF_CSP_ENDPOINT)
|
|
140
162
|
end
|
|
141
163
|
|
|
142
164
|
it "doesn't change report-uri if a path supplied" do
|
|
143
165
|
csp = ContentSecurityPolicy.new({:report_uri => "/csp_reports"}, :request => request_for(FIREFOX, "https://anexample.com"))
|
|
144
|
-
csp.report_uri.
|
|
166
|
+
expect(csp.report_uri).to eq("/csp_reports")
|
|
145
167
|
end
|
|
146
168
|
|
|
147
169
|
it "forwards if the request_uri is set to a non-matching value" do
|
|
148
170
|
csp = ContentSecurityPolicy.new({:report_uri => "https://another.example.com", :forward_endpoint => '/somewhere'}, :ua => "Firefox", :request_uri => "https://anexample.com")
|
|
149
|
-
csp.report_uri.
|
|
171
|
+
expect(csp.report_uri).to eq(FF_CSP_ENDPOINT)
|
|
150
172
|
end
|
|
151
173
|
end
|
|
152
174
|
|
|
153
175
|
it "does not update the URI is the report_uri is on the same origin" do
|
|
154
176
|
opts = {:report_uri => 'https://example.com/csp', :forward_endpoint => 'https://anotherexample.com'}
|
|
155
177
|
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, "https://example.com/somewhere"))
|
|
156
|
-
csp.report_uri.
|
|
178
|
+
expect(csp.report_uri).to eq('https://example.com/csp')
|
|
157
179
|
end
|
|
158
180
|
|
|
159
181
|
it "does not update the report-uri when using a non-firefox browser" do
|
|
160
182
|
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
161
|
-
csp.report_uri.
|
|
183
|
+
expect(csp.report_uri).to eq('https://example.com/csp')
|
|
162
184
|
end
|
|
163
185
|
|
|
164
186
|
context "when using a protocol-relative value for report-uri" do
|
|
@@ -171,20 +193,20 @@ module SecureHeaders
|
|
|
171
193
|
|
|
172
194
|
it "uses the current protocol" do
|
|
173
195
|
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX, '/', :ssl => true))
|
|
174
|
-
csp.value.
|
|
196
|
+
expect(csp.value).to match(%r{report-uri https://example.com/csp;})
|
|
175
197
|
|
|
176
198
|
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
|
177
|
-
csp.value.
|
|
199
|
+
expect(csp.value).to match(%r{report-uri http://example.com/csp;})
|
|
178
200
|
end
|
|
179
201
|
|
|
180
202
|
it "uses the pre-configured https protocol" do
|
|
181
203
|
csp = ContentSecurityPolicy.new(opts, :ua => "Firefox", :ssl => true)
|
|
182
|
-
csp.value.
|
|
204
|
+
expect(csp.value).to match(%r{report-uri https://example.com/csp;})
|
|
183
205
|
end
|
|
184
206
|
|
|
185
207
|
it "uses the pre-configured http protocol" do
|
|
186
208
|
csp = ContentSecurityPolicy.new(opts, :ua => "Firefox", :ssl => false)
|
|
187
|
-
csp.value.
|
|
209
|
+
expect(csp.value).to match(%r{report-uri http://example.com/csp;})
|
|
188
210
|
end
|
|
189
211
|
end
|
|
190
212
|
end
|
|
@@ -192,20 +214,20 @@ module SecureHeaders
|
|
|
192
214
|
describe "#value" do
|
|
193
215
|
it "raises an exception when default-src is missing" do
|
|
194
216
|
csp = ContentSecurityPolicy.new({:script_src => 'anything'}, :request => request_for(CHROME))
|
|
195
|
-
|
|
217
|
+
expect {
|
|
196
218
|
csp.value
|
|
197
|
-
}.
|
|
219
|
+
}.to raise_error(ContentSecurityPolicyBuildError, "Couldn't build CSP header :( Expected to find default_src directive value")
|
|
198
220
|
end
|
|
199
221
|
|
|
200
222
|
context "auto-whitelists data: uris for img-src" do
|
|
201
223
|
it "sets the value if no img-src specified" do
|
|
202
224
|
csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
|
|
203
|
-
csp.value.
|
|
225
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
204
226
|
end
|
|
205
227
|
|
|
206
228
|
it "appends the value if img-src is specified" do
|
|
207
229
|
csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
|
|
208
|
-
csp.value.
|
|
230
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
209
231
|
end
|
|
210
232
|
end
|
|
211
233
|
|
|
@@ -213,30 +235,55 @@ module SecureHeaders
|
|
|
213
235
|
options = default_opts.merge(:disable_fill_missing => false)
|
|
214
236
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
215
237
|
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;"
|
|
216
|
-
csp.value.
|
|
238
|
+
expect(csp.value).to eq(value)
|
|
217
239
|
end
|
|
218
240
|
|
|
219
241
|
it "sends the standard csp header if an unknown browser is supplied" do
|
|
220
242
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(IE))
|
|
221
|
-
csp.value.
|
|
243
|
+
expect(csp.value).to match "default-src"
|
|
222
244
|
end
|
|
223
245
|
|
|
224
246
|
context "Firefox" do
|
|
225
247
|
it "builds a csp header for firefox" do
|
|
226
248
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
|
|
227
|
-
csp.value.
|
|
249
|
+
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;")
|
|
228
250
|
end
|
|
229
251
|
end
|
|
230
252
|
|
|
231
253
|
context "Chrome" do
|
|
232
254
|
it "builds a csp header for chrome" do
|
|
233
255
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME))
|
|
234
|
-
csp.value.
|
|
256
|
+
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;")
|
|
235
257
|
end
|
|
236
258
|
|
|
237
259
|
it "ignores :forward_endpoint settings" do
|
|
238
260
|
csp = ContentSecurityPolicy.new(@options_with_forwarding, :request => request_for(CHROME))
|
|
239
|
-
csp.value.
|
|
261
|
+
expect(csp.value).to match(/report-uri #{@options_with_forwarding[:report_uri]};/)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
context "when using a nonce" do
|
|
266
|
+
it "adds a nonce and unsafe-inline to the script-src value" do
|
|
267
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
|
|
268
|
+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it "adds a nonce and unsafe-inline to the style-src value" do
|
|
272
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
|
|
273
|
+
expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it "adds an identical nonce to the style and script-src directives" do
|
|
277
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce", :script_src => "self nonce"), :request => request_for(CHROME))
|
|
278
|
+
nonce = header.nonce
|
|
279
|
+
value = header.value
|
|
280
|
+
expect(value).to include("style-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
|
|
281
|
+
expect(value).to include("script-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
it "does not add 'unsafe-inline' twice" do
|
|
285
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce inline"), :request => request_for(CHROME))
|
|
286
|
+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline';")
|
|
240
287
|
end
|
|
241
288
|
end
|
|
242
289
|
|
|
@@ -253,12 +300,12 @@ module SecureHeaders
|
|
|
253
300
|
|
|
254
301
|
it "returns the original value" do
|
|
255
302
|
header = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
256
|
-
header.value.
|
|
303
|
+
expect(header.value).to eq("default-src 'self'; img-src 'self' data:; script-src https://*;")
|
|
257
304
|
end
|
|
258
305
|
|
|
259
306
|
it "it returns the experimental value if requested" do
|
|
260
307
|
header = ContentSecurityPolicy.new(options, {:request => request_for(CHROME), :experimental => true})
|
|
261
|
-
header.value.
|
|
308
|
+
expect(header.value).not_to match(/https/)
|
|
262
309
|
end
|
|
263
310
|
end
|
|
264
311
|
|
|
@@ -274,17 +321,17 @@ module SecureHeaders
|
|
|
274
321
|
|
|
275
322
|
it "adds directive values for headers on http" do
|
|
276
323
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
277
|
-
csp.value.
|
|
324
|
+
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;")
|
|
278
325
|
end
|
|
279
326
|
|
|
280
327
|
it "does not add the directive values if requesting https" do
|
|
281
328
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME, '/', :ssl => true))
|
|
282
|
-
csp.value.
|
|
329
|
+
expect(csp.value).not_to match(/http:/)
|
|
283
330
|
end
|
|
284
331
|
|
|
285
332
|
it "does not add the directive values if requesting https" do
|
|
286
333
|
csp = ContentSecurityPolicy.new(options, :ua => "Chrome", :ssl => true)
|
|
287
|
-
csp.value.
|
|
334
|
+
expect(csp.value).not_to match(/http:/)
|
|
288
335
|
end
|
|
289
336
|
|
|
290
337
|
context "when supplying an experimental block" do
|
|
@@ -314,47 +361,20 @@ module SecureHeaders
|
|
|
314
361
|
|
|
315
362
|
it "uses the value in the experimental block over SSL" do
|
|
316
363
|
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX, '/', :ssl => true))
|
|
317
|
-
csp.value.
|
|
364
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:; script-src 'self';")
|
|
318
365
|
end
|
|
319
366
|
|
|
320
367
|
it "detects the :ssl => true option" do
|
|
321
368
|
csp = ContentSecurityPolicy.new(options, :experimental => true, :ua => FIREFOX, :ssl => true)
|
|
322
|
-
csp.value.
|
|
369
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:; script-src 'self';")
|
|
323
370
|
end
|
|
324
371
|
|
|
325
372
|
it "merges the values from experimental/http_additions when not over SSL" do
|
|
326
373
|
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX))
|
|
327
|
-
csp.value.
|
|
374
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:; script-src 'self' https://mycdn.example.com;")
|
|
328
375
|
end
|
|
329
376
|
end
|
|
330
377
|
end
|
|
331
|
-
|
|
332
|
-
context "when supplying a script nonce callback" do
|
|
333
|
-
let(:options) {
|
|
334
|
-
default_opts.merge({
|
|
335
|
-
:script_nonce => "random",
|
|
336
|
-
})
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
it "uses the value in the X-Webkit-CSP" do
|
|
340
|
-
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
341
|
-
csp.value.should match "script-nonce random;"
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
it "runs a dynamic nonce generator" do
|
|
345
|
-
options[:script_nonce] = lambda { 'something' }
|
|
346
|
-
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
347
|
-
csp.value.should match "script-nonce something;"
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
it "runs against the given controller context" do
|
|
351
|
-
fake_params = {}
|
|
352
|
-
options[:script_nonce] = lambda { params[:script_nonce] = 'something' }
|
|
353
|
-
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME), :controller => double(:params => fake_params))
|
|
354
|
-
csp.value.should match "script-nonce something;"
|
|
355
|
-
fake_params.should == {:script_nonce => 'something'}
|
|
356
|
-
end
|
|
357
|
-
end
|
|
358
378
|
end
|
|
359
379
|
end
|
|
360
380
|
end
|
|
@@ -2,59 +2,59 @@ 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
13
|
|
|
14
14
|
context "with an invalid configuration" do
|
|
15
15
|
context "with a hash argument" do
|
|
16
16
|
it "should allow string values for max-age" do
|
|
17
|
-
|
|
17
|
+
expect {
|
|
18
18
|
StrictTransportSecurity.new(:max_age => '1234')
|
|
19
|
-
}.
|
|
19
|
+
}.not_to raise_error
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
it "should allow integer values for max-age" do
|
|
23
|
-
|
|
23
|
+
expect {
|
|
24
24
|
StrictTransportSecurity.new(:max_age => 1234)
|
|
25
|
-
}.
|
|
25
|
+
}.not_to raise_error
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
it "raises an exception with an invalid max-age" do
|
|
29
|
-
|
|
29
|
+
expect {
|
|
30
30
|
StrictTransportSecurity.new(:max_age => 'abc123')
|
|
31
|
-
}.
|
|
31
|
+
}.to raise_error(STSBuildError)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
it "raises an exception if max-age is not supplied" do
|
|
35
|
-
|
|
35
|
+
expect {
|
|
36
36
|
StrictTransportSecurity.new(:includeSubdomains => true)
|
|
37
|
-
}.
|
|
37
|
+
}.to raise_error(STSBuildError)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
context "with a string argument" do
|
|
42
42
|
it "raises an exception with an invalid max-age" do
|
|
43
|
-
|
|
43
|
+
expect {
|
|
44
44
|
StrictTransportSecurity.new('max-age=abc123')
|
|
45
|
-
}.
|
|
45
|
+
}.to raise_error(STSBuildError)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
it "raises an exception if max-age is not supplied" do
|
|
49
|
-
|
|
49
|
+
expect {
|
|
50
50
|
StrictTransportSecurity.new('includeSubdomains')
|
|
51
|
-
}.
|
|
51
|
+
}.to raise_error(STSBuildError)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
it "raises an exception with an invalid format" do
|
|
55
|
-
|
|
55
|
+
expect {
|
|
56
56
|
StrictTransportSecurity.new('max-age=123includeSubdomains')
|
|
57
|
-
}.
|
|
57
|
+
}.to raise_error(STSBuildError)
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
end
|
|
@@ -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
|
|
@@ -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
|
|
@@ -1,50 +1,50 @@
|
|
|
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
7
|
|
|
8
8
|
context "with invalid configuration" do
|
|
9
9
|
it "should raise an error when providing a string that is not valid" do
|
|
10
|
-
|
|
10
|
+
expect {
|
|
11
11
|
XXssProtection.new("asdf")
|
|
12
|
-
}.
|
|
12
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
expect {
|
|
15
15
|
XXssProtection.new("asdf; mode=donkey")
|
|
16
|
-
}.
|
|
16
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
context "when using a hash value" do
|
|
20
20
|
it "should allow string values ('1' or '0' are the only valid strings)" do
|
|
21
|
-
|
|
21
|
+
expect {
|
|
22
22
|
XXssProtection.new(:value => '1')
|
|
23
|
-
}.
|
|
23
|
+
}.not_to raise_error
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it "should allow integer values (1 or 0 are the only valid integers)" do
|
|
27
|
-
|
|
27
|
+
expect {
|
|
28
28
|
XXssProtection.new(:value => 1)
|
|
29
|
-
}.
|
|
29
|
+
}.not_to raise_error
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
it "should raise an error if no value key is supplied" do
|
|
33
|
-
|
|
33
|
+
expect {
|
|
34
34
|
XXssProtection.new(:mode => 'block')
|
|
35
|
-
}.
|
|
35
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
it "should raise an error if an invalid key is supplied" do
|
|
39
|
-
|
|
39
|
+
expect {
|
|
40
40
|
XXssProtection.new(:value => 123)
|
|
41
|
-
}.
|
|
41
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
it "should raise an error if mode != block" do
|
|
45
|
-
|
|
45
|
+
expect {
|
|
46
46
|
XXssProtection.new(:value => 1, :mode => "donkey")
|
|
47
|
-
}.
|
|
47
|
+
}.to raise_error(XXssProtectionBuildError)
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|