rack-protection 1.3.2 → 1.5.5

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