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 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