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 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 => '1', :mode => false}
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 => '1', :mode => false} # set the :mode option to 'block' to enforce the browser's xss filter
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 => '1', :mode => false}
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(request, nil)
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(request, nil)
301
+ set_csp_header
290
302
  render 'index'
291
303
  end
292
304
  end
@@ -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 :set_security_headers
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 set_security_headers(options = self.class.secure_headers_options)
48
- brwsr = Brwsr::Browser.new(:ua => request.env['HTTP_USER_AGENT'])
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
- def set_csp_header(request, options=nil)
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 set_a_header(name, klass, options=nil)
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=nil)
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=nil)
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=nil)
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
 
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -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).with(:set_security_headers)
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.set_security_headers
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.set_security_headers(:x_content_type_options => false)
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.set_security_headers(:x_xss_protection => false)
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.set_security_headers(:x_frame_options => false)
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.set_security_headers(:hsts => false)
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.set_security_headers(:hsts => {:include_subdomains => true})
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.set_security_headers(options_for(:csp).merge(:csp => false))
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(:set_csp_header)
134
- subject.set_security_headers(options_for(:csp))
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.set_security_headers
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 request
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 request
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 request
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 request, opts
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 request, @opts
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.3.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-03-08 00:00:00.000000000 Z
12
+ date: 2013-04-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: brwsr