secure_headers 0.5.0 → 1.0.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,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