secure_headers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe StrictTransportSecurity do
5
+ specify{ StrictTransportSecurity.new.name.should == "Strict-Transport-Security" }
6
+
7
+ describe "#value" do
8
+ it "sets Strict Transport Security headers" do
9
+ s = StrictTransportSecurity.new
10
+ s.value.should == StrictTransportSecurity::Constants::DEFAULT_VALUE
11
+ end
12
+
13
+ it "allows you to specify includeSubdomains" do
14
+ s = StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
15
+ s.value.should == "max-age=#{HSTS_MAX_AGE}; includeSubdomains"
16
+ end
17
+
18
+ it "accepts a string value and returns verbatim" do
19
+ s = StrictTransportSecurity.new('max-age=1234')
20
+ s.value.should == "max-age=1234"
21
+ end
22
+
23
+ it "allows you to specify max-age" do
24
+ age = '8675309'
25
+ s = StrictTransportSecurity.new(:max_age => age)
26
+ s.value.should == "max-age=#{age}"
27
+ end
28
+
29
+ context "with an invalid configuration" do
30
+ context "with a hash argument" do
31
+ it "raises an exception with an invalid max-age" do
32
+ lambda {
33
+ StrictTransportSecurity.new(:max_age => 'abc123')
34
+ }.should raise_error(STSBuildError)
35
+ end
36
+
37
+ it "raises an exception if max-age is not supplied" do
38
+ lambda {
39
+ StrictTransportSecurity.new(:includeSubdomains => true)
40
+ }.should raise_error(STSBuildError)
41
+ end
42
+ end
43
+
44
+ context "with a string argument" do
45
+ it "raises an exception with an invalid max-age" do
46
+ lambda {
47
+ StrictTransportSecurity.new('max-age=abc123')
48
+ }.should raise_error(STSBuildError)
49
+ end
50
+
51
+ it "raises an exception if max-age is not supplied" do
52
+ lambda {
53
+ StrictTransportSecurity.new('includeSubdomains')
54
+ }.should raise_error(STSBuildError)
55
+ end
56
+
57
+ it "raises an exception with an invalid format" do
58
+ lambda {
59
+ StrictTransportSecurity.new('max-age=123includeSubdomains')
60
+ }.should raise_error(STSBuildError)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,35 @@
1
+ module SecureHeaders
2
+ describe XContentTypeOptions do
3
+ specify{ XContentTypeOptions.new.name.should == "X-Content-Type-Options" }
4
+
5
+ describe "#value" do
6
+ specify { XContentTypeOptions.new.value.should == XContentTypeOptions::Constants::DEFAULT_VALUE}
7
+ specify { XContentTypeOptions.new("nosniff").value.should == "nosniff"}
8
+ specify { XContentTypeOptions.new(:value => 'nosniff').value.should == "nosniff"}
9
+
10
+ context "invalid configuration values" do
11
+ it "accepts nosniff" do
12
+ lambda {
13
+ XContentTypeOptions.new("nosniff")
14
+ }.should_not raise_error
15
+
16
+ lambda {
17
+ XContentTypeOptions.new(:value => "nosniff")
18
+ }.should_not raise_error
19
+ end
20
+
21
+ it "accepts nil" do
22
+ lambda {
23
+ XContentTypeOptions.new
24
+ }.should_not raise_error
25
+ end
26
+
27
+ it "doesn't accept anything besides no-sniff" do
28
+ lambda {
29
+ XContentTypeOptions.new("donkey")
30
+ }.should raise_error(XContentTypeOptionsBuildError)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe XFrameOptions do
5
+ specify{ XFrameOptions.new.name.should == "X-FRAME-OPTIONS" }
6
+
7
+ describe "#value" do
8
+ specify { XFrameOptions.new.value.should == XFrameOptions::Constants::DEFAULT_VALUE}
9
+ specify { XFrameOptions.new("SAMEORIGIN").value.should == "SAMEORIGIN"}
10
+ specify { XFrameOptions.new(:value => 'DENY').value.should == "DENY"}
11
+
12
+ context "with invalid configuration" do
13
+ it "allows SAMEORIGIN" do
14
+ lambda {
15
+ XFrameOptions.new("SAMEORIGIN").value
16
+ }.should_not raise_error(XFOBuildError)
17
+ end
18
+
19
+ it "allows DENY" do
20
+ lambda {
21
+ XFrameOptions.new("DENY").value
22
+ }.should_not raise_error(XFOBuildError)
23
+ end
24
+
25
+ it "allows ALLOW-FROM*" do
26
+ lambda {
27
+ XFrameOptions.new("ALLOW-FROM: example.com").value
28
+ }.should_not raise_error(XFOBuildError)
29
+ end
30
+ it "does not allow garbage" do
31
+ lambda {
32
+ XFrameOptions.new("I like turtles").value
33
+ }.should raise_error(XFOBuildError)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ module SecureHeaders
2
+ describe XXssProtection do
3
+ specify { XXssProtection.new.name.should == X_XSS_PROTECTION_HEADER_NAME}
4
+ specify { XXssProtection.new.value.should == "1"}
5
+ specify { XXssProtection.new("0").value.should == "0"}
6
+ specify { XXssProtection.new(:value => 1, :mode => 'block').value.should == '1; mode=block' }
7
+
8
+ context "with invalid configuration" do
9
+ it "should raise an error when providing a string that is not valid" do
10
+ lambda {
11
+ XXssProtection.new("asdf")
12
+ }.should raise_error(XXssProtectionBuildError)
13
+
14
+ lambda {
15
+ XXssProtection.new("asdf; mode=donkey")
16
+ }.should raise_error(XXssProtectionBuildError)
17
+ end
18
+
19
+ context "when using a hash value" do
20
+ it "should raise an error if no value key is supplied" do
21
+ lambda {
22
+ XXssProtection.new(:mode => 'block')
23
+ }.should raise_error(XXssProtectionBuildError)
24
+ end
25
+
26
+ it "should raise an error if an invalid key is supplied" do
27
+ lambda {
28
+ XXssProtection.new(:value => 123)
29
+ }.should raise_error(XXssProtectionBuildError)
30
+ end
31
+
32
+ it "should raise an error if mode != block" do
33
+ lambda {
34
+ XXssProtection.new(:value => 1, :mode => "donkey")
35
+ }.should raise_error(XXssProtectionBuildError)
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,252 @@
1
+ require 'spec_helper'
2
+
3
+ # half spec test, half integration test...
4
+ describe SecureHeaders do
5
+ class DummyClass
6
+ include ::SecureHeaders
7
+ end
8
+
9
+ subject {DummyClass.new}
10
+ let(:headers) {double}
11
+ let(:response) {double(:headers => headers)}
12
+ let(:max_age) {99}
13
+ let(:request) {double(:ssl? => true, :url => 'https://example.com')}
14
+
15
+ before(:each) do
16
+ stub_user_agent(nil)
17
+ headers.stub(:[])
18
+ subject.stub(:response).and_return(response)
19
+ subject.stub(:request).and_return(request)
20
+ end
21
+
22
+ ALL_HEADERS = Hash[[:hsts, :csp, :x_frame_options, :x_content_type_options, :x_xss_protection].map{|header| [header, false]}]
23
+ USER_AGENTS = {
24
+ :firefox => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1',
25
+ :chrome => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
26
+ :ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
27
+ :opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
28
+ :ios5 => "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"
29
+ }
30
+
31
+ describe "#set_header" do
32
+ it "sets the given header and value" do
33
+ headers.should_receive(:[]=).with("header", "value")
34
+ subject.set_header("header", "value")
35
+ end
36
+ end
37
+
38
+ def should_assign_header name, value
39
+ subject.should_receive(:set_header).with(name, value)
40
+ end
41
+
42
+ def should_not_assign_header name
43
+ subject.should_not_receive(:set_header).with(name, anything)
44
+ end
45
+
46
+ def stub_user_agent val
47
+ request.stub_chain(:env, :[]).and_return(val)
48
+ end
49
+
50
+ def options_for header
51
+ ALL_HEADERS.reject{|k,v| k == header}
52
+ end
53
+
54
+ def reset_config
55
+ ::SecureHeaders::Configuration.configure do |config|
56
+ config.hsts = nil
57
+ config.x_frame_options = nil
58
+ config.x_content_type_options = nil
59
+ config.x_xss_protection = nil
60
+ config.csp = nil
61
+ end
62
+ end
63
+
64
+ describe "#ensure_security_headers" do
65
+ it "sets a before filter" do
66
+ options = {}
67
+ DummyClass.should_receive(:before_filter).with(:set_security_headers)
68
+ DummyClass.ensure_security_headers(options)
69
+ end
70
+ end
71
+
72
+ describe "#set_security_headers" do
73
+ before(:each) do
74
+ SecureHeaders::ContentSecurityPolicy.stub(:new).and_return(double.as_null_object)
75
+ end
76
+ USER_AGENTS.each do |name, useragent|
77
+ it "sets all default headers for #{name} (smoke test)" do
78
+ stub_user_agent(useragent)
79
+ number_of_headers = case name
80
+ when :ie
81
+ 5
82
+ when :opera
83
+ 4
84
+ when :ios5
85
+ 3 # csp is disabled for ios5
86
+ else
87
+ 4
88
+ end
89
+
90
+ subject.should_receive(:set_header).exactly(number_of_headers).times # a request for a given header
91
+ subject.set_security_headers
92
+ end
93
+ end
94
+
95
+ it "does not set the X-Content-Type-Options when disabled" do
96
+ stub_user_agent(USER_AGENTS[:ie])
97
+ should_not_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME)
98
+ subject.set_security_headers(:x_content_type_options => false)
99
+ end
100
+
101
+ it "does not set the X-XSS-PROTECTION when disabled" do
102
+ stub_user_agent(USER_AGENTS[:ie])
103
+ should_not_assign_header(X_XSS_PROTECTION_HEADER_NAME)
104
+ subject.set_security_headers(:x_xss_protection => false)
105
+ end
106
+
107
+ it "does not set the X-FRAME-OPTIONS header if disabled" do
108
+ should_not_assign_header(XFO_HEADER_NAME)
109
+ subject.set_security_headers(:x_frame_options => false)
110
+ end
111
+
112
+ it "does not set the hsts header if disabled" do
113
+ should_not_assign_header(HSTS_HEADER_NAME)
114
+ subject.set_security_headers(:hsts => false)
115
+ end
116
+
117
+ it "does not set the hsts header the request is over HTTP" do
118
+ subject.stub_chain(:request, :ssl?).and_return(false)
119
+ should_not_assign_header(HSTS_HEADER_NAME)
120
+ subject.set_security_headers(:hsts => {:include_subdomains => true})
121
+ end
122
+
123
+ it "does not set the CSP header if disabled" do
124
+ stub_user_agent(USER_AGENTS[:chrome])
125
+ should_not_assign_header(WEBKIT_CSP_HEADER_NAME)
126
+ subject.set_security_headers(options_for(:csp).merge(:csp => false))
127
+ end
128
+
129
+ # apparently iOS5 safari with CSP in enforce mode causes nothing to render
130
+ # it has no effect in report-only mode (as in no report is sent)
131
+ it "does not set CSP header if using ios5" do
132
+ stub_user_agent(USER_AGENTS[:ios5])
133
+ subject.should_not_receive(:set_csp_header)
134
+ subject.set_security_headers(options_for(:csp))
135
+ end
136
+
137
+ context "when disabled by configuration settings" do
138
+ it "does not set the X-Content-Type-Options when disabled" do
139
+ ::SecureHeaders::Configuration.configure do |config|
140
+ config.hsts = false
141
+ config.x_frame_options = false
142
+ config.x_content_type_options = false
143
+ config.x_xss_protection = false
144
+ config.csp = false
145
+ end
146
+ subject.should_not_receive(:set_header)
147
+ subject.set_security_headers
148
+ reset_config
149
+ end
150
+ end
151
+ end
152
+
153
+ describe "#set_x_frame_options_header" do
154
+ it "sets the X-FRAME-OPTIONS header" do
155
+ should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
156
+ subject.set_x_frame_options_header
157
+ end
158
+
159
+ it "allows a custom X-FRAME-OPTIONS header" do
160
+ should_assign_header(XFO_HEADER_NAME, "DENY")
161
+ subject.set_x_frame_options_header(:value => 'DENY')
162
+ end
163
+ end
164
+
165
+ context "when using IE" do
166
+ before(:each) do
167
+ stub_user_agent(USER_AGENTS[:ie])
168
+ end
169
+
170
+ describe "#set_x_xss_protection" do
171
+ it "sets the XSS protection header" do
172
+ should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '1')
173
+ subject.set_x_xss_protection_header
174
+ end
175
+
176
+ it "sets a custom X-XSS-PROTECTION header" do
177
+ should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '0')
178
+ subject.set_x_xss_protection_header("0")
179
+ end
180
+
181
+ it "sets the block flag" do
182
+ should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '1; mode=block')
183
+ subject.set_x_xss_protection_header(:mode => 'block', :value => 1)
184
+ end
185
+ end
186
+
187
+ describe "#set_x_content_type_options" do
188
+ it "sets the X-Content-Type-Options" do
189
+ should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, 'nosniff')
190
+ subject.set_x_content_type_options_header
191
+ end
192
+
193
+ it "lets you override X-Content-Type-Options" do
194
+ should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, 'nosniff')
195
+ subject.set_x_content_type_options_header(:value => 'nosniff')
196
+ end
197
+ end
198
+ end
199
+
200
+ describe "#set_csp_header" do
201
+ context "when using Firefox" do
202
+ it "sets CSP headers" do
203
+ stub_user_agent(USER_AGENTS[:firefox])
204
+ should_assign_header(FIREFOX_CSP_HEADER_NAME + "-Report-Only", FIREFOX_CSP_HEADER)
205
+ subject.set_csp_header request
206
+ end
207
+ end
208
+
209
+ context "when using Chrome" do
210
+ it "sets default CSP header" do
211
+ stub_user_agent(USER_AGENTS[:chrome])
212
+ should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", WEBKIT_CSP_HEADER)
213
+ subject.set_csp_header request
214
+ end
215
+ end
216
+
217
+ context "when using a browser besides chrome/firefox" do
218
+ it "sets the CSP header" do
219
+ stub_user_agent(USER_AGENTS[:opera])
220
+ should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", WEBKIT_CSP_HEADER)
221
+ subject.set_csp_header request
222
+ end
223
+ end
224
+
225
+ context "when using the experimental key" do
226
+ before(:each) do
227
+ stub_user_agent(USER_AGENTS[:chrome])
228
+ @opts = {
229
+ :enforce => true,
230
+ :default_src => 'self',
231
+ :script_src => 'https://mycdn.example.com',
232
+ :experimental => {
233
+ :script_src => 'self',
234
+ }
235
+ }
236
+ end
237
+
238
+ it "does not set the header in enforce mode if experimental is supplied, but enforce is disabled" do
239
+ opts = @opts.merge(:enforce => false)
240
+ should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
241
+ should_not_assign_header(WEBKIT_CSP_HEADER_NAME)
242
+ subject.set_csp_header request, opts
243
+ end
244
+
245
+ it "sets a header in enforce mode as well as report-only mode" do
246
+ should_assign_header(WEBKIT_CSP_HEADER_NAME, anything)
247
+ should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
248
+ subject.set_csp_header request, @opts
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'spork'
3
+
4
+ unless Spork.using_spork?
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ add_filter "spec"
8
+ end
9
+ end
10
+
11
+ Spork.prefork do
12
+ require 'pry'
13
+ require 'rspec'
14
+ end
15
+
16
+ Spork.each_run do
17
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
18
+ require File.join(File.dirname(__FILE__), '..', 'app', 'controllers', 'content_security_policy_controller')
19
+ include ::SecureHeaders::StrictTransportSecurity::Constants
20
+ include ::SecureHeaders::ContentSecurityPolicy::Constants
21
+ include ::SecureHeaders::XFrameOptions::Constants
22
+ include ::SecureHeaders::XXssProtection::Constants
23
+ include ::SecureHeaders::XContentTypeOptions::Constants
24
+ end
25
+