secure_headers 0.3.0 → 0.4.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.
- 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
|