rack-attack 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-attack might be problematic. Click here for more details.

@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b294cc1911f079c5df74d5325e01fabd2a207498
4
+ data.tar.gz: 99306d1db94cfeab978bef7573e1455c0e2cc428
5
+ SHA512:
6
+ metadata.gz: 12a543ddffa7cc24893c3ee302071738e8f9e39fe063e9b6610492c41d03ba5179c43bffe50ff182e880fd936a1d970ab9f6a0d6c536c2db77e89b555dd253d2
7
+ data.tar.gz: b439faf86fd536d941327aa03c0d4e2d344a527857f120e09e0ab0bdec9fbbcef686d716da9c56867d4ca277c20edf7d8313c6c79d7f01c4e09ff0d929092a7a
data/README.md CHANGED
@@ -4,7 +4,14 @@
4
4
  Rack::Attack is a rack middleware to protect your web app from bad clients.
5
5
  It allows *whitelisting*, *blacklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
6
6
 
7
- Throttle state is stored in a configurable cache (e.g. `Rails.cache`), presumably backed by memcached or redis (at least v3.0.0).
7
+ Throttle state is stored in a configurable cache (e.g. `Rails.cache`), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
8
+
9
+ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack.
10
+
11
+ [![Gem Version](https://badge.fury.io/rb/rack-attack.png)](http://badge.fury.io/rb/rack-attack)
12
+ [![Build Status](https://travis-ci.org/kickstarter/rack-attack.png?branch=master)](https://travis-ci.org/kickstarter/rack-attack)
13
+ [![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.png)](https://codeclimate.com/github/kickstarter/rack-attack)
14
+
8
15
 
9
16
  ## Installation
10
17
 
@@ -15,7 +22,7 @@ Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it t
15
22
  gem 'rack-attack'
16
23
  ```
17
24
  Tell your app to use the Rack::Attack middleware.
18
- For Rails 3 apps:
25
+ For Rails 3+ apps:
19
26
 
20
27
  ```ruby
21
28
  # In config/application.rb
@@ -43,7 +50,7 @@ The Rack::Attack middleware compares each request against *whitelists*, *blackli
43
50
 
44
51
  * If the request matches any **whitelist**, it is allowed.
45
52
  * Otherwise, if the request matches any **blacklist**, it is blocked.
46
- * Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If the throttle limit is exceeded, the request is blocked.
53
+ * Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked.
47
54
  * Otherwise, all **tracks** are checked, and the request is allowed.
48
55
 
49
56
  The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
@@ -71,7 +78,7 @@ The algorithm is actually more concise in code: See [Rack::Attack.call](https://
71
78
 
72
79
  ## Usage
73
80
 
74
- Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
81
+ Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
75
82
  these go in an initializer in `config/initializers/`.
76
83
  A [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/Request.html) object is passed to the block (named 'req' in the examples).
77
84
 
@@ -121,6 +128,24 @@ how the parameters work.
121
128
  end
122
129
  ```
123
130
 
131
+ #### Allow2Ban
132
+ `Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
133
+ clients until such time as they reach maxretry at which they are cut off as per normal.
134
+ ```ruby
135
+ # Lockout IP addresses that are hammering your login page.
136
+ # After 20 requests in 1 minute, block all requests from that IP for 1 hour.
137
+ Rack::Attack.blacklist('allow2ban login scrapers') do |req|
138
+ # `filter` returns false value if request is to your login page (but still
139
+ # increments the count) so request below the limit are not blocked until
140
+ # they hit the limit. At that point, filter will return true and block.
141
+ Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do
142
+ # The count for the IP is incremented if the return value is truthy.
143
+ req.path = '/login' and req.method == 'post'
144
+ end
145
+ end
146
+ ```
147
+
148
+
124
149
  ### Throttles
125
150
 
126
151
  ```ruby
@@ -140,6 +165,13 @@ how the parameters work.
140
165
  Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
141
166
  req.params['email'] if req.path == '/login' && req.post?
142
167
  end
168
+
169
+ # You can also set a limit using a proc instead of a number. For
170
+ # instance, after Rack::Auth::Basic has authenticated the user:
171
+ limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
172
+ Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
173
+ req.ip
174
+ end
143
175
  ```
144
176
 
145
177
  ### Tracks
@@ -204,9 +236,9 @@ You can subscribe to 'rack.attack' events and log it, graph it, etc:
204
236
 
205
237
  ## Testing
206
238
 
207
- A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will
208
- need to enable the cache in your development environment. See [Caching with Rails](http://guides.rubyonrails.org/caching_with_rails.html)
209
- for more on how to do this.
239
+ A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will
240
+ need to enable the cache in your development environment. See [Caching with Rails](http://guides.rubyonrails.org/caching_with_rails.html)
241
+ for more on how to do this.
210
242
 
211
243
  ## Performance
212
244
 
@@ -230,10 +262,12 @@ It is impractical if not impossible to block abusive clients completely.
230
262
  Rack::Attack aims to let developers quickly mitigate abusive requests and rely
231
263
  less on short-term, one-off hacks to block a particular attack.
232
264
 
233
- See also: the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack.
265
+ ## Mailing list
234
266
 
235
- [![Build Status](https://travis-ci.org/kickstarter/rack-attack.png?branch=master)](https://travis-ci.org/kickstarter/rack-attack)
236
- [![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.png)](https://codeclimate.com/github/kickstarter/rack-attack)
267
+ New releases of Rack::Attack are announced on
268
+ <rack.attack.announce@librelist.com>. To subscribe, just send an email to
269
+ <rack.attack.announce@librelist.com>. See the
270
+ [archives](http://librelist.com/browser/rack.attack.announce/).
237
271
 
238
272
  ## License
239
273
 
@@ -8,6 +8,7 @@ module Rack::Attack
8
8
  autoload :Track, 'rack/attack/track'
9
9
  autoload :StoreProxy,'rack/attack/store_proxy'
10
10
  autoload :Fail2Ban, 'rack/attack/fail2ban'
11
+ autoload :Allow2Ban, 'rack/attack/allow2ban'
11
12
 
12
13
  class << self
13
14
 
@@ -0,0 +1,23 @@
1
+ module Rack
2
+ module Attack
3
+ class Allow2Ban < Fail2Ban
4
+ class << self
5
+ protected
6
+ def key_prefix
7
+ 'allow2ban'
8
+ end
9
+
10
+ # everything the same here except we return only return true
11
+ # (blocking the request) if they have tripped the limit.
12
+ def fail!(discriminator, bantime, findtime, maxretry)
13
+ count = cache.count("#{key_prefix}:count:#{discriminator}", findtime)
14
+ if count >= maxretry
15
+ ban!(discriminator, bantime)
16
+ end
17
+ # we may not block them this time, but they're banned for next time
18
+ false
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -15,23 +15,28 @@ module Rack
15
15
  end
16
16
  end
17
17
 
18
- private
18
+ protected
19
+ def key_prefix
20
+ 'fail2ban'
21
+ end
22
+
19
23
  def fail!(discriminator, bantime, findtime, maxretry)
20
- count = cache.count("fail2ban:count:#{discriminator}", findtime)
24
+ count = cache.count("#{key_prefix}:count:#{discriminator}", findtime)
21
25
  if count >= maxretry
22
26
  ban!(discriminator, bantime)
23
27
  end
24
28
 
25
- # Return true for blacklist
26
29
  true
27
30
  end
28
31
 
32
+
33
+ private
29
34
  def ban!(discriminator, bantime)
30
- cache.write("fail2ban:ban:#{discriminator}", 1, bantime)
35
+ cache.write("#{key_prefix}:ban:#{discriminator}", 1, bantime)
31
36
  end
32
37
 
33
38
  def banned?(discriminator)
34
- cache.read("fail2ban:ban:#{discriminator}")
39
+ cache.read("#{key_prefix}:ban:#{discriminator}")
35
40
  end
36
41
 
37
42
  def cache
@@ -22,14 +22,15 @@ module Rack
22
22
 
23
23
  key = "#{name}:#{discriminator}"
24
24
  count = cache.count(key, period)
25
+ current_limit = limit.respond_to?(:call) ? limit.call(req) : limit
25
26
  data = {
26
27
  :count => count,
27
28
  :period => period,
28
- :limit => limit
29
+ :limit => current_limit
29
30
  }
30
31
  (req.env['rack.attack.throttle_data'] ||= {})[name] = data
31
32
 
32
- (count > limit).tap do |throttled|
33
+ (count > current_limit).tap do |throttled|
33
34
  if throttled
34
35
  req.env['rack.attack.matched'] = name
35
36
  req.env['rack.attack.match_type'] = :throttle
@@ -38,7 +39,6 @@ module Rack
38
39
  end
39
40
  end
40
41
  end
41
-
42
42
  end
43
43
  end
44
44
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module Attack
3
- VERSION = '2.2.1'
3
+ VERSION = '2.3.0'
4
4
  end
5
5
  end
@@ -0,0 +1,121 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Rack::Attack.Allow2Ban' do
3
+ before do
4
+ # Use a long findtime; failures due to cache key rotation less likely
5
+ @cache = Rack::Attack.cache
6
+ @findtime = 60
7
+ @bantime = 60
8
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
9
+ @f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
10
+ Rack::Attack.blacklist('pentest') do |req|
11
+ Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
12
+ end
13
+ end
14
+
15
+ describe 'discriminator has not been banned' do
16
+ describe 'making ok request' do
17
+ it 'succeeds' do
18
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
19
+ last_response.status.must_equal 200
20
+ end
21
+ end
22
+
23
+ describe 'making qualifying request' do
24
+ describe 'when not at maxretry' do
25
+ before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
26
+ it 'succeeds' do
27
+ last_response.status.must_equal 200
28
+ end
29
+
30
+ it 'increases fail count' do
31
+ key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4"
32
+ @cache.store.read(key).must_equal 1
33
+ end
34
+
35
+ it 'is not banned' do
36
+ key = "rack::attack:allow2ban:1.2.3.4"
37
+ @cache.store.read(key).must_be_nil
38
+ end
39
+ end
40
+
41
+ describe 'when at maxretry' do
42
+ before do
43
+ # maxretry is 2 - so hit with an extra failed request first
44
+ get '/?test=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
45
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
46
+ end
47
+
48
+ it 'succeeds' do
49
+ last_response.status.must_equal 200
50
+ end
51
+
52
+ it 'increases fail count' do
53
+ key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4"
54
+ @cache.store.read(key).must_equal 2
55
+ end
56
+
57
+ it 'is banned' do
58
+ key = "rack::attack:allow2ban:ban:1.2.3.4"
59
+ @cache.store.read(key).must_equal 1
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'discriminator has been banned' do
67
+ before do
68
+ # maxretry is 2 - so hit enough times to get banned
69
+ get '/?test=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
70
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
71
+ end
72
+
73
+ describe 'making request for other discriminator' do
74
+ it 'succeeds' do
75
+ get '/', {}, 'REMOTE_ADDR' => '2.2.3.4'
76
+ last_response.status.must_equal 200
77
+ end
78
+ end
79
+
80
+ describe 'making ok request' do
81
+ before do
82
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
83
+ end
84
+
85
+ it 'fails' do
86
+ last_response.status.must_equal 401
87
+ end
88
+
89
+ it 'does not increase fail count' do
90
+ key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4"
91
+ @cache.store.read(key).must_equal 2
92
+ end
93
+
94
+ it 'is still banned' do
95
+ key = "rack::attack:allow2ban:ban:1.2.3.4"
96
+ @cache.store.read(key).must_equal 1
97
+ end
98
+ end
99
+
100
+ describe 'making failing request' do
101
+ before do
102
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
103
+ end
104
+
105
+ it 'fails' do
106
+ last_response.status.must_equal 401
107
+ end
108
+
109
+ it 'does not increase fail count' do
110
+ key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4"
111
+ @cache.store.read(key).must_equal 2
112
+ end
113
+
114
+ it 'is still banned' do
115
+ key = "rack::attack:allow2ban:ban:1.2.3.4"
116
+ @cache.store.read(key).must_equal 1
117
+ end
118
+ end
119
+
120
+ end
121
+ end
@@ -37,7 +37,27 @@ describe 'Rack::Attack.throttle' do
37
37
  last_response.headers['Retry-After'].must_equal @period.to_s
38
38
  end
39
39
  end
40
-
41
40
  end
42
41
 
42
+ describe 'Rack::Attack.throttle with limit as proc' do
43
+ before do
44
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
45
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
46
+ Rack::Attack.throttle('ip/sec', :limit => lambda {|req| 1}, :period => @period) { |req| req.ip }
47
+ end
48
+
49
+ allow_ok_requests
50
+
51
+ describe 'a single request' do
52
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
53
+ it 'should set the counter for one request' do
54
+ key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4"
55
+ Rack::Attack.cache.store.read(key).must_equal 1
56
+ end
43
57
 
58
+ it 'should populate throttle data' do
59
+ data = { :count => 1, :limit => 1, :period => @period }
60
+ last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
61
+ end
62
+ end
63
+ end
@@ -13,7 +13,7 @@ rescue LoadError
13
13
  #nothing to do here
14
14
  end
15
15
 
16
- class Minitest::Spec
16
+ class MiniTest::Spec
17
17
 
18
18
  include Rack::Test::Methods
19
19
 
metadata CHANGED
@@ -1,110 +1,134 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-attack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
5
- prerelease:
4
+ version: 2.3.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Aaron Suggs
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-08-13 00:00:00.000000000 Z
11
+ date: 2013-10-11 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rack
16
- requirement: &1 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
17
  - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :runtime
23
21
  prerelease: false
24
- version_requirements: *1
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
25
27
  - !ruby/object:Gem::Dependency
26
28
  name: minitest
27
- requirement: &2 !ruby/object:Gem::Requirement
28
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - '>='
31
32
  - !ruby/object:Gem::Version
32
33
  version: '0'
33
34
  type: :development
34
35
  prerelease: false
35
- version_requirements: *2
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
36
41
  - !ruby/object:Gem::Dependency
37
42
  name: rack-test
38
- requirement: &3 !ruby/object:Gem::Requirement
39
- none: false
43
+ requirement: !ruby/object:Gem::Requirement
40
44
  requirements:
41
45
  - - '>='
42
46
  - !ruby/object:Gem::Version
43
47
  version: '0'
44
48
  type: :development
45
49
  prerelease: false
46
- version_requirements: *3
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
47
55
  - !ruby/object:Gem::Dependency
48
56
  name: rake
49
- requirement: &4 !ruby/object:Gem::Requirement
50
- none: false
57
+ requirement: !ruby/object:Gem::Requirement
51
58
  requirements:
52
59
  - - '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: '0'
55
62
  type: :development
56
63
  prerelease: false
57
- version_requirements: *4
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: activesupport
60
- requirement: &5 !ruby/object:Gem::Requirement
61
- none: false
71
+ requirement: !ruby/object:Gem::Requirement
62
72
  requirements:
63
73
  - - '>='
64
74
  - !ruby/object:Gem::Version
65
75
  version: 3.0.0
66
76
  type: :development
67
77
  prerelease: false
68
- version_requirements: *5
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 3.0.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: debugger
71
- requirement: &6 !ruby/object:Gem::Requirement
72
- none: false
85
+ requirement: !ruby/object:Gem::Requirement
73
86
  requirements:
74
87
  - - ~>
75
88
  - !ruby/object:Gem::Version
76
89
  version: '1.5'
77
90
  type: :development
78
91
  prerelease: false
79
- version_requirements: *6
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.5'
80
97
  - !ruby/object:Gem::Dependency
81
98
  name: redis-activesupport
82
- requirement: &7 !ruby/object:Gem::Requirement
83
- none: false
99
+ requirement: !ruby/object:Gem::Requirement
84
100
  requirements:
85
101
  - - '>='
86
102
  - !ruby/object:Gem::Version
87
103
  version: '0'
88
104
  type: :development
89
105
  prerelease: false
90
- version_requirements: *7
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
91
111
  - !ruby/object:Gem::Dependency
92
112
  name: dalli
93
- requirement: &8 !ruby/object:Gem::Requirement
94
- none: false
113
+ requirement: !ruby/object:Gem::Requirement
95
114
  requirements:
96
115
  - - '>='
97
116
  - !ruby/object:Gem::Version
98
117
  version: '0'
99
118
  type: :development
100
119
  prerelease: false
101
- version_requirements: *8
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
102
125
  description: A rack middleware for throttling and blocking abusive requests
103
126
  email: aaron@ktheory.com
104
127
  executables: []
105
128
  extensions: []
106
129
  extra_rdoc_files: []
107
130
  files:
131
+ - lib/rack/attack/allow2ban.rb
108
132
  - lib/rack/attack/blacklist.rb
109
133
  - lib/rack/attack/cache.rb
110
134
  - lib/rack/attack/check.rb
@@ -117,6 +141,7 @@ files:
117
141
  - lib/rack/attack.rb
118
142
  - Rakefile
119
143
  - README.md
144
+ - spec/allow2ban_spec.rb
120
145
  - spec/fail2ban_spec.rb
121
146
  - spec/rack_attack_cache_spec.rb
122
147
  - spec/rack_attack_spec.rb
@@ -126,30 +151,30 @@ files:
126
151
  homepage: http://github.com/kickstarter/rack-attack
127
152
  licenses:
128
153
  - MIT
154
+ metadata: {}
129
155
  post_install_message:
130
156
  rdoc_options:
131
157
  - --charset=UTF-8
132
158
  require_paths:
133
159
  - lib
134
160
  required_ruby_version: !ruby/object:Gem::Requirement
135
- none: false
136
161
  requirements:
137
162
  - - '>='
138
163
  - !ruby/object:Gem::Version
139
164
  version: 1.9.2
140
165
  required_rubygems_version: !ruby/object:Gem::Requirement
141
- none: false
142
166
  requirements:
143
167
  - - '>='
144
168
  - !ruby/object:Gem::Version
145
169
  version: '0'
146
170
  requirements: []
147
171
  rubyforge_project:
148
- rubygems_version: 1.8.3
172
+ rubygems_version: 2.1.6
149
173
  signing_key:
150
- specification_version: 3
174
+ specification_version: 4
151
175
  summary: Block & throttle abusive requests
152
176
  test_files:
177
+ - spec/allow2ban_spec.rb
153
178
  - spec/fail2ban_spec.rb
154
179
  - spec/rack_attack_cache_spec.rb
155
180
  - spec/rack_attack_spec.rb