secure_headers 0.5.0 → 1.0.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,12 +1,24 @@
1
+ 1.0.0
2
+ ======
3
+
4
+ Features:
5
+
6
+ - Use non-prefixed header names for Firefox >= 23, Chrome >= 25
7
+ - Use csp 1.0 compliant header for firefox >= 23
8
+
9
+ Bug Fix:
10
+
11
+ - Stop sending CSP on safari 5.1+
12
+
1
13
  0.5.0
2
14
  ======
3
15
 
4
- X-Content-Type-Options also applied to Chrome requests
16
+ - X-Content-Type-Options also applied to Chrome requests
5
17
 
6
18
  0.4.3
7
19
  ======
8
20
 
9
- Safari 5 is just completely broken when CSP is used, both mobile and desktop versions
21
+ - Safari 5 is just completely broken when CSP is used, both mobile and desktop versions
10
22
 
11
23
  0.4.2
12
24
  ======
@@ -59,6 +59,8 @@ module SecureHeaders
59
59
  # set_csp_header(+Hash+) - uses the request accessor and options from parameters
60
60
  # set_csp_header(+Rack::Request+, +Hash+)
61
61
  def set_csp_header(req = nil, options=nil)
62
+ return if broken_implementation?(brwsr)
63
+
62
64
  if req.is_a?(Hash)
63
65
  options = req
64
66
  elsif req
@@ -66,17 +68,15 @@ module SecureHeaders
66
68
  end
67
69
 
68
70
  options = self.class.secure_headers_options[:csp] if options.nil?
69
-
70
- return if broken_implementation?(brwsr)
71
-
72
71
  options = self.class.options_for :csp, options
72
+
73
73
  return if options == false
74
74
 
75
- header = ContentSecurityPolicy.new(options, :request => request, :controller => self)
76
- set_header(header.name, header.value)
75
+ csp_header = ContentSecurityPolicy.new(options, :request => request, :controller => self)
76
+ set_header(csp_header)
77
77
  if options && options[:experimental] && options[:enforce]
78
- header = ContentSecurityPolicy.new(options, :experimental => true, :request => request, :controller => self)
79
- set_header(header.name, header.value)
78
+ experimental_header = ContentSecurityPolicy.new(options, :experimental => true, :request => request, :controller => self)
79
+ set_header(experimental_header)
80
80
  end
81
81
  end
82
82
 
@@ -105,26 +105,32 @@ module SecureHeaders
105
105
  return if options == false
106
106
 
107
107
  header = klass.new(options)
108
- set_header(header.name, header.value)
108
+ set_header(header)
109
109
  end
110
110
 
111
- def set_header(name, value)
112
- response.headers[name] = value
111
+ def set_header(name_or_header, value=nil)
112
+ if name_or_header.is_a?(Header)
113
+ header = name_or_header
114
+ response.headers[header.name] = header.value
115
+ else
116
+ response.headers[name_or_header] = value
117
+ end
113
118
  end
114
119
 
115
120
  def broken_implementation?(browser)
116
- return browser.ios5? || (browser.safari? && browser.version == 5)
121
+ return browser.ios5? || (browser.safari? && browser.version == '5')
117
122
  end
118
123
  end
119
124
  end
120
125
 
121
126
 
122
127
  require "secure_headers/version"
128
+ require "secure_headers/header"
123
129
  require "secure_headers/headers/content_security_policy"
124
130
  require "secure_headers/headers/content_security_policy/browser_strategy"
125
131
  require "secure_headers/headers/content_security_policy/firefox_browser_strategy"
126
132
  require "secure_headers/headers/content_security_policy/ie_browser_strategy"
127
- require "secure_headers/headers/content_security_policy/webkit_browser_strategy"
133
+ require "secure_headers/headers/content_security_policy/standard_browser_strategy"
128
134
  require "secure_headers/headers/x_frame_options"
129
135
  require "secure_headers/headers/strict_transport_security"
130
136
  require "secure_headers/headers/x_xss_protection"
@@ -0,0 +1,5 @@
1
+ module SecureHeaders
2
+ class Header
3
+
4
+ end
5
+ end
@@ -3,7 +3,7 @@ require 'brwsr'
3
3
 
4
4
  module SecureHeaders
5
5
  class ContentSecurityPolicyBuildError < StandardError; end
6
- class ContentSecurityPolicy
6
+ class ContentSecurityPolicy < Header
7
7
  module Constants
8
8
  WEBKIT_CSP_HEADER = "default-src https: data: 'unsafe-inline' 'unsafe-eval'; frame-src https://* about: javascript:; img-src chrome-extension:"
9
9
  FIREFOX_CSP_HEADER = "options eval-script inline-script; allow https://* data:; frame-src https://* about: javascript:; img-src chrome-extension:"
@@ -12,9 +12,13 @@ module SecureHeaders
12
12
  klass = if browser.ie?
13
13
  IeBrowserStrategy
14
14
  elsif browser.firefox?
15
- FirefoxBrowserStrategy
15
+ if browser.version.to_i >= 23
16
+ StandardBrowserStrategy
17
+ else
18
+ FirefoxBrowserStrategy
19
+ end
16
20
  else
17
- WebkitBrowserStrategy
21
+ StandardBrowserStrategy
18
22
  end
19
23
 
20
24
  klass.new content_security_policy
@@ -1,8 +1,12 @@
1
1
  module SecureHeaders
2
2
  class ContentSecurityPolicy
3
- class WebkitBrowserStrategy < BrowserStrategy
3
+ class StandardBrowserStrategy < BrowserStrategy
4
4
  def base_name
5
- SecureHeaders::ContentSecurityPolicy::WEBKIT_CSP_HEADER_NAME
5
+ if (browser.firefox? && browser.version.to_i >= 23) || (browser.chrome? && browser.version.to_i >= 25)
6
+ SecureHeaders::ContentSecurityPolicy::STANDARD_HEADER_NAME
7
+ else
8
+ SecureHeaders::ContentSecurityPolicy::WEBKIT_CSP_HEADER_NAME
9
+ end
6
10
  end
7
11
 
8
12
  def add_missing_extension_values
@@ -1,7 +1,7 @@
1
1
  module SecureHeaders
2
2
  class STSBuildError < StandardError; end
3
3
 
4
- class StrictTransportSecurity
4
+ class StrictTransportSecurity < Header
5
5
  module Constants
6
6
  HSTS_HEADER_NAME = 'Strict-Transport-Security'
7
7
  HSTS_MAX_AGE = "631138519"
@@ -1,7 +1,7 @@
1
1
  module SecureHeaders
2
2
  class XContentTypeOptionsBuildError < StandardError; end
3
3
  # IE only
4
- class XContentTypeOptions
4
+ class XContentTypeOptions < Header
5
5
  module Constants
6
6
  X_CONTENT_TYPE_OPTIONS_HEADER_NAME = "X-Content-Type-Options"
7
7
  DEFAULT_VALUE = "nosniff"
@@ -1,6 +1,6 @@
1
1
  module SecureHeaders
2
2
  class XFOBuildError < StandardError; end
3
- class XFrameOptions
3
+ class XFrameOptions < Header
4
4
  module Constants
5
5
  XFO_HEADER_NAME = "X-Frame-Options"
6
6
  DEFAULT_VALUE = 'SAMEORIGIN'
@@ -1,6 +1,6 @@
1
1
  module SecureHeaders
2
2
  class XXssProtectionBuildError < StandardError; end
3
- class XXssProtection
3
+ class XXssProtection < Header
4
4
  module Constants
5
5
  X_XSS_PROTECTION_HEADER_NAME = 'X-XSS-Protection'
6
6
  DEFAULT_VALUE = "1"
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "0.5.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -16,8 +16,10 @@ module SecureHeaders
16
16
 
17
17
  IE = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"
18
18
  FIREFOX = "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8"
19
- FIREFOX_18 = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/18.0 Firefox/18.0"
19
+ FIREFOX_23 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0"
20
20
  CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4"
21
+ CHROME_25 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
22
+
21
23
 
22
24
  def request_for user_agent, request_uri=nil, options={:ssl => false}
23
25
  double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://areallylongdomainexample.com') )
@@ -31,13 +33,17 @@ module SecureHeaders
31
33
  context "when supplying options to override request" do
32
34
  specify { ContentSecurityPolicy.new(default_opts, :ua => IE).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
33
35
  specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
36
+ specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
34
37
  specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
38
+ specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
35
39
  end
36
40
 
37
41
  context "when in report-only mode" do
38
42
  specify { ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
39
43
  specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
44
+ specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
40
45
  specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
46
+ specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
41
47
  end
42
48
 
43
49
  context "when in enforce mode" do
@@ -45,14 +51,18 @@ module SecureHeaders
45
51
 
46
52
  specify { ContentSecurityPolicy.new(opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME}
47
53
  specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.should == FIREFOX_CSP_HEADER_NAME}
54
+ specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME}
48
55
  specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.should == WEBKIT_CSP_HEADER_NAME}
56
+ specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME}
49
57
  end
50
58
 
51
59
  context "when in experimental mode" do
52
60
  let(:opts) { default_opts.merge(:enforce => true).merge(:experimental => {})}
53
61
  specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
54
62
  specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name.should == FIREFOX_CSP_HEADER_NAME + "-Report-Only"}
63
+ specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
55
64
  specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.should == WEBKIT_CSP_HEADER_NAME + "-Report-Only"}
65
+ specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
56
66
  end
57
67
  end
58
68
 
@@ -224,7 +234,6 @@ module SecureHeaders
224
234
  end
225
235
 
226
236
  context "auto-whitelists data: uris for img-src" do
227
-
228
237
  it "sets the value if no img-src specified" do
229
238
  csp = ContentSecurityPolicy.new({:default_src => 'self', :disable_fill_missing => true, :disable_chrome_extension => true}, :request => request_for(CHROME))
230
239
  csp.value.should == "default-src 'self'; img-src data:;"
@@ -248,7 +257,7 @@ module SecureHeaders
248
257
  csp.value.should match "default-src"
249
258
  end
250
259
 
251
- context "X-Content-Security-Policy" do
260
+ context "Firefox" do
252
261
  it "builds a csp header for firefox" do
253
262
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
254
263
  csp.value.should == "allow https://*; options inline-script eval-script; img-src data:; script-src https://* data:; style-src https://* about:; report-uri /csp_report;"
@@ -270,19 +279,15 @@ module SecureHeaders
270
279
  csp.value.should =~ /xhr-src 'self' http:/
271
280
  end
272
281
 
273
- it "copies connect-src values to xhr_src values for FF 18" do
274
- opts = {
275
- :default_src => 'http://twitter.com',
276
- :connect_src => 'self http://*.localhost.com:*',
277
- :disable_chrome_extension => true,
278
- :disable_fill_missing => true
279
- }
280
- csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_18))
281
- csp.value.should =~ /xhr-src 'self' http:\/\/\*\.localhost\.com:\*/
282
+ context "Firefox >= 23" do
283
+ it "builds a csp header for firefox" do
284
+ csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23))
285
+ csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
286
+ end
282
287
  end
283
288
  end
284
289
 
285
- context "X-Webkit-CSP" do
290
+ context "Chrome" do
286
291
  it "builds a csp header for chrome" do
287
292
  csp = ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME))
288
293
  csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
@@ -27,15 +27,16 @@ describe SecureHeaders do
27
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",
28
28
  :ios6 => "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
29
29
  :safari5 => "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
30
+ :safari5_1 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
30
31
  :safari6 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
31
32
  }
32
33
 
33
34
  def should_assign_header name, value
34
- subject.should_receive(:set_header).with(name, value)
35
+ response.headers.should_receive(:[]=).with(name, value)
35
36
  end
36
37
 
37
38
  def should_not_assign_header name
38
- subject.should_not_receive(:set_header).with(name, anything)
39
+ response.headers.should_not_receive(:[]=).with(name, anything)
39
40
  end
40
41
 
41
42
  def stub_user_agent val
@@ -72,6 +73,18 @@ describe SecureHeaders do
72
73
  end
73
74
  end
74
75
 
76
+ describe "#set_header" do
77
+ it "accepts name/value pairs" do
78
+ should_assign_header("X-Hipster-Ipsum", "kombucha")
79
+ subject.send(:set_header, "X-Hipster-Ipsum", "kombucha")
80
+ end
81
+
82
+ it "accepts header objects" do
83
+ should_assign_header("Strict-Transport-Security", SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
84
+ subject.send(:set_header, SecureHeaders::StrictTransportSecurity.new)
85
+ end
86
+ end
87
+
75
88
  describe "#set_security_headers" do
76
89
  before(:each) do
77
90
  SecureHeaders::ContentSecurityPolicy.stub(:new).and_return(double.as_null_object)
@@ -82,7 +95,7 @@ describe SecureHeaders do
82
95
  number_of_headers = case name
83
96
  when :ie, :chrome
84
97
  5
85
- when :ios5, :safari5
98
+ when :ios5, :safari5, :safari5_1
86
99
  3 # csp breaks these browsers
87
100
  else
88
101
  4
@@ -262,13 +275,13 @@ describe SecureHeaders do
262
275
  opts = @opts.merge(:enforce => false)
263
276
  should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
264
277
  should_not_assign_header(WEBKIT_CSP_HEADER_NAME)
265
- subject.set_csp_header opts
278
+ subject.set_csp_header(opts)
266
279
  end
267
280
 
268
281
  it "sets a header in enforce mode as well as report-only mode" do
269
282
  should_assign_header(WEBKIT_CSP_HEADER_NAME, anything)
270
283
  should_assign_header(WEBKIT_CSP_HEADER_NAME + "-Report-Only", anything)
271
- subject.set_csp_header @opts
284
+ subject.set_csp_header(@opts)
272
285
  end
273
286
  end
274
287
  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.5.0
4
+ version: 1.0.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-05-21 00:00:00.000000000 Z
12
+ date: 2013-06-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: brwsr
@@ -148,11 +148,12 @@ files:
148
148
  - fixtures/rails_3_2_12_no_init/vendor/assets/stylesheets/.gitkeep
149
149
  - fixtures/rails_3_2_12_no_init/vendor/plugins/.gitkeep
150
150
  - lib/secure_headers.rb
151
+ - lib/secure_headers/header.rb
151
152
  - lib/secure_headers/headers/content_security_policy.rb
152
153
  - lib/secure_headers/headers/content_security_policy/browser_strategy.rb
153
154
  - lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb
154
155
  - lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb
155
- - lib/secure_headers/headers/content_security_policy/webkit_browser_strategy.rb
156
+ - lib/secure_headers/headers/content_security_policy/standard_browser_strategy.rb
156
157
  - lib/secure_headers/headers/strict_transport_security.rb
157
158
  - lib/secure_headers/headers/x_content_type_options.rb
158
159
  - lib/secure_headers/headers/x_frame_options.rb