### Rack::Attack Installation Source: https://github.com/rack/rack-attack/blob/main/README.md Instructions on how to install the Rack::Attack gem. ```APIDOC ## Installing Rack::Attack Add this line to your application's Gemfile: ```ruby # In your Gemfile gem "rack-attack", "~> 6.8" ``` And then execute: $ bundle Or install it yourself as: $ gem install rack-attack ``` -------------------------------- ### Rack::Attack Installation and Configuration Source: https://context7.com/rack/rack-attack/llms.txt Instructions on how to install the Rack::Attack gem and configure it as middleware in Rails and non-Rails applications. ```APIDOC ## Installation Add the gem to your application's Gemfile and configure it as middleware. ```ruby # Gemfile gem "rack-attack", "~> 6.8" # For Rails applications, Rack::Attack is used by default # To disable it temporarily: Rack::Attack.enabled = false # For non-Rails Rack applications, add to config.ru: require "rack/attack" use Rack::Attack ``` ``` -------------------------------- ### Install Rack::Attack Gem Source: https://github.com/rack/rack-attack/blob/main/README.md Add the gem to your Gemfile to include the middleware in your project. ```ruby # In your Gemfile gem "rack-attack", "~> 6.8" ``` -------------------------------- ### Install and Configure Rack::Attack Source: https://context7.com/rack/rack-attack/llms.txt Add the gem to your Gemfile and register the middleware in your application configuration. ```ruby # Gemfile gem "rack-attack", "~> 6.8" # For Rails applications, Rack::Attack is used by default # To disable it temporarily: Rack::Attack.enabled = false # For non-Rails Rack applications, add to config.ru: require "rack/attack" use Rack::Attack ``` -------------------------------- ### Custom Blocklist Responder - Rack::Attack Source: https://context7.com/rack/rack-attack/llms.txt Customizes the response for blocklisted requests. The default response is 403 Forbidden. This example returns a 503 Service Unavailable. ```ruby # config/initializers/rack_attack.rb # Custom blocklist response (default is 403 Forbidden) Rack::Attack.blocklisted_responder = lambda do |request| # Return 503 to make attacker think they DOSed the site [503, { "content-type" => "text/plain" }, ["Service Unavailable\n"]] end ``` -------------------------------- ### Test Rack::Attack rate limiting Source: https://context7.com/rack/rack-attack/llms.txt Example RSpec test demonstrating how to verify that a throttle rule correctly triggers a 429 status code. ```ruby RSpec.describe "Rate limiting", :rack_attack do before do Rack::Attack.throttle("test/ip", limit: 2, period: 60) { |req| req.ip } end it "throttles after limit exceeded" do 3.times { get "/" } expect(response.status).to eq(429) end end ``` -------------------------------- ### Selectively Enable Rack::Attack in Tests Source: https://context7.com/rack/rack-attack/llms.txt Selectively enables Rack::Attack for specific tests using RSpec's `config.around` block. It ensures Rack::Attack is enabled only for the duration of the tagged example. ```ruby # spec/rails_helper.rb or test/test_helper.rb # Or selectively enable for specific tests RSpec.configure do |config| config.around(:each, :rack_attack) do |example| Rack::Attack.enabled = true example.run Rack::Attack.enabled = false end end ``` -------------------------------- ### Configure Cache Store Source: https://github.com/rack/rack-attack/blob/main/README.md Sets the cache store for throttling and tracking state, with a recommendation to use a dedicated Redis instance. ```ruby # This is the default Rack::Attack.cache.store = Rails.cache # It is recommended to use a separate database for throttling/allow2ban/fail2ban. Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(url: "...") ``` -------------------------------- ### Configure Rack Middleware Source: https://github.com/rack/rack-attack/blob/main/README.md Manually add the middleware to your Rack application configuration. ```ruby # In config.ru require "rack/attack" use Rack::Attack ``` -------------------------------- ### Implement RateLimit Headers Source: https://github.com/rack/rack-attack/blob/main/README.md Customizes the throttled response to include standard RateLimit headers. ```ruby Rack::Attack.throttled_responder = lambda do |request| match_data = request.env['rack.attack.match_data'] now = match_data[:epoch_time] headers = { 'ratelimit-limit' => match_data[:limit].to_s, 'ratelimit-remaining' => '0', 'ratelimit-reset' => (now + (match_data[:period] - now % match_data[:period])).to_s } [ 429, headers, ["Throttled\n"]] end ``` -------------------------------- ### Implement Exponential Backoff Throttles in Ruby Source: https://github.com/rack/rack-attack/blob/main/docs/advanced_configuration.md Mimics exponential backoff by layering throttles with increasing limits and periods. Useful for rate limiting that becomes stricter over time. ```ruby # Allows 20 requests in 8 seconds # 40 requests in 64 seconds # ... # 100 requests in 0.38 days (~250 requests/day) (1..5).each do |level| throttle("logins/ip/#{level}", :limit => (20 * level), :period => (8 ** level).seconds) do |req| if req.path == '/login' && req.post? req.ip end end end ``` -------------------------------- ### Configure Rack::Attack Cache Source: https://github.com/rack/rack-attack/blob/main/docs/example_configuration.md Configure a custom cache store if not using Rails.cache. The store must implement .increment and .write. ```ruby class Rack::Attack ### Configure Cache ### # If you don't want to use Rails.cache (Rack::Attack's default), then # configure it here. # # Note: The store is only used for throttling (not blocklisting and # safelisting). It must implement .increment and .write like # ActiveSupport::Cache::Store # Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new ### Throttle Spammy Clients ### # If any single client IP is making tons of requests, then they're # probably malicious or a poorly-configured scraper. Either way, they # don't deserve to hog all of the app server's CPU. Cut them off! # # Note: If you're serving assets through rack, those requests may be # counted by rack-attack and this throttle may be activated too # quickly. If so, enable the condition to exclude them from tracking. # Throttle all requests by IP (60rpm) # # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}" throttle('req/ip', limit: 300, period: 5.minutes) do |req| req.ip # unless req.path.start_with?('/assets') end ### Prevent Brute-Force Login Attacks ### # The most common brute-force login attack is a brute-force password # attack where an attacker simply tries a large number of emails and # passwords to see if any credentials match. # # Another common method of attack is to use a swarm of computers with # different IPs to try brute-forcing a password for a specific account. # Throttle POST requests to /login by IP address # # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" throttle('logins/ip', limit: 5, period: 20.seconds) do |req| if req.path == '/login' && req.post? req.ip end end # Throttle POST requests to /login by email param # # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{normalized_email}" # # Note: This creates a problem where a malicious user could intentionally # throttle logins for another user and force their login requests to be # denied, but that's not very common and shouldn't happen to you. (Knock # on wood!) throttle('logins/email', limit: 5, period: 20.seconds) do |req| if req.path == '/login' && req.post? # Normalize the email, using the same logic as your authentication process, to # protect against rate limit bypasses. Return the normalized email if present, nil otherwise. req.params['email'].to_s.downcase.gsub(/\s+/, "").presence end end ### Custom Throttle Response ### # By default, Rack::Attack returns an HTTP 429 for throttled responses, # which is just fine. # # If you want to return 503 so that the attacker might be fooled into # believing that they've successfully broken your app (or you just want to # customize the response), then uncomment these lines. # self.throttled_responder = lambda do |env| # [ 503, # status # {}, # ['']] # end end ``` -------------------------------- ### Rack::Attack Plugging into Application Source: https://github.com/rack/rack-attack/blob/main/README.md How to integrate Rack::Attack middleware into your Rack or Rails application. ```APIDOC ## Plugging into the application Then tell your ruby web application to use rack-attack as a middleware. a) For __rails__ applications it is used by default. You can disable it permanently (like for specific environment) or temporarily (can be useful for specific test cases) by writing: ```ruby Rack::Attack.enabled = false ``` b) For __rack__ applications: ```ruby # In config.ru require "rack/attack" use Rack::Attack ``` __IMPORTANT__: By default, rack-attack won't perform any blocking or throttling, until you specifically tell it what to protect against by configuring some rules. ``` -------------------------------- ### Configure Dynamic Throttle Limits Source: https://github.com/rack/rack-attack/blob/main/README.md Uses procs to define dynamic limits and periods based on the authenticated user. ```ruby limit_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 100 : 1 } period_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 1 : 60 } Rack::Attack.throttle('request per ip', limit: limit_proc, period: period_proc) do |request| request.ip end ``` -------------------------------- ### Rack::Attack Call Method Logic Source: https://github.com/rack/rack-attack/blob/main/README.md Illustrates the core logic of the Rack::Attack middleware's `call` method, showing the order of checks for safelisting, blocklisting, throttling, and tracking. ```ruby def call(env) req = Rack::Attack::Request.new(env) if safelisted?(req) @app.call(env) elsif blocklisted?(req) self.class.blocklisted_responder.call(req) elsif throttled?(req) self.class.throttled_responder.call(req) else tracked?(req) @app.call(env) end end ``` -------------------------------- ### Blocklist Referers from ENV Variables in Ruby Source: https://github.com/rack/rack-attack/blob/main/docs/advanced_configuration.md Configures blocklists based on a comma-separated list of referers stored in an environment variable. Parses the ENV variable and creates a regular expression for matching. ```ruby class Rack::Attack # Split on a comma with 0 or more spaces after it. # E.g. ENV['HEROKU_VARIABLE'] = "foo.com, bar.com" # spammers = ["foo.com", "bar.com"] spammers = ENV['HEROKU_VARIABLE'].split(/,\s*/) # Turn spammers array into a regexp spammer_regexp = Regexp.union(spammers) # /foo\.com|bar\.com/ blocklist("block referer spam") do |request| request.referer =~ spammer_regexp end end ``` -------------------------------- ### Extend Rack::Attack::Request with custom helpers Source: https://context7.com/rack/rack-attack/llms.txt Define custom methods on the request object to simplify rule logic in your initializer. ```ruby # config/initializers/rack_attack.rb class Rack::Attack::Request < ::Rack::Request def localhost? ip == "127.0.0.1" || ip == "::1" end def from_trusted_proxy? ENV.fetch("TRUSTED_PROXIES", "").split(",").include?(ip) end def api_request? path.start_with?("/api/") end def authenticated? env["warden"]&.authenticated? || env["HTTP_AUTHORIZATION"].present? end def api_key env["HTTP_X_API_KEY"] end end # Use custom helpers in rules Rack::Attack.safelist("localhost") { |req| req.localhost? } Rack::Attack.safelist("trusted proxies") { |req| req.from_trusted_proxy? } Rack::Attack.throttle("api/unauthenticated", limit: 60, period: 1.minute) do |req| req.ip if req.api_request? && !req.authenticated? end ``` -------------------------------- ### Configure Cache Store - Rack::Attack Source: https://context7.com/rack/rack-attack/llms.txt Configures the cache store for Rack::Attack's state management. Defaults to Rails.cache, but can be set to Redis, Memcached, or MemoryStore. ```ruby # config/initializers/rack_attack.rb # Use Rails.cache (default in Rails apps) Rack::Attack.cache.store = Rails.cache # Use a dedicated Redis instance (recommended for production) Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new( url: ENV["RACK_ATTACK_REDIS_URL"], namespace: "rack_attack", expires_in: 1.hour ) # Use Memcached Rack::Attack.cache.store = ActiveSupport::Cache::MemCacheStore.new( ENV["MEMCACHED_SERVERS"]&.split(",") || "localhost:11211" ) # Use memory store for development (not recommended for production) Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # Custom cache prefix Rack::Attack.cache.prefix = "myapp:rack_attack" ``` -------------------------------- ### Rack::Attack.blocklist Source: https://context7.com/rack/rack-attack/llms.txt Create custom blocklist rules using a block. Return a truthy value to block the request. ```APIDOC ## Rack::Attack.blocklist Create custom blocklist rules using a block. Return a truthy value to block the request. ```ruby # config/initializers/rack_attack.rb # Block all access to admin paths from non-internal IPs Rack::Attack.blocklist("block admin access") do |request| request.path.start_with?("/admin") && !request.ip.start_with?("10.") end # Block requests with suspicious user agents Rack::Attack.blocklist("block bad user agents") do |req| bad_agents = ["BadBot", "EvilScraper", "MaliciousCrawler"] bad_agents.any? { |agent| req.user_agent.to_s.include?(agent) } end # Block login attempts with specific characteristics Rack::Attack.blocklist("block bad login attempts") do |req| req.path == "/login" && req.post? && req.user_agent == "BadUA" end # Block based on referer spam spammers = ENV.fetch('BLOCKED_REFERERS', '').split(/,\s*/) spammer_regexp = Regexp.union(spammers) Rack::Attack.blocklist("block referer spam") do |request| request.referer =~ spammer_regexp end # Dynamic blocklist using Rails.cache # To block an IP: Rails.cache.write("block:1.2.3.4", true, expires_in: 2.days) ``` ``` -------------------------------- ### Define Custom Rack::Attack Request Helpers in Ruby Source: https://github.com/rack/rack-attack/blob/main/docs/advanced_configuration.md Extends Rack::Attack::Request with custom methods like `localhost?` by monkey-patching. Requires defining the helper method and then using it in a safelist or blocklist. ```ruby class Rack::Attack::Request < ::Rack::Request def localhost? ip == "127.0.0.1" end end Rack::Attack.safelist("localhost") { |req| req.localhost? } ``` -------------------------------- ### Implement Allow2Ban Blocking Source: https://context7.com/rack/rack-attack/llms.txt Allow a specific number of requests before blocking, useful for distinguishing between legitimate users and scrapers. ```ruby # config/initializers/rack_attack.rb # Allow login attempts until abuse threshold is reached # After 20 failed attempts in 1 minute, block for 1 hour Rack::Attack.blocklist("allow2ban login scrapers") do |req| Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 20, findtime: 1.minute, bantime: 1.hour ) do # Increment counter for login page POSTs req.path == "/login" && req.post? end end # Allow some incorrect Basic Auth attempts before blocking Rack::Attack.blocklist("basic auth crackers") do |req| Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 5, findtime: 1.minute, bantime: 1.hour ) do # Return true if authorization header is incorrect if req.env["HTTP_AUTHORIZATION"] auth = Rack::Auth::Basic::Request.new(req.env) auth.provided? && auth.basic? && auth.credentials != ["admin", ENV["ADMIN_PASSWORD"]] end end end ``` -------------------------------- ### Allow2Ban Integration Source: https://github.com/rack/rack-attack/blob/main/README.md Integrate with Allow2Ban logic to temporarily allow traffic until a retry limit is reached. ```APIDOC ## Allow2Ban Integration ### Description Use `Allow2Ban.filter` within a blocklist to allow requests from clients until they reach a `maxretry` limit within a `findtime`, after which they are blocked for `bantime`. ### Method `Rack::Attack::Allow2Ban.filter(discriminator, options, &block)` ### Endpoint N/A (Configuration block) ### Parameters - **discriminator** (string) - A unique identifier for the filter, typically the IP address (e.g., `req.ip`). - **options** (Hash) - Configuration options: - **maxretry** (Integer) - The maximum number of retries allowed before blocking. - **findtime** (Integer) - The time window in seconds to count retries. - **bantime** (Integer) - The duration in seconds to ban the IP. - **block** (Proc) - A block that returns truthy if the request matches the condition for counting towards the retry limit. ### Request Example ```ruby Rack::Attack.blocklist('allow2ban login scrapers') do |req| Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 20, findtime: 1.minute, bantime: 1.hour) do req.path == '/login' and req.post? end end ``` ### Response N/A (Requests are allowed or blocked based on Allow2Ban logic) ``` -------------------------------- ### Custom Blocklisting Source: https://github.com/rack/rack-attack/blob/main/README.md Define custom blocklist rules based on request properties. ```APIDOC ## Custom Blocklisting ### Description Define custom blocklist rules using a block that evaluates request properties. If the block returns truthy, the request is blocked. ### Method `Rack::Attack.blocklist(name, &block)` ### Endpoint N/A (Configuration block) ### Parameters - **name** (string) - A descriptive name for the blocklist rule. - **block** (Proc) - A block that receives the request object and should return truthy if the request is to be blocked. ### Request Example ```ruby Rack::Attack.blocklist("block all access to admin") do |request| request.path.start_with?("/admin") end Rack::Attack.blocklist('block bad UA logins') do |req| req.path == '/login' && req.post? && req.user_agent == 'BadUA' end ``` ### Response N/A (Requests are blocked directly) ``` -------------------------------- ### Fail2Ban Integration Source: https://github.com/rack/rack-attack/blob/main/README.md Integrate with Fail2Ban logic to block IPs after a certain number of retries. ```APIDOC ## Fail2Ban Integration ### Description Use `Fail2Ban.filter` within a blocklist to automatically block IPs that exhibit suspicious behavior after a defined number of retries within a time period. ### Method `Rack::Attack::Fail2Ban.filter(discriminator, options, &block)` ### Endpoint N/A (Configuration block) ### Parameters - **discriminator** (string) - A unique identifier for the filter, typically including the IP address (e.g., `"pentesters-#{req.ip}"`). - **options** (Hash) - Configuration options: - **maxretry** (Integer) - The maximum number of retries allowed. - **findtime** (Integer) - The time window in seconds to count retries. - **bantime** (Integer) - The duration in seconds to ban the IP. - **block** (Proc) - A block that returns truthy if the request matches the suspicious pattern. The count for the IP is incremented if this block returns truthy. ### Request Example ```ruby Rack::Attack.blocklist('fail2ban pentesters') do |req| Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", maxretry: 3, findtime: 10.minutes, bantime: 5.minutes) do CGI.unescape(req.query_string) =~ %r{/etc/passwd} || req.path.include?('/etc/passwd') || req.path.include?('wp-admin') || req.path.include?('wp-login') end end ``` ### Response N/A (Requests are blocked based on Fail2Ban logic) ``` -------------------------------- ### Implement Allow2Ban Filtering Source: https://github.com/rack/rack-attack/blob/main/README.md Allows requests until a threshold is reached, then blocks the client. ```ruby # Lockout IP addresses that are hammering your login page. # After 20 requests in 1 minute, block all requests from that IP for 1 hour. Rack::Attack.blocklist('allow2ban login scrapers') do |req| # `filter` returns false value if request is to your login page (but still # increments the count) so request below the limit are not blocked until # they hit the limit. At that point, filter will return true and block. Rack::Attack::Allow2Ban.filter(req.ip, maxretry: 20, findtime: 1.minute, bantime: 1.hour) do # The count for the IP is incremented if the return value is truthy. req.path == '/login' and req.post? end end ``` -------------------------------- ### Rack::Attack Custom Safelist Source: https://github.com/rack/rack-attack/blob/main/README.md How to define a custom safelist rule using a Ruby block. ```APIDOC ### `safelist(name, &block)` Name your custom safelist and make your ruby-block argument return a truthy value if you want the request to be allowed, and falsy otherwise. The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request). E.g. ```ruby # config/initializers/rack_attack.rb (for rails apps) # Provided that trusted users use an HTTP request header named APIKey Rack::Attack.safelist("mark any authenticated access safe") do |request| # Requests are allowed if the return value is truthy request.env["HTTP_APIKEY"] == "secret-string" end # Always allow requests from localhost ``` -------------------------------- ### Enable Retry-After Header Source: https://github.com/rack/rack-attack/blob/main/README.md Enables the Retry-After header for throttled responses to inform clients when to retry. ```ruby Rack::Attack.throttled_response_retry_after_header = true ``` -------------------------------- ### Custom Throttling Source: https://github.com/rack/rack-attack/blob/main/README.md Implement custom throttling rules to limit request rates. ```APIDOC ## Custom Throttling ### Description Define custom throttling rules to limit the rate of requests based on a discriminator (e.g., IP address, user ID). Specify the `limit` (maximum requests) and `period` (time window in seconds). ### Method `Rack::Attack.throttle(name, options, &block)` ### Endpoint N/A (Configuration block) ### Parameters - **name** (string) - A descriptive name for the throttle rule. - **options** (Hash) - Configuration options: - **limit** (Integer) - The maximum number of requests allowed within the period. - **period** (Integer) - The time window in seconds for the limit. - **block** (Proc) - A block that receives the request object and should return the discriminator value (e.g., `request.ip`). ### Request Example ```ruby # Throttle requests by IP address to 5 requests per 2 seconds Rack::Attack.throttle("requests by ip", limit: 5, period: 2) do |request| request.ip end # Throttle login attempts for a given email to 6 requests/minute Rack::Attack.throttle('limit logins per email', limit: 6, period: 60) do |req| if req.path == '/login' && req.post? req.params['email'].to_s.downcase.gsub(/\s+/, "") end end ``` ### Response N/A (Requests are throttled based on the defined limits) ``` -------------------------------- ### Subscribe to Throttle Events - ActiveSupport::Notifications Source: https://context7.com/rack/rack-attack/llms.txt Subscribes to 'throttle.rack_attack' events to log throttled requests and send metrics. It logs the matched rule, IP, method, and path. ```ruby # config/initializers/rack_attack.rb # Subscribe to throttle events ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start, finish, id, payload| req = payload[:request] Rails.logger.warn "[Rack::Attack] Throttled #{req.env['rack.attack.matched']}: " \ "#{req.ip} #{req.request_method} #{req.path}" # Send to metrics service StatsD.increment("rack_attack.throttled", tags: [ "rule:#{req.env['rack.attack.matched']}", "path:#{req.path}" ]) end ``` -------------------------------- ### Block IPs from Rails.cache in Ruby Source: https://github.com/rack/rack-attack/blob/main/docs/advanced_configuration.md Configures blocklists to check IP addresses against values stored in `Rails.cache`. Allows dynamic management of blocked IPs via the cache. ```ruby # Block attacks from IPs in cache # To add an IP: Rails.cache.write("block 1.2.3.4", true, expires_in: 2.days) # To remove an IP: Rails.cache.delete("block 1.2.3.4") Rack::Attack.blocklist("block IP") do |req| Rails.cache.read("block #{req.ip}") end ``` -------------------------------- ### Rack::Attack.safelist Source: https://context7.com/rack/rack-attack/llms.txt Create custom safelist rules using a block that receives the request object. Return a truthy value to allow the request, bypassing all other checks. ```APIDOC ## Rack::Attack.safelist Create custom safelist rules using a block that receives the request object. Return a truthy value to allow the request, bypassing all other checks. ```ruby # config/initializers/rack_attack.rb # Safelist requests with a valid API key header Rack::Attack.safelist("authorized api clients") do |request| request.env["HTTP_X_API_KEY"] == ENV['INTERNAL_API_KEY'] end # Always allow requests from localhost Rack::Attack.safelist("allow from localhost") do |req| req.ip == "127.0.0.1" || req.ip == "::1" end # Safelist health check endpoints Rack::Attack.safelist("health checks") do |req| req.path == "/health" || req.path == "/ping" end # Safelist requests based on user agent Rack::Attack.safelist("internal user agent") do |req| req.user_agent == "InternalMonitoringService/1.0" end # Safelist specific controller actions in Rails Rack::Attack.safelist("unlimited requests") do |request| safelist = ["webhooks#create", "api/internal#status"] route = (Rails.application.routes.recognize_path(request.url) rescue {}) || {} action = "#{route[:controller]}##{route[:action]}" safelist.include?(action) end ``` ``` -------------------------------- ### Rack::Attack Safelist IP Subnet Source: https://github.com/rack/rack-attack/blob/main/README.md How to safelist an entire IP subnet to allow all requests from it. ```APIDOC ### `safelist_ip(ip_subnet_string)` E.g. ```ruby # config/initializers/rack_attack.rb (for rails app) Rack::Attack.safelist_ip("5.6.7.0/24") ``` ``` -------------------------------- ### Subscribe to All Rack::Attack Events Source: https://github.com/rack/rack-attack/blob/main/README.md Subscribe to all Rack::Attack events using a regular expression with ActiveSupport::Notifications. The request object is available in the payload. ```ruby ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, instrumenter_id, payload| # request object available in payload[:request] # Your code here end ``` -------------------------------- ### Rack::Attack Safelist IP Address Source: https://github.com/rack/rack-attack/blob/main/README.md How to safelist a specific IP address to allow all requests from it. ```APIDOC ## Safelisting Safelists have the most precedence, so any request matching a safelist would be allowed despite matching any number of blocklists or throttles. ### `safelist_ip(ip_address_string)` E.g. ```ruby # config/initializers/rack_attack.rb (for rails app) Rack::Attack.safelist_ip("5.6.7.8") ``` ``` -------------------------------- ### Define Custom Safelist Source: https://github.com/rack/rack-attack/blob/main/README.md Create a custom rule using a block to determine if a request should be allowed. ```ruby # config/initializers/rack_attack.rb (for rails apps) # Provided that trusted users use an HTTP request header named APIKey Rack::Attack.safelist("mark any authenticated access safe") do |request| # Requests are allowed if the return value is truthy request.env["HTTP_APIKEY"] == "secret-string" end ``` -------------------------------- ### Subscribe to Throttle Events Source: https://github.com/rack/rack-attack/blob/main/README.md Subscribe to specific Rack::Attack throttle events using ActiveSupport::Notifications. The request object is available in the payload. ```ruby ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start, finish, instrumenter_id, payload| # request object available in payload[:request] # Your code here end ``` -------------------------------- ### Monitor Requests with Track Source: https://context7.com/rack/rack-attack/llms.txt Log or monitor specific request patterns without applying blocking or throttling. ```ruby # config/initializers/rack_attack.rb # Track requests from a specific user agent Rack::Attack.track("special_agent") do |req| req.user_agent == "SpecialAgent" end ``` -------------------------------- ### Track with Limit - Rack::Attack Source: https://context7.com/rack/rack-attack/llms.txt Tracks requests and triggers notifications only when a specified limit is reached within a given period. It uses a block to define which requests to track. ```ruby Rack::Attack.track("api_heavy_users", limit: 100, period: 1.minute) do |req| req.env["HTTP_X_API_KEY"] if req.path.start_with?("/api/") end ``` -------------------------------- ### Implement Fail2Ban Blocking Source: https://context7.com/rack/rack-attack/llms.txt Block IPs that exhibit suspicious behavior after a defined retry threshold is exceeded. ```ruby # config/initializers/rack_attack.rb # Block IPs that probe for vulnerabilities # After 3 suspicious requests in 10 minutes, block all requests for 5 minutes Rack::Attack.blocklist("fail2ban pentesters") do |req| Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", maxretry: 3, findtime: 10.minutes, bantime: 5.minutes ) do # Count as suspicious if path contains common exploit patterns CGI.unescape(req.query_string) =~ %r{/etc/passwd} || req.path.include?("/etc/passwd") || req.path.include?("wp-admin") || req.path.include?("wp-login") || req.path.include?(".env") || req.path.include?("phpinfo") || req.path =~ /\.(php|asp|aspx|jsp|cgi)$/ end end # Block IPs with too many 404 errors (scanning for vulnerabilities) Rack::Attack.blocklist("fail2ban 404 scanners") do |req| Rack::Attack::Fail2Ban.filter("scanners-#{req.ip}", maxretry: 10, findtime: 1.minute, bantime: 1.hour ) do req.env["action_dispatch.exception"]&.is_a?(ActionController::RoutingError) end end # Check if an IP is currently banned # Rack::Attack::Fail2Ban.banned?("1.2.3.4") # => true/false # Reset ban for specific IP (useful for admin interface) # Rack::Attack::Fail2Ban.reset("pentesters-1.2.3.4", findtime: 10.minutes) ``` -------------------------------- ### Customize Blocklist and Throttle Responses Source: https://github.com/rack/rack-attack/blob/main/README.md Overrides default response behavior for blocked or throttled requests using the Rack app interface. ```ruby Rack::Attack.blocklisted_responder = lambda do |request| # Using 503 because it may make attacker think that they have successfully # DOSed the site. Rack::Attack returns 403 for blocklists by default [ 503, {}, ['Blocked']] end Rack::Attack.throttled_responder = lambda do |request| # NB: you have access to the name and other data about the matched throttle # request.env['rack.attack.matched'], # request.env['rack.attack.match_type'], # request.env['rack.attack.match_data'], # request.env['rack.attack.match_discriminator'] # Using 503 because it may make attacker think that they have successfully # DOSed the site. Rack::Attack returns 429 for throttling by default [ 503, {}, ["Server Error\n"]] end ``` -------------------------------- ### Manage Dynamic IP Blocklists Source: https://context7.com/rack/rack-attack/llms.txt Use Rails cache to dynamically block or unblock specific IP addresses. ```ruby # To unblock: Rails.cache.delete("block:1.2.3.4") Rack::Attack.blocklist("dynamic ip blocklist") do |req| Rails.cache.read("block:#{req.ip}") end ``` -------------------------------- ### Reset Specific Rack::Attack Throttles in Ruby Source: https://github.com/rack/rack-attack/blob/main/docs/advanced_configuration.md Adds a helper to reset specific throttles by name and identifier. This is a workaround as Rack::Attack does not natively support resetting individual throttles. ```ruby Rack::Attack.reset_throttle "logins/email", "user@example.com" ``` -------------------------------- ### Define a Safelist Source: https://github.com/rack/rack-attack/blob/main/README.md Allows requests if the block returns a truthy value. ```ruby Rack::Attack.safelist('allow from localhost') do |req| # Requests are allowed if the return value is truthy '127.0.0.1' == req.ip || '::1' == req.ip end ``` -------------------------------- ### Define Custom Blocklist Rules Source: https://context7.com/rack/rack-attack/llms.txt Implement custom blocking logic using blocks that return a truthy value for blocked requests. ```ruby # config/initializers/rack_attack.rb # Block all access to admin paths from non-internal IPs Rack::Attack.blocklist("block admin access") do |request| request.path.start_with?("/admin") && !request.ip.start_with?("10.") end # Block requests with suspicious user agents Rack::Attack.blocklist("block bad user agents") do |req| bad_agents = ["BadBot", "EvilScraper", "MaliciousCrawler"] bad_agents.any? { |agent| req.user_agent.to_s.include?(agent) } end # Block login attempts with specific characteristics Rack::Attack.blocklist("block bad login attempts") do |req| req.path == "/login" && req.post? && req.user_agent == "BadUA" end # Block based on referer spam spammers = ENV.fetch('BLOCKED_REFERERS', '').split(/,\s*/) spammer_regexp = Regexp.union(spammers) Rack::Attack.blocklist("block referer spam") do |request| request.referer =~ spammer_regexp end # Dynamic blocklist using Rails.cache # To block an IP: Rails.cache.write("block:1.2.3.4", true, expires_in: 2.days) ``` -------------------------------- ### Track Requests with Notifications Source: https://github.com/rack/rack-attack/blob/main/README.md Tracks specific request patterns and subscribes to notifications for logging or metrics. ```ruby # Track requests from a special user agent. Rack::Attack.track("special_agent") do |req| req.user_agent == "SpecialAgent" end # Supports optional limit and period, triggers the notification only when the limit is reached. Rack::Attack.track("special_agent", limit: 6, period: 60) do |req| req.user_agent == "SpecialAgent" end # Track it using ActiveSupport::Notification ActiveSupport::Notifications.subscribe("track.rack_attack") do |name, start, finish, instrumenter_id, payload| req = payload[:request] if req.env['rack.attack.matched'] == "special_agent" Rails.logger.info "special_agent: #{req.path}" STATSD.increment("special_agent") end end ``` -------------------------------- ### Throttle Basic Auth Failures in Ruby Source: https://github.com/rack/rack-attack/blob/main/docs/advanced_configuration.md Blocks IPs attempting multiple basic authentication failures within a specified time. Uses `Rack::Attack::Allow2Ban` to manage retries and ban times. ```ruby # After 5 requests with incorrect auth in 1 minute, # block all requests from that IP for 1 hour. Rack::Attack.blocklist('basic auth crackers') do |req| Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 5, :findtime => 1.minute, :bantime => 1.hour) do # Return true if the authorization header is incorrect auth = Rack::Auth::Basic::Request.new(req.env) auth.credentials != [my_username, my_password] end end ``` -------------------------------- ### Subscribe to All Rack::Attack Events - ActiveSupport::Notifications Source: https://context7.com/rack/rack-attack/llms.txt Subscribes to all Rack::Attack events using a regular expression. It logs a JSON object containing event details like type, IP, path, user agent, and matched rule. ```ruby # config/initializers/rack_attack.rb # Subscribe to all Rack::Attack events ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, id, payload| req = payload[:request] event_type = name.split(".").first Rails.logger.info({ event: "rack_attack", type: event_type, ip: req.ip, path: req.path, user_agent: req.user_agent, matched: req.env["rack.attack.matched"] }.to_json) end ``` -------------------------------- ### Implement Fail2Ban Filtering Source: https://github.com/rack/rack-attack/blob/main/README.md Blocks clients after a specified number of failed attempts within a time window. ```ruby # Block suspicious requests for '/etc/password' or wordpress specific paths. # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes. Rack::Attack.blocklist('fail2ban pentesters') do |req| # `filter` returns truthy value if request fails, or if it's from a previously banned IP # so the request is blocked Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", maxretry: 3, findtime: 10.minutes, bantime: 5.minutes) do # The count for the IP is incremented if the return value is truthy CGI.unescape(req.query_string) =~ %r{/etc/passwd} || req.path.include?('/etc/passwd') || req.path.include?('wp-admin') || req.path.include?('wp-login') end end ``` -------------------------------- ### Define Custom Blocklists Source: https://github.com/rack/rack-attack/blob/main/README.md Blocks requests when the block returns a truthy value. ```ruby # config/initializers/rack_attack.rb (for rails apps) Rack::Attack.blocklist("block all access to admin") do |request| # Requests are blocked if the return value is truthy request.path.start_with?("/admin") end Rack::Attack.blocklist('block bad UA logins') do |req| req.path == '/login' && req.post? && req.user_agent == 'BadUA' end ``` -------------------------------- ### Safelisting Requests Source: https://github.com/rack/rack-attack/blob/main/README.md Safelist requests to allow specific traffic, such as from localhost. ```APIDOC ## Safelist Requests ### Description Allows specific requests to bypass all checks. Useful for allowing trusted IPs like localhost. ### Method `Rack::Attack.safelist(name, &block)` ### Endpoint N/A (Configuration block) ### Parameters - **name** (string) - A descriptive name for the safelist rule. - **block** (Proc) - A block that receives the request object and should return truthy if the request is to be allowed. ### Request Example ```ruby Rack::Attack.safelist('allow from localhost') do |req| '127.0.0.1' == req.ip || '::1' == req.ip end ``` ### Response N/A (Requests are allowed directly) ``` -------------------------------- ### Define Custom Safelist Rules Source: https://context7.com/rack/rack-attack/llms.txt Implement complex safelist logic using blocks that return a truthy value for allowed requests. ```ruby # config/initializers/rack_attack.rb # Safelist requests with a valid API key header Rack::Attack.safelist("authorized api clients") do |request| request.env["HTTP_X_API_KEY"] == ENV['INTERNAL_API_KEY'] end # Always allow requests from localhost Rack::Attack.safelist("allow from localhost") do |req| req.ip == "127.0.0.1" || req.ip == "::1" end # Safelist health check endpoints Rack::Attack.safelist("health checks") do |req| req.path == "/health" || req.path == "/ping" end # Safelist requests based on user agent Rack::Attack.safelist("internal user agent") do |req| req.user_agent == "InternalMonitoringService/1.0" end # Safelist specific controller actions in Rails Rack::Attack.safelist("unlimited requests") do |request| safelist = ["webhooks#create", "api/internal#status"] route = (Rails.application.routes.recognize_path(request.url) rescue {}) || {} action = "#{route[:controller]}##{route[:action]}" safelist.include?(action) end ``` -------------------------------- ### Subscribe to Blocklist Events - ActiveSupport::Notifications Source: https://context7.com/rack/rack-attack/llms.txt Subscribes to 'blocklist.rack_attack' events to log blocked requests and trigger alerts. It logs the IP, method, and path of the blocked request. ```ruby # config/initializers/rack_attack.rb # Subscribe to blocklist events ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |name, start, finish, id, payload| req = payload[:request] Rails.logger.error "[Rack::Attack] Blocked: #{req.ip} #{req.request_method} #{req.path}" # Alert security team for repeated blocks SecurityAlerter.notify("IP blocked: #{req.ip}") if should_alert?(req.ip) end ``` -------------------------------- ### Safelist IP Address Source: https://github.com/rack/rack-attack/blob/main/README.md Allow requests from a specific IP address. ```ruby # config/initializers/rack_attack.rb (for rails app) Rack::Attack.safelist_ip("5.6.7.8") ``` -------------------------------- ### Subscribe to Safelist Events - ActiveSupport::Notifications Source: https://context7.com/rack/rack-attack/llms.txt Subscribes to 'safelist.rack_attack' events to log requests that have been explicitly allowed. It logs the IP address of the safelisted request. ```ruby # config/initializers/rack_attack.rb # Subscribe to safelist events ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |name, start, finish, id, payload| req = payload[:request] Rails.logger.debug "[Rack::Attack] Safelisted: #{req.ip}" end ``` -------------------------------- ### Custom Throttled Responder - Rack::Attack Source: https://context7.com/rack/rack-attack/llms.txt Customizes the response for throttled requests, including rate limit headers. It calculates the reset time based on the current time and the period. ```ruby # config/initializers/rack_attack.rb # Custom throttle response with rate limit headers Rack::Attack.throttled_responder = lambda do |request| match_data = request.env["rack.attack.match_data"] now = match_data[:epoch_time] headers = { "content-type" => "text/plain", "ratelimit-limit" => match_data[:limit].to_s, "ratelimit-remaining" => "0", "ratelimit-reset" => (now + (match_data[:period] - now % match_data[:period])).to_s } [429, headers, ["Rate limit exceeded. Retry later.\n"]] end # Enable Retry-After header automatically Rack::Attack.throttled_response_retry_after_header = true # Access match data in your application (for requests that weren't throttled) # Available in controller: request.env['rack.attack.throttle_data']['req/ip'] # Returns: { discriminator: "1.2.3.4", count: 50, period: 60, limit: 100, epoch_time: 1234567890 } ``` -------------------------------- ### Access Throttle Data Source: https://github.com/rack/rack-attack/blob/main/README.md Retrieves throttle match data from the request environment for non-throttled requests. ```ruby request.env['rack.attack.throttle_data'][name] # => { discriminator: d, count: n, period: p, limit: l, epoch_time: t } ``` -------------------------------- ### Blocklist IP Addresses Source: https://context7.com/rack/rack-attack/llms.txt Use blocklist_ip to deny access from specific IPs or subnets, returning a 403 Forbidden by default. ```ruby # config/initializers/rack_attack.rb # Block a single malicious IP Rack::Attack.blocklist_ip("1.2.3.4") # Block an entire subnet Rack::Attack.blocklist_ip("1.2.0.0/16") # Block known bad IP ranges %w[ 185.130.44.0/24 91.200.12.0/24 37.0.10.0/24 ].each do |ip_range| Rack::Attack.blocklist_ip(ip_range) end # Block IPs loaded from an external source File.readlines("blocked_ips.txt").each do |line| ip = line.strip Rack::Attack.blocklist_ip(ip) unless ip.empty? || ip.start_with?("#") end ``` -------------------------------- ### Safelist IP Subnet Source: https://github.com/rack/rack-attack/blob/main/README.md Allow requests from an entire IP subnet. ```ruby # config/initializers/rack_attack.rb (for rails app) Rack::Attack.safelist_ip("5.6.7.0/24") ```