secure_headers 0.3.0 → 0.4.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.
- data/HISTORY.md +5 -0
- data/README.md +18 -6
- data/lib/secure_headers.rb +40 -25
- data/lib/secure_headers/headers/x_xss_protection.rb +1 -1
- data/lib/secure_headers/version.rb +1 -1
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +12 -0
- data/spec/lib/secure_headers_spec.rb +28 -24
- metadata +2 -2
data/HISTORY.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
0.4.0
|
2
|
+
=======
|
3
|
+
- Treat each header as it's own before_filter. This allows you to `skip_before_filter :set_X_header, :only => :bad_idea
|
4
|
+
- Should be backwards compatible, but it is a change to the API.
|
5
|
+
|
1
6
|
0.3.0
|
2
7
|
=======
|
3
8
|
- Greatly reduce the need to use the forward_endpoint attribute. If you are posting from your site to a host that matches TLD+1 (e.g. translate.twitter.com matches twitter.com), use a protocol relative value for report-uri. This will alleviate the need to use forwarding. If your host doesn't match, you still need to use forwarding due to host mismatches for Firefox.
|
data/README.md
CHANGED
@@ -37,13 +37,25 @@ Functionality provided
|
|
37
37
|
|
38
38
|
By default, it will set all of the headers listed in the options section below unless specified.
|
39
39
|
|
40
|
+
### Disabling
|
41
|
+
|
42
|
+
Use the standard `skip_before_filter :filter_name, options` mechanism. e.g. `skip_before_filter :set_csp_header, :only => :tinymce_page`
|
43
|
+
|
44
|
+
The following methods are going to be called, unles they are provided in a `skip_before_filter` block.
|
45
|
+
|
46
|
+
* `:set_csp_header`
|
47
|
+
* `:set_hsts_header`
|
48
|
+
* `:set_x_frame_options_header`
|
49
|
+
* `:set_x_xss_protection_header`
|
50
|
+
* `:set_x_content_type_options_header`
|
51
|
+
|
40
52
|
### Automagic
|
41
53
|
|
42
54
|
This gem makes a few assumptions about how you will use some features. For example:
|
43
55
|
|
44
56
|
* It adds 'chrome-extension:' to your CSP directives by default. This helps drastically reduce the amount of reports, but you can also disable this feature by supplying :disable_chrome_extension => true.
|
45
57
|
* It fills any blank directives with the value in :default_src Getting a default\-src report is pretty useless. This way, you will always know what type of violation occurred. You can disable this feature by supplying :disable_fill_missing => true.
|
46
|
-
* It copies the connect\-src value to xhr\-src for AJAX requests.
|
58
|
+
* It copies the connect\-src value to xhr\-src for AJAX requests when using Firefox.
|
47
59
|
* Firefox does not support cross\-origin CSP reports. If we are using Firefox, AND the value for :report_uri does not satisfy the same\-origin requirements, we will instead forward to an internal endpoint (`FF_CSP_ENDPOINT`). This is also the case if :report_uri only contains a path, which we assume will be cross host. This endpoint will in turn forward the request to the value in :forward_endpoint without restriction. More information can be found in the "Note on Firefox handling of CSP" section.
|
48
60
|
|
49
61
|
|
@@ -56,7 +68,7 @@ This gem makes a few assumptions about how you will use some features. For exam
|
|
56
68
|
config.hsts = {:max_age => 99, :include_subdomains => true}
|
57
69
|
config.x_frame_options = 'DENY'
|
58
70
|
config.x_content_type_options = "nosniff"
|
59
|
-
config.x_xss_protection = {:value =>
|
71
|
+
config.x_xss_protection = {:value => 1, :mode => false}
|
60
72
|
config.csp = {
|
61
73
|
:default_src => "https://* inline eval",
|
62
74
|
:report_uri => '//example.com/uri-directive',
|
@@ -91,7 +103,7 @@ header will be constructed using the supplied options.
|
|
91
103
|
```ruby
|
92
104
|
:hsts => {:max_age => 631138519, :include_subdomain => true}
|
93
105
|
:x_frame_options => {:value => 'SAMEORIGIN'}
|
94
|
-
:x_xss_protection => {:value =>
|
106
|
+
:x_xss_protection => {:value => 1, :mode => false} # set the :mode option to 'block' to enforce the browser's xss filter
|
95
107
|
```
|
96
108
|
|
97
109
|
### Content Security Policy (CSP)
|
@@ -235,7 +247,7 @@ require 'secure_headers'
|
|
235
247
|
config.hsts = {:max_age => 99, :include_subdomains => true}
|
236
248
|
config.x_frame_options = 'DENY'
|
237
249
|
config.x_content_type_options = "nosniff"
|
238
|
-
config.x_xss_protection = {:value =>
|
250
|
+
config.x_xss_protection = {:value => 1, :mode => false}
|
239
251
|
config.csp = {
|
240
252
|
:default_src => "https://* inline eval",
|
241
253
|
:report_uri => '//example.com/uri-directive',
|
@@ -249,7 +261,7 @@ class Donkey < Sinatra::Application
|
|
249
261
|
set :root, APP_ROOT
|
250
262
|
|
251
263
|
get '/' do
|
252
|
-
set_csp_header
|
264
|
+
set_csp_header
|
253
265
|
haml :index
|
254
266
|
end
|
255
267
|
end
|
@@ -286,7 +298,7 @@ module Web
|
|
286
298
|
end
|
287
299
|
|
288
300
|
get '/' do
|
289
|
-
set_csp_header
|
301
|
+
set_csp_header
|
290
302
|
render 'index'
|
291
303
|
end
|
292
304
|
end
|
data/lib/secure_headers.rb
CHANGED
@@ -33,7 +33,11 @@ module SecureHeaders
|
|
33
33
|
|
34
34
|
def ensure_security_headers options = {}
|
35
35
|
self.secure_headers_options = options
|
36
|
-
before_filter :
|
36
|
+
before_filter :set_hsts_header
|
37
|
+
before_filter :set_x_frame_options_header
|
38
|
+
before_filter :set_csp_header
|
39
|
+
before_filter :set_x_xss_protection_header
|
40
|
+
before_filter :set_x_content_type_options_header
|
37
41
|
end
|
38
42
|
|
39
43
|
# we can't use ||= because I'm overloading false => disable, nil => default
|
@@ -44,18 +48,27 @@ module SecureHeaders
|
|
44
48
|
end
|
45
49
|
|
46
50
|
module InstanceMethods
|
47
|
-
def
|
48
|
-
|
49
|
-
set_hsts_header(options[:hsts]) if request.ssl?
|
50
|
-
set_x_frame_options_header(options[:x_frame_options])
|
51
|
-
set_csp_header(request, options[:csp]) unless broken_implementation?(brwsr)
|
52
|
-
set_x_xss_protection_header(options[:x_xss_protection])
|
53
|
-
if brwsr.ie?
|
54
|
-
set_x_content_type_options_header(options[:x_content_type_options])
|
55
|
-
end
|
51
|
+
def brwsr
|
52
|
+
@secure_headers_brwsr ||= Brwsr::Browser.new(:ua => request.env['HTTP_USER_AGENT'])
|
56
53
|
end
|
57
54
|
|
58
|
-
|
55
|
+
# backwards compatibility jank, to be removed in 1.0. Old API required a request
|
56
|
+
# object when it didn't really need to.
|
57
|
+
# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
|
58
|
+
# set_csp_header(+Rack::Request+) - uses the parameter and and SecureHeader::Configuration settings
|
59
|
+
# set_csp_header(+Hash+) - uses the request accessor and options from parameters
|
60
|
+
# set_csp_header(+Rack::Request+, +Hash+)
|
61
|
+
def set_csp_header(req = nil, options=nil)
|
62
|
+
if req.is_a?(Hash)
|
63
|
+
options = req
|
64
|
+
elsif req
|
65
|
+
@secure_headers_brwsr = Brwsr::Browser.new(:ua => req.env['HTTP_USER_AGENT'])
|
66
|
+
end
|
67
|
+
|
68
|
+
options = self.class.secure_headers_options[:csp] if options.nil?
|
69
|
+
|
70
|
+
return if broken_implementation?(brwsr)
|
71
|
+
|
59
72
|
options = self.class.options_for :csp, options
|
60
73
|
return if options == false
|
61
74
|
|
@@ -67,36 +80,38 @@ module SecureHeaders
|
|
67
80
|
end
|
68
81
|
end
|
69
82
|
|
70
|
-
def
|
71
|
-
options = self.class.options_for name, options
|
72
|
-
return if options == false
|
73
|
-
|
74
|
-
header = klass.new(options)
|
75
|
-
set_header(header.name, header.value)
|
76
|
-
end
|
77
|
-
|
78
|
-
def set_x_frame_options_header(options=nil)
|
83
|
+
def set_x_frame_options_header(options=self.class.secure_headers_options[:x_frame_options])
|
79
84
|
set_a_header(:x_frame_options, XFrameOptions, options)
|
80
85
|
end
|
81
86
|
|
82
|
-
def set_x_content_type_options_header(options=
|
87
|
+
def set_x_content_type_options_header(options=self.class.secure_headers_options[:x_content_type_options])
|
88
|
+
return unless brwsr.ie?
|
83
89
|
set_a_header(:x_content_type_options, XContentTypeOptions, options)
|
84
90
|
end
|
85
91
|
|
86
|
-
def set_x_xss_protection_header(options=
|
92
|
+
def set_x_xss_protection_header(options=self.class.secure_headers_options[:x_xss_protection])
|
87
93
|
set_a_header(:x_xss_protection, XXssProtection, options)
|
88
94
|
end
|
89
95
|
|
90
|
-
def set_hsts_header(options=
|
96
|
+
def set_hsts_header(options=self.class.secure_headers_options[:hsts])
|
97
|
+
return unless request.ssl?
|
91
98
|
set_a_header(:hsts, StrictTransportSecurity, options)
|
92
99
|
end
|
93
100
|
|
101
|
+
private
|
102
|
+
|
103
|
+
def set_a_header(name, klass, options=nil)
|
104
|
+
options = self.class.options_for name, options
|
105
|
+
return if options == false
|
106
|
+
|
107
|
+
header = klass.new(options)
|
108
|
+
set_header(header.name, header.value)
|
109
|
+
end
|
110
|
+
|
94
111
|
def set_header(name, value)
|
95
112
|
response.headers[name] = value
|
96
113
|
end
|
97
114
|
|
98
|
-
private
|
99
|
-
|
100
115
|
def broken_implementation?(browser)
|
101
116
|
#IOS 5 sometimes refuses to load external resources even when whitelisted with CSP
|
102
117
|
return browser.ios5?
|
@@ -38,7 +38,7 @@ module SecureHeaders
|
|
38
38
|
if !@config[:value]
|
39
39
|
raise XXssProtectionBuildError.new(":value key is missing")
|
40
40
|
elsif @config[:value]
|
41
|
-
unless [0,1].include?(@config[:value])
|
41
|
+
unless [0,1].include?(@config[:value].to_i)
|
42
42
|
raise XXssProtectionBuildError.new(":value must be 1 or 0")
|
43
43
|
end
|
44
44
|
|
@@ -17,6 +17,18 @@ module SecureHeaders
|
|
17
17
|
end
|
18
18
|
|
19
19
|
context "when using a hash value" do
|
20
|
+
it "should allow string values ('1' or '0' are the only valid strings)" do
|
21
|
+
lambda {
|
22
|
+
XXssProtection.new(:value => '1')
|
23
|
+
}.should_not raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should allow integer values (1 or 0 are the only valid integers)" do
|
27
|
+
lambda {
|
28
|
+
XXssProtection.new(:value => 1)
|
29
|
+
}.should_not raise_error
|
30
|
+
end
|
31
|
+
|
20
32
|
it "should raise an error if no value key is supplied" do
|
21
33
|
lambda {
|
22
34
|
XXssProtection.new(:mode => 'block')
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
# half spec test, half integration test...
|
4
3
|
describe SecureHeaders do
|
5
4
|
class DummyClass
|
6
5
|
include ::SecureHeaders
|
@@ -28,13 +27,6 @@ describe SecureHeaders do
|
|
28
27
|
: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
28
|
}
|
30
29
|
|
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
30
|
def should_assign_header name, value
|
39
31
|
subject.should_receive(:set_header).with(name, value)
|
40
32
|
end
|
@@ -61,10 +53,18 @@ describe SecureHeaders do
|
|
61
53
|
end
|
62
54
|
end
|
63
55
|
|
56
|
+
def set_security_headers(subject)
|
57
|
+
subject.set_csp_header
|
58
|
+
subject.set_hsts_header
|
59
|
+
subject.set_x_frame_options_header
|
60
|
+
subject.set_x_content_type_options_header
|
61
|
+
subject.set_x_xss_protection_header
|
62
|
+
end
|
63
|
+
|
64
64
|
describe "#ensure_security_headers" do
|
65
65
|
it "sets a before filter" do
|
66
66
|
options = {}
|
67
|
-
DummyClass.should_receive(:before_filter).
|
67
|
+
DummyClass.should_receive(:before_filter).exactly(5).times
|
68
68
|
DummyClass.ensure_security_headers(options)
|
69
69
|
end
|
70
70
|
end
|
@@ -88,50 +88,54 @@ describe SecureHeaders do
|
|
88
88
|
end
|
89
89
|
|
90
90
|
subject.should_receive(:set_header).exactly(number_of_headers).times # a request for a given header
|
91
|
-
subject.
|
91
|
+
subject.set_csp_header
|
92
|
+
subject.set_x_frame_options_header
|
93
|
+
subject.set_hsts_header
|
94
|
+
subject.set_x_xss_protection_header
|
95
|
+
subject.set_x_content_type_options_header
|
92
96
|
end
|
93
97
|
end
|
94
98
|
|
95
99
|
it "does not set the X-Content-Type-Options when disabled" do
|
96
100
|
stub_user_agent(USER_AGENTS[:ie])
|
97
101
|
should_not_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME)
|
98
|
-
subject.
|
102
|
+
subject.set_x_content_type_options_header(false)
|
99
103
|
end
|
100
104
|
|
101
105
|
it "does not set the X-XSS-PROTECTION when disabled" do
|
102
106
|
stub_user_agent(USER_AGENTS[:ie])
|
103
107
|
should_not_assign_header(X_XSS_PROTECTION_HEADER_NAME)
|
104
|
-
subject.
|
108
|
+
subject.set_x_xss_protection_header(false)
|
105
109
|
end
|
106
110
|
|
107
111
|
it "does not set the X-FRAME-OPTIONS header if disabled" do
|
108
112
|
should_not_assign_header(XFO_HEADER_NAME)
|
109
|
-
subject.
|
113
|
+
subject.set_x_frame_options_header(false)
|
110
114
|
end
|
111
115
|
|
112
116
|
it "does not set the hsts header if disabled" do
|
113
117
|
should_not_assign_header(HSTS_HEADER_NAME)
|
114
|
-
subject.
|
118
|
+
subject.set_hsts_header(false)
|
115
119
|
end
|
116
120
|
|
117
121
|
it "does not set the hsts header the request is over HTTP" do
|
118
122
|
subject.stub_chain(:request, :ssl?).and_return(false)
|
119
123
|
should_not_assign_header(HSTS_HEADER_NAME)
|
120
|
-
subject.
|
124
|
+
subject.set_hsts_header({:include_subdomains => true})
|
121
125
|
end
|
122
126
|
|
123
127
|
it "does not set the CSP header if disabled" do
|
124
128
|
stub_user_agent(USER_AGENTS[:chrome])
|
125
129
|
should_not_assign_header(WEBKIT_CSP_HEADER_NAME)
|
126
|
-
subject.
|
130
|
+
subject.set_csp_header(options_for(:csp).merge(:csp => false))
|
127
131
|
end
|
128
132
|
|
129
133
|
# apparently iOS5 safari with CSP in enforce mode causes nothing to render
|
130
134
|
# it has no effect in report-only mode (as in no report is sent)
|
131
135
|
it "does not set CSP header if using ios5" do
|
132
136
|
stub_user_agent(USER_AGENTS[:ios5])
|
133
|
-
subject.should_not_receive(:
|
134
|
-
subject.
|
137
|
+
subject.should_not_receive(:set_header)
|
138
|
+
subject.set_csp_header(options_for(:csp))
|
135
139
|
end
|
136
140
|
|
137
141
|
context "when disabled by configuration settings" do
|
@@ -144,7 +148,7 @@ describe SecureHeaders do
|
|
144
148
|
config.csp = false
|
145
149
|
end
|
146
150
|
subject.should_not_receive(:set_header)
|
147
|
-
subject
|
151
|
+
set_security_headers(subject)
|
148
152
|
reset_config
|
149
153
|
end
|
150
154
|
end
|
@@ -202,7 +206,7 @@ describe SecureHeaders do
|
|
202
206
|
it "sets CSP headers" do
|
203
207
|
stub_user_agent(USER_AGENTS[:firefox])
|
204
208
|
should_assign_header(FIREFOX_CSP_HEADER_NAME + "-Report-Only", FIREFOX_CSP_HEADER)
|
205
|
-
subject.set_csp_header
|
209
|
+
subject.set_csp_header
|
206
210
|
end
|
207
211
|
end
|
208
212
|
|
@@ -210,7 +214,7 @@ describe SecureHeaders do
|
|
210
214
|
it "sets default CSP header" do
|
211
215
|
stub_user_agent(USER_AGENTS[:chrome])
|
212
216
|
should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", WEBKIT_CSP_HEADER)
|
213
|
-
subject.set_csp_header
|
217
|
+
subject.set_csp_header
|
214
218
|
end
|
215
219
|
end
|
216
220
|
|
@@ -218,7 +222,7 @@ describe SecureHeaders do
|
|
218
222
|
it "sets the CSP header" do
|
219
223
|
stub_user_agent(USER_AGENTS[:opera])
|
220
224
|
should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", WEBKIT_CSP_HEADER)
|
221
|
-
subject.set_csp_header
|
225
|
+
subject.set_csp_header
|
222
226
|
end
|
223
227
|
end
|
224
228
|
|
@@ -239,13 +243,13 @@ describe SecureHeaders do
|
|
239
243
|
opts = @opts.merge(:enforce => false)
|
240
244
|
should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
|
241
245
|
should_not_assign_header(WEBKIT_CSP_HEADER_NAME)
|
242
|
-
subject.set_csp_header
|
246
|
+
subject.set_csp_header opts
|
243
247
|
end
|
244
248
|
|
245
249
|
it "sets a header in enforce mode as well as report-only mode" do
|
246
250
|
should_assign_header(WEBKIT_CSP_HEADER_NAME, anything)
|
247
251
|
should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
|
248
|
-
subject.set_csp_header
|
252
|
+
subject.set_csp_header @opts
|
249
253
|
end
|
250
254
|
end
|
251
255
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secure_headers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-04-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: brwsr
|