secure_headers 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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
+