rack-protection 1.3.2 → 1.5.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2b7d78da301d9f7fc81ae73e46a389c2b8ce10ab8121f169fd760018ac506d47
4
+ data.tar.gz: a91bd28f8624f325d6714262ac11d83e0a347405f14e600780cb1bfd846e5b34
5
+ SHA512:
6
+ metadata.gz: 0c5de92c0283313c00d50c1f9a219c808ad587caabff81c4d1530abd8f0e7d9c0f3753ad9bab7c06a29ce97b2a717fddc04ced642adff058f3431419286e4da6
7
+ data.tar.gz: d3bf5830bf30475871b73ba54ee38f962bd93c2e1f420b59b649d6dcb7f97d89f091d3add3f692ba847ffe5f5c6cade665d522c86220087d977fb3706e41bd58
data/README.md CHANGED
@@ -50,7 +50,7 @@ Prevented by:
50
50
  Prevented by:
51
51
 
52
52
  * `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`)
53
- * `Rack::Protection::XssHeader` (Internet Explorer only)
53
+ * `Rack::Protection::XSSHeader` (Internet Explorer only)
54
54
 
55
55
  ## Clickjacking
56
56
 
@@ -80,3 +80,11 @@ Prevented by:
80
80
 
81
81
  gem install rack-protection
82
82
 
83
+ # Instrumentation
84
+
85
+ Instrumentation is enabled by passing in an instrumenter as an option.
86
+ ```
87
+ use Rack::Protection, instrumenter: ActiveSupport::Notifications
88
+ ```
89
+
90
+ The instrumenter is passed a namespace (String) and environment (Hash). The namespace is 'rack.protection' and the attack type can be obtained from the environment key 'rack.protection.attack'.
data/Rakefile CHANGED
@@ -14,15 +14,18 @@ task(:spec) { ruby '-S rspec spec' }
14
14
  desc "generate gemspec"
15
15
  task 'rack-protection.gemspec' do
16
16
  require 'rack/protection/version'
17
- content = File.read 'rack-protection.gemspec'
17
+ content = File.binread 'rack-protection.gemspec'
18
18
 
19
19
  # fetch data
20
20
  fields = {
21
- :authors => `git shortlog -sn`.scan(/[^\d\s].*/),
22
- :email => `git shortlog -sne`.scan(/[^<]+@[^>]+/),
23
- :files => `git ls-files`.split("\n").reject { |f| f =~ /^(\.|Gemfile)/ }
21
+ :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/),
22
+ :email => `git shortlog -sne`.force_encoding('utf-8').scan(/[^<]+@[^>]+/),
23
+ :files => `git ls-files`.force_encoding('utf-8').split("\n").reject { |f| f =~ /^(\.|Gemfile)/ }
24
24
  }
25
25
 
26
+ # double email :(
27
+ fields[:email].delete("konstantin.haase@gmail.com")
28
+
26
29
  # insert data
27
30
  fields.each do |field, values|
28
31
  updated = " s.#{field} = ["
@@ -20,7 +20,11 @@ module Rack
20
20
  def self.new(app, options = {})
21
21
  # does not include: RemoteReferrer, AuthenticityToken and FormToken
22
22
  except = Array options[:except]
23
+ use_these = Array options[:use]
23
24
  Rack::Builder.new do
25
+ use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer
26
+ use ::Rack::Protection::AuthenticityToken,options if use_these.include? :authenticity_token
27
+ use ::Rack::Protection::FormToken, options if use_these.include? :form_token
24
28
  use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options
25
29
  use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin
26
30
  use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing
@@ -11,13 +11,20 @@ module Rack
11
11
  # included in the session.
12
12
  #
13
13
  # Compatible with Rails and rack-csrf.
14
+ #
15
+ # Options:
16
+ #
17
+ # authenticity_param: Defines the param's name that should contain the token on a request.
18
+ #
14
19
  class AuthenticityToken < Base
20
+ default_options :authenticity_param => 'authenticity_token'
21
+
15
22
  def accepts?(env)
16
- return true if safe? env
17
23
  session = session env
18
24
  token = session[:csrf] ||= session['_csrf_token'] || random_string
19
- env['HTTP_X_CSRF_TOKEN'] == token or
20
- Request.new(env).params['authenticity_token'] == token
25
+ safe?(env) ||
26
+ secure_compare(env['HTTP_X_CSRF_TOKEN'].to_s, token) ||
27
+ secure_compare(Request.new(env).params[options[:authenticity_param]].to_s, token)
21
28
  end
22
29
  end
23
30
  end
@@ -11,6 +11,7 @@ module Rack
11
11
  :message => 'Forbidden', :encryptor => Digest::SHA1,
12
12
  :session_key => 'rack.session', :status => 403,
13
13
  :allow_empty_referrer => true,
14
+ :report_key => "protection.failed",
14
15
  :html_types => %w[text/html application/xhtml]
15
16
  }
16
17
 
@@ -42,7 +43,7 @@ module Rack
42
43
 
43
44
  def call(env)
44
45
  unless accepts? env
45
- warn env, "attack prevented by #{self.class}"
46
+ instrument env
46
47
  result = react env
47
48
  end
48
49
  result or app.call(env)
@@ -59,10 +60,22 @@ module Rack
59
60
  l.warn(message)
60
61
  end
61
62
 
63
+ def instrument(env)
64
+ return unless i = options[:instrumenter]
65
+ env['rack.protection.attack'] = self.class.name.split('::').last.downcase
66
+ i.instrument('rack.protection', env)
67
+ end
68
+
62
69
  def deny(env)
70
+ warn env, "attack prevented by #{self.class}"
63
71
  [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]]
64
72
  end
65
73
 
74
+ def report(env)
75
+ warn env, "attack reported by #{self.class}"
76
+ env[options[:report_key]] = true
77
+ end
78
+
66
79
  def session?(env)
67
80
  env.include? options[:session_key]
68
81
  end
@@ -80,6 +93,7 @@ module Rack
80
93
  ref = env['HTTP_REFERER'].to_s
81
94
  return if !options[:allow_empty_referrer] and ref.empty?
82
95
  URI.parse(ref).host || Request.new(env).host
96
+ rescue URI::InvalidURIError
83
97
  end
84
98
 
85
99
  def origin(env)
@@ -87,7 +101,7 @@ module Rack
87
101
  end
88
102
 
89
103
  def random_string(secure = defined? SecureRandom)
90
- secure ? SecureRandom.hex(32) : "%032x" % rand(2**128-1)
104
+ secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1)
91
105
  rescue NotImplementedError
92
106
  random_string false
93
107
  end
@@ -96,6 +110,30 @@ module Rack
96
110
  options[:encryptor].hexdigest value.to_s
97
111
  end
98
112
 
113
+ # The implementations of secure_compare and bytesize are taken from
114
+ # Rack::Utils to be able to support rack older than XXXX.
115
+ def secure_compare(a, b)
116
+ return false unless bytesize(a) == bytesize(b)
117
+
118
+ l = a.unpack("C*")
119
+
120
+ r, i = 0, -1
121
+ b.each_byte { |v| r |= v ^ l[i+=1] }
122
+ r == 0
123
+ end
124
+
125
+ # Return the bytesize of String; uses String#size under Ruby 1.8 and
126
+ # String#bytesize under 1.9.
127
+ if ''.respond_to?(:bytesize)
128
+ def bytesize(string)
129
+ string.bytesize
130
+ end
131
+ else
132
+ def bytesize(string)
133
+ string.size
134
+ end
135
+ end
136
+
99
137
  alias default_reaction deny
100
138
 
101
139
  def html?(headers)
@@ -16,16 +16,22 @@ module Rack
16
16
  # frame_options:: Defines who should be allowed to embed the page in a
17
17
  # frame. Use :deny to forbid any embedding, :sameorigin
18
18
  # to allow embedding from the same origin (default).
19
- class FrameOptions < XSSHeader
19
+ class FrameOptions < Base
20
20
  default_options :frame_options => :sameorigin
21
21
 
22
- def header
23
- @header ||= begin
22
+ def frame_options
23
+ @frame_options ||= begin
24
24
  frame_options = options[:frame_options]
25
25
  frame_options = options[:frame_options].to_s.upcase unless frame_options.respond_to? :to_str
26
- { 'X-Frame-Options' => frame_options.to_str }
26
+ frame_options.to_str
27
27
  end
28
28
  end
29
+
30
+ def call(env)
31
+ status, headers, body = @app.call(env)
32
+ headers['X-Frame-Options'] ||= frame_options if html? headers
33
+ [status, headers, body]
34
+ end
29
35
  end
30
36
  end
31
37
  end
@@ -11,17 +11,24 @@ module Rack
11
11
  # Array prototype has been patched to track data. Checks the referrer
12
12
  # even on GET requests if the content type is JSON.
13
13
  class JsonCsrf < Base
14
- default_reaction :deny
14
+ alias react deny
15
15
 
16
16
  def call(env)
17
+ request = Request.new(env)
17
18
  status, headers, body = app.call(env)
18
- if headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/
19
- if origin(env).nil? and referrer(env) != Request.new(env).host
20
- result = react(env)
21
- warn env, "attack prevented by #{self.class}"
22
- end
19
+
20
+ if has_vector? request, headers
21
+ warn env, "attack prevented by #{self.class}"
22
+ react(env) or [status, headers, body]
23
+ else
24
+ [status, headers, body]
23
25
  end
24
- result or [status, headers, body]
26
+ end
27
+
28
+ def has_vector?(request, headers)
29
+ return false if request.xhr?
30
+ return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/
31
+ origin(request.env).nil? and referrer(request.env) != request.host
25
32
  end
26
33
  end
27
34
  end
@@ -19,16 +19,30 @@ module Rack
19
19
  end
20
20
 
21
21
  def cleanup(path)
22
+ if path.respond_to?(:encoding)
23
+ # Ruby 1.9+ M17N
24
+ encoding = path.encoding
25
+ dot = '.'.encode(encoding)
26
+ slash = '/'.encode(encoding)
27
+ backslash = '\\'.encode(encoding)
28
+ else
29
+ # Ruby 1.8
30
+ dot = '.'
31
+ slash = '/'
32
+ backslash = '\\'
33
+ end
34
+
22
35
  parts = []
23
- unescaped = path.gsub('%2e', '.').gsub('%2f', '/')
36
+ unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash).gsub(/%5c/i, backslash)
37
+ unescaped = unescaped.gsub(backslash, slash)
24
38
 
25
- unescaped.split('/').each do |part|
26
- next if part.empty? or part == '.'
39
+ unescaped.split(slash).each do |part|
40
+ next if part.empty? or part == dot
27
41
  part == '..' ? parts.pop : parts << part
28
42
  end
29
43
 
30
- cleaned = '/' << parts.join('/')
31
- cleaned << '/' if parts.any? and unescaped =~ /\/\.{0,2}$/
44
+ cleaned = slash + parts.join(slash)
45
+ cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$}
32
46
  cleaned
33
47
  end
34
48
  end
@@ -9,9 +9,6 @@ module Rack
9
9
  #
10
10
  # Does not accept unsafe HTTP requests if the Referer [sic] header is set to
11
11
  # a different host.
12
- #
13
- # Combine with NoReferrer to also block remote requests from non-HTTP pages
14
- # (FTP/HTTPS/...).
15
12
  class RemoteReferrer < Base
16
13
  default_reaction :deny
17
14
 
@@ -9,12 +9,12 @@ module Rack
9
9
  #
10
10
  # Tracks request properties like the user agent in the session and empties
11
11
  # the session if those properties change. This essentially prevents attacks
12
- # from Firesheep. Since all headers taken into consideration might be
13
- # spoofed, too, this will not prevent all hijacking attempts.
12
+ # from Firesheep. Since all headers taken into consideration can be
13
+ # spoofed, too, this will not prevent determined hijacking attempts.
14
14
  class SessionHijacking < Base
15
15
  default_reaction :drop_session
16
16
  default_options :tracking_key => :tracking, :encrypt_tracking => true,
17
- :track => %w[HTTP_USER_AGENT HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE]
17
+ :track => %w[HTTP_USER_AGENT HTTP_ACCEPT_LANGUAGE]
18
18
 
19
19
  def accepts?(env)
20
20
  session = session env
@@ -4,7 +4,7 @@ module Rack
4
4
  VERSION
5
5
  end
6
6
 
7
- SIGNATURE = [1, 3, 2]
7
+ SIGNATURE = [1, 5, 5]
8
8
  VERSION = SIGNATURE.join('.')
9
9
 
10
10
  VERSION.extend Comparable
@@ -14,18 +14,10 @@ module Rack
14
14
  class XSSHeader < Base
15
15
  default_options :xss_mode => :block, :nosniff => true
16
16
 
17
- def header
18
- headers = {
19
- 'X-XSS-Protection' => "1; mode=#{options[:xss_mode]}",
20
- 'X-Content-Type-Options' => "nosniff"
21
- }
22
- headers.delete("X-Content-Type-Options") unless options[:nosniff]
23
- headers
24
- end
25
-
26
17
  def call(env)
27
18
  status, headers, body = @app.call(env)
28
- headers = header.merge(headers) if options[:nosniff] and html?(headers)
19
+ headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers
20
+ headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff]
29
21
  [status, headers, body]
30
22
  end
31
23
  end
@@ -2,47 +2,74 @@
2
2
  Gem::Specification.new do |s|
3
3
  # general infos
4
4
  s.name = "rack-protection"
5
- s.version = "1.3.2"
5
+ s.version = "1.5.5"
6
6
  s.description = "You should use protection!"
7
7
  s.homepage = "http://github.com/rkh/rack-protection"
8
8
  s.summary = s.description
9
+ s.license = 'MIT'
9
10
 
10
11
  # generated from git shortlog -sn
11
12
  s.authors = [
12
13
  "Konstantin Haase",
13
14
  "Alex Rodionov",
14
- "Chris Heald",
15
- "Chris Mytton",
16
- "Corey Ward",
17
- "David Kellum",
15
+ "Patrick Ellis",
16
+ "Jason Staten",
17
+ "ITO Nobuaki",
18
+ "Jeff Welling",
19
+ "Matteo Centenaro",
20
+ "Egor Homakov",
21
+ "Florian Gilcher",
18
22
  "Fojas",
23
+ "Igor Bochkariov",
19
24
  "Mael Clerambault",
20
25
  "Martin Mauch",
26
+ "Renne Nissinen",
21
27
  "SAKAI, Kazuaki",
22
28
  "Stanislav Savulchik",
23
29
  "Steve Agalloco",
24
- "Akzhan Abdulin",
25
30
  "TOBY",
26
- "Bj\u{f8}rge N\u{e6}ss"
31
+ "Thais Camilo and Konstantin Haase",
32
+ "Vipul A M",
33
+ "Akzhan Abdulin",
34
+ "brookemckim",
35
+ "Bj\u{f8}rge N\u{e6}ss",
36
+ "Chris Heald",
37
+ "Chris Mytton",
38
+ "Corey Ward",
39
+ "Dario Cravero",
40
+ "David Kellum"
27
41
  ]
28
42
 
29
43
  # generated from git shortlog -sne
30
44
  s.email = [
31
45
  "konstantin.mailinglists@googlemail.com",
32
46
  "p0deje@gmail.com",
33
- "cheald@gmail.com",
34
- "self@hecticjeff.net",
35
- "coreyward@me.com",
36
- "dek-oss@gravitext.com",
47
+ "jstaten07@gmail.com",
48
+ "patrick@soundcloud.com",
49
+ "jeff.welling@gmail.com",
50
+ "bugant@gmail.com",
51
+ "daydream.trippers@gmail.com",
52
+ "florian.gilcher@asquera.de",
37
53
  "developer@fojasaur.us",
54
+ "ujifgc@gmail.com",
38
55
  "mael@clerambault.fr",
39
56
  "martin.mauch@gmail.com",
57
+ "rennex@iki.fi",
40
58
  "kaz.july.7@gmail.com",
41
59
  "s.savulchik@gmail.com",
42
60
  "steve.agalloco@gmail.com",
43
- "akzhan.abdulin@gmail.com",
44
61
  "toby.net.info.mail+git@gmail.com",
45
- "bjoerge@bengler.no"
62
+ "dev+narwen+rkh@rkh.im",
63
+ "vipulnsward@gmail.com",
64
+ "akzhan.abdulin@gmail.com",
65
+ "brooke@digitalocean.com",
66
+ "bjoerge@bengler.no",
67
+ "cheald@gmail.com",
68
+ "self@hecticjeff.net",
69
+ "coreyward@me.com",
70
+ "dario@uxtemple.com",
71
+ "dek-oss@gravitext.com",
72
+ "homakov@gmail.com"
46
73
  ]
47
74
 
48
75
  # generated from git ls-files
@@ -68,6 +95,7 @@ Gem::Specification.new do |s|
68
95
  "lib/rack/protection/xss_header.rb",
69
96
  "rack-protection.gemspec",
70
97
  "spec/authenticity_token_spec.rb",
98
+ "spec/base_spec.rb",
71
99
  "spec/escaped_params_spec.rb",
72
100
  "spec/form_token_spec.rb",
73
101
  "spec/frame_options_spec.rb",
@@ -30,4 +30,19 @@ describe Rack::Protection::AuthenticityToken do
30
30
  it "prevents ajax requests without a valid token" do
31
31
  post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest").should_not be_ok
32
32
  end
33
+
34
+ it "allows for a custom authenticity token param" do
35
+ mock_app do
36
+ use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param'
37
+ run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] }
38
+ end
39
+
40
+ post('/', {"csrf_param" => "a"}, 'rack.session' => {:csrf => "a"})
41
+ last_response.should be_ok
42
+ end
43
+
44
+ it "sets a new csrf token for the session in env, even after a 'safe' request" do
45
+ get('/', {}, {})
46
+ env['rack.session'][:csrf].should_not be_nil
47
+ end
33
48
  end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../spec_helper.rb', __FILE__)
2
+
3
+ describe Rack::Protection::Base do
4
+
5
+ subject { described_class.new(lambda {}) }
6
+
7
+ describe "#random_string" do
8
+ it "outputs a string of 32 characters" do
9
+ subject.random_string.length.should == 32
10
+ end
11
+ end
12
+
13
+ describe "#referrer" do
14
+ it "Reads referrer from Referer header" do
15
+ env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/valid"}
16
+ subject.referrer(env).should == "bar.com"
17
+ end
18
+
19
+ it "Reads referrer from Host header when Referer header is relative" do
20
+ env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "/valid"}
21
+ subject.referrer(env).should == "foo.com"
22
+ end
23
+
24
+ it "Reads referrer from Host header when Referer header is missing" do
25
+ env = {"HTTP_HOST" => "foo.com"}
26
+ subject.referrer(env).should == "foo.com"
27
+ end
28
+
29
+ it "Returns nil when Referer header is missing and allow_empty_referrer is false" do
30
+ env = {"HTTP_HOST" => "foo.com"}
31
+ subject.options[:allow_empty_referrer] = false
32
+ subject.referrer(env).should be_nil
33
+ end
34
+
35
+ it "Returns nil when Referer header is invalid" do
36
+ env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/bad|uri"}
37
+ subject.referrer(env).should be_nil
38
+ end
39
+ end
40
+ end
@@ -33,7 +33,6 @@ describe Rack::Protection::EscapedParams do
33
33
 
34
34
  it 'leaves cache-breaker params untouched' do
35
35
  mock_app do |env|
36
- request = Rack::Request.new(env)
37
36
  [200, {'Content-Type' => 'text/plain'}, ['hi']]
38
37
  end
39
38
 
@@ -27,6 +27,11 @@ describe Rack::Protection::JsonCsrf do
27
27
  it "accepts get requests with json responses with no referrer" do
28
28
  get('/', {}).should be_ok
29
29
  end
30
+
31
+ it "accepts XHR requests" do
32
+ get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest').should be_ok
33
+ end
34
+
30
35
  end
31
36
 
32
37
  describe 'not json response' do
@@ -37,4 +42,17 @@ describe Rack::Protection::JsonCsrf do
37
42
  end
38
43
 
39
44
  end
45
+
46
+ describe 'with drop_session as default reaction' do
47
+ it 'still denies' do
48
+ mock_app do
49
+ use Rack::Protection, :reaction => :drop_session
50
+ run proc { |e| [200, {'Content-Type' => 'application/json'}, []]}
51
+ end
52
+
53
+ session = {:foo => :bar}
54
+ get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session)
55
+ last_response.should_not be_ok
56
+ end
57
+ end
40
58
  end
@@ -14,8 +14,8 @@ describe Rack::Protection::PathTraversal do
14
14
 
15
15
  { # yes, this is ugly, feel free to change that
16
16
  '/..' => '/', '/a/../b' => '/b', '/a/../b/' => '/b/', '/a/.' => '/a/',
17
- '/%2e.' => '/', '/a/%2e%2e/b' => '/b', '/a%2f%2e%2e%2fb/' => '/b/',
18
- '//' => '/', '/%2fetc%2fpasswd' => '/etc/passwd'
17
+ '/%2e.' => '/', '/a/%2E%2e/b' => '/b', '/a%2f%2E%2e%2Fb/' => '/b/',
18
+ '//' => '/', '/%2fetc%2Fpasswd' => '/etc/passwd'
19
19
  }.each do |a, b|
20
20
  it("replaces #{a.inspect} with #{b.inspect}") { get(a).body.should == b }
21
21
  end
@@ -25,4 +25,17 @@ describe Rack::Protection::PathTraversal do
25
25
  app.call({}).should be == 42
26
26
  end
27
27
  end
28
+
29
+ if "".respond_to?(:encoding) # Ruby 1.9+ M17N
30
+ context "PATH_INFO's encoding" do
31
+ before do
32
+ @app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] })
33
+ end
34
+
35
+ it 'should remain unchanged as ASCII-8BIT' do
36
+ body = @app.call({ 'PATH_INFO' => '/'.encode('ASCII-8BIT') })[2][0]
37
+ body.should == 'ASCII-8BIT'
38
+ end
39
+ end
40
+ end
28
41
  end
@@ -18,6 +18,53 @@ describe Rack::Protection do
18
18
  session.should be_empty
19
19
  end
20
20
 
21
+ it 'passes errors through if :reaction => :report is used' do
22
+ mock_app do
23
+ use Rack::Protection, :reaction => :report
24
+ run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] }
25
+ end
26
+
27
+ session = {:foo => :bar}
28
+ post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com')
29
+ last_response.should be_ok
30
+ body.should == "true"
31
+ end
32
+
33
+ describe "#react" do
34
+ it 'prevents attacks and warns about it' do
35
+ io = StringIO.new
36
+ mock_app do
37
+ use Rack::Protection, :logger => Logger.new(io)
38
+ run DummyApp
39
+ end
40
+ post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
41
+ io.string.should match /prevented.*Origin/
42
+ end
43
+
44
+ it 'reports attacks if reaction is to report' do
45
+ io = StringIO.new
46
+ mock_app do
47
+ use Rack::Protection, :reaction => :report, :logger => Logger.new(io)
48
+ run DummyApp
49
+ end
50
+ post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
51
+ io.string.should match /reported.*Origin/
52
+ io.string.should_not match /prevented.*Origin/
53
+ end
54
+
55
+ it 'passes errors to reaction method if specified' do
56
+ io = StringIO.new
57
+ Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect }
58
+ mock_app do
59
+ use Rack::Protection, :reaction => :special, :logger => Logger.new(io)
60
+ run DummyApp
61
+ end
62
+ post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com')
63
+ io.string.should match /HTTP_ORIGIN.*malicious.com/
64
+ io.string.should_not match /reported|prevented/
65
+ end
66
+ end
67
+
21
68
  describe "#html?" do
22
69
  context "given an appropriate content-type header" do
23
70
  subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" }
@@ -34,4 +81,25 @@ describe Rack::Protection do
34
81
  it { should be_false }
35
82
  end
36
83
  end
84
+
85
+ describe "#instrument" do
86
+ let(:env) { { 'rack.protection.attack' => 'base' } }
87
+ let(:instrumenter) { double('Instrumenter') }
88
+
89
+ after do
90
+ app.instrument(env)
91
+ end
92
+
93
+ context 'with an instrumenter specified' do
94
+ let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) }
95
+
96
+ it { instrumenter.should_receive(:instrument).with('rack.protection', env) }
97
+ end
98
+
99
+ context 'with no instrumenter specified' do
100
+ let(:app) { Rack::Protection::Base.new(nil) }
101
+
102
+ it { instrumenter.should_not_receive(:instrument) }
103
+ end
104
+ end
37
105
  end
@@ -17,11 +17,12 @@ describe Rack::Protection::SessionHijacking do
17
17
  session.should be_empty
18
18
  end
19
19
 
20
- it "denies requests with a changing Accept-Encoding header" do
20
+ it "accepts requests with a changing Accept-Encoding header" do
21
+ # this is tested because previously it led to clearing the session
21
22
  session = {:foo => :bar}
22
23
  get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a'
23
24
  get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b'
24
- session.should be_empty
25
+ session.should_not be_empty
25
26
  end
26
27
 
27
28
  it "denies requests with a changing Accept-Language header" do
@@ -34,6 +34,12 @@ describe Rack::Protection::XSSHeader do
34
34
  get('/', {}, 'wants' => 'text/html').header["X-Content-Type-Options"].should == "nosniff"
35
35
  end
36
36
 
37
+
38
+ it 'should set the X-Content-Type-Options for other content types' do
39
+ get('/', {}, 'wants' => 'application/foo').header["X-Content-Type-Options"].should == "nosniff"
40
+ end
41
+
42
+
37
43
  it 'should allow changing the nosniff-mode off' do
38
44
  mock_app do
39
45
  use Rack::Protection::XSSHeader, :nosniff => false
metadata CHANGED
@@ -1,95 +1,114 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-protection
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
5
- prerelease:
4
+ version: 1.5.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - Konstantin Haase
9
8
  - Alex Rodionov
10
- - Chris Heald
11
- - Chris Mytton
12
- - Corey Ward
13
- - David Kellum
9
+ - Patrick Ellis
10
+ - Jason Staten
11
+ - ITO Nobuaki
12
+ - Jeff Welling
13
+ - Matteo Centenaro
14
+ - Egor Homakov
15
+ - Florian Gilcher
14
16
  - Fojas
17
+ - Igor Bochkariov
15
18
  - Mael Clerambault
16
19
  - Martin Mauch
20
+ - Renne Nissinen
17
21
  - SAKAI, Kazuaki
18
22
  - Stanislav Savulchik
19
23
  - Steve Agalloco
20
- - Akzhan Abdulin
21
24
  - TOBY
25
+ - Thais Camilo and Konstantin Haase
26
+ - Vipul A M
27
+ - Akzhan Abdulin
28
+ - brookemckim
22
29
  - Bjørge Næss
30
+ - Chris Heald
31
+ - Chris Mytton
32
+ - Corey Ward
33
+ - Dario Cravero
34
+ - David Kellum
23
35
  autorequire:
24
36
  bindir: bin
25
37
  cert_chain: []
26
- date: 2012-12-12 00:00:00.000000000 Z
38
+ date: 2018-03-07 00:00:00.000000000 Z
27
39
  dependencies:
28
40
  - !ruby/object:Gem::Dependency
29
41
  name: rack
30
42
  requirement: !ruby/object:Gem::Requirement
31
- none: false
32
43
  requirements:
33
- - - ! '>='
44
+ - - ">="
34
45
  - !ruby/object:Gem::Version
35
46
  version: '0'
36
47
  type: :runtime
37
48
  prerelease: false
38
49
  version_requirements: !ruby/object:Gem::Requirement
39
- none: false
40
50
  requirements:
41
- - - ! '>='
51
+ - - ">="
42
52
  - !ruby/object:Gem::Version
43
53
  version: '0'
44
54
  - !ruby/object:Gem::Dependency
45
55
  name: rack-test
46
56
  requirement: !ruby/object:Gem::Requirement
47
- none: false
48
57
  requirements:
49
- - - ! '>='
58
+ - - ">="
50
59
  - !ruby/object:Gem::Version
51
60
  version: '0'
52
61
  type: :development
53
62
  prerelease: false
54
63
  version_requirements: !ruby/object:Gem::Requirement
55
- none: false
56
64
  requirements:
57
- - - ! '>='
65
+ - - ">="
58
66
  - !ruby/object:Gem::Version
59
67
  version: '0'
60
68
  - !ruby/object:Gem::Dependency
61
69
  name: rspec
62
70
  requirement: !ruby/object:Gem::Requirement
63
- none: false
64
71
  requirements:
65
- - - ~>
72
+ - - "~>"
66
73
  - !ruby/object:Gem::Version
67
74
  version: '2.0'
68
75
  type: :development
69
76
  prerelease: false
70
77
  version_requirements: !ruby/object:Gem::Requirement
71
- none: false
72
78
  requirements:
73
- - - ~>
79
+ - - "~>"
74
80
  - !ruby/object:Gem::Version
75
81
  version: '2.0'
76
82
  description: You should use protection!
77
83
  email:
78
84
  - konstantin.mailinglists@googlemail.com
79
85
  - p0deje@gmail.com
80
- - cheald@gmail.com
81
- - self@hecticjeff.net
82
- - coreyward@me.com
83
- - dek-oss@gravitext.com
86
+ - jstaten07@gmail.com
87
+ - patrick@soundcloud.com
88
+ - jeff.welling@gmail.com
89
+ - bugant@gmail.com
90
+ - daydream.trippers@gmail.com
91
+ - florian.gilcher@asquera.de
84
92
  - developer@fojasaur.us
93
+ - ujifgc@gmail.com
85
94
  - mael@clerambault.fr
86
95
  - martin.mauch@gmail.com
96
+ - rennex@iki.fi
87
97
  - kaz.july.7@gmail.com
88
98
  - s.savulchik@gmail.com
89
99
  - steve.agalloco@gmail.com
90
- - akzhan.abdulin@gmail.com
91
100
  - toby.net.info.mail+git@gmail.com
101
+ - dev+narwen+rkh@rkh.im
102
+ - vipulnsward@gmail.com
103
+ - akzhan.abdulin@gmail.com
104
+ - brooke@digitalocean.com
92
105
  - bjoerge@bengler.no
106
+ - cheald@gmail.com
107
+ - self@hecticjeff.net
108
+ - coreyward@me.com
109
+ - dario@uxtemple.com
110
+ - dek-oss@gravitext.com
111
+ - homakov@gmail.com
93
112
  executables: []
94
113
  extensions: []
95
114
  extra_rdoc_files: []
@@ -115,6 +134,7 @@ files:
115
134
  - lib/rack/protection/xss_header.rb
116
135
  - rack-protection.gemspec
117
136
  - spec/authenticity_token_spec.rb
137
+ - spec/base_spec.rb
118
138
  - spec/escaped_params_spec.rb
119
139
  - spec/form_token_spec.rb
120
140
  - spec/frame_options_spec.rb
@@ -129,28 +149,27 @@ files:
129
149
  - spec/spec_helper.rb
130
150
  - spec/xss_header_spec.rb
131
151
  homepage: http://github.com/rkh/rack-protection
132
- licenses: []
152
+ licenses:
153
+ - MIT
154
+ metadata: {}
133
155
  post_install_message:
134
156
  rdoc_options: []
135
157
  require_paths:
136
158
  - lib
137
159
  required_ruby_version: !ruby/object:Gem::Requirement
138
- none: false
139
160
  requirements:
140
- - - ! '>='
161
+ - - ">="
141
162
  - !ruby/object:Gem::Version
142
163
  version: '0'
143
164
  required_rubygems_version: !ruby/object:Gem::Requirement
144
- none: false
145
165
  requirements:
146
- - - ! '>='
166
+ - - ">="
147
167
  - !ruby/object:Gem::Version
148
168
  version: '0'
149
169
  requirements: []
150
170
  rubyforge_project:
151
- rubygems_version: 1.8.23
171
+ rubygems_version: 2.7.3
152
172
  signing_key:
153
- specification_version: 3
173
+ specification_version: 4
154
174
  summary: You should use protection!
155
175
  test_files: []
156
- has_rdoc: