rack/rack-attack · error · ArgumentError

Must pass maxretry option

Error message

Must pass maxretry option

What it means

Fail2Ban.filter's third mandatory option is :maxretry - the number of failures within findtime that trigger a ban. 'maxretry = options[:maxretry] or raise ArgumentError' fires when the key is missing or nil/false, because fail! compares cache.count(...) >= maxretry and banning on nil would never (or always) trigger. The check happens before banned?/yield, so the very first filtered request raises.

Source

Thrown at lib/rack/attack/fail2ban.rb:10

# frozen_string_literal: true

module Rack
  class Attack
    class Fail2Ban
      class << self
        def filter(discriminator, options)
          bantime   = options[:bantime]   or raise ArgumentError, "Must pass bantime option"
          findtime  = options[:findtime]  or raise ArgumentError, "Must pass findtime option"
          maxretry  = options[:maxretry]  or raise ArgumentError, "Must pass maxretry option"

          if banned?(discriminator)
            # Return true for blocklist
            true
          elsif yield
            fail!(discriminator, bantime, findtime, maxretry)
          end
        end

        def reset(discriminator, options)
          findtime = options[:findtime] or raise ArgumentError, "Must pass findtime option"
          cache.reset_count("#{key_prefix}:count:#{discriminator}", findtime)
          # Clear ban flag just in case it's there
          cache.delete("#{key_prefix}:ban:#{discriminator}")
        end

        def banned?(discriminator)
          cache.read("#{key_prefix}:ban:#{discriminator}") ? true : false

View on GitHub (pinned to b771ea18af)

Solutions

  1. Pass maxretry with the other two: Rack::Attack::Fail2Ban.filter(req.ip, bantime: 3600, findtime: 600, maxretry: 5) { ... }.
  2. Use one shared frozen options hash for all fail2ban call sites so the trio stays complete.
  3. Coerce ENV values eagerly: maxretry: Integer(ENV.fetch('MAXRETRY', '5')) so nil never reaches the API.
  4. Double-check exact key names (:bantime, :findtime, :maxretry) when porting examples from other libraries (the real fail2ban daemon uses different names like bantime.findtime/maxretry in INI syntax, which invites typos).

Example fix

# before
Rack::Attack::Fail2Ban.filter(req.ip, bantime: 3600, findtime: 600) { req.post? && req.path == '/login' && bad_credentials?(req) }
# => ArgumentError: Must pass maxretry option

# after
Rack::Attack::Fail2Ban.filter(req.ip, bantime: 3600, findtime: 600, maxretry: 5) { req.post? && req.path == '/login' && bad_credentials?(req) }
Defensive patterns

Strategy: validation

Validate before calling

opts = { bantime: 3600, findtime: 600, maxretry: 5 }
raise ArgumentError, 'maxretry required' unless opts[:maxretry].to_i.positive?
Rack::Attack::Fail2Ban.filter(ip, opts) { ... }

Type guard

def fail2ban_filter_args_valid?(ip, opts)
  ip.is_a?(String) && !ip.empty? &&
    %i[bantime findtime maxretry].all? { |k| opts[k].is_a?(Integer) && opts[k].positive? }
end

Try / catch

begin
  Rack::Attack::Fail2Ban.filter(ip, opts) { suspicious?(req) }
rescue ArgumentError => e
  raise unless e.message.include?('Must pass')
  notify_ops("fail2ban option missing: #{e.message}")
  false
end

Prevention

When it happens

Trigger: Rack::Attack::Fail2Ban.filter(ip, bantime: 3600, findtime: 600) { ... } - maxretry omitted - evaluated from a blocklist on the first matching request; maxretry: nil from config like ENV['MAXRETRY'] unset; key typos (max_retry:, retries:). Direct invocation in tests: Fail2Ban.filter('1.2.3.4', {bantime: 1, findtime: 1}) { true }.

Common situations: Partial copy of the fail2ban snippet from docs/guides; moving hard-coded options into per-environment config where one key is absent in one env; nil-sensitivity surprises: 0 or false for maxretry also raise (falsy), though 1+ integers pass; upgrading rack-attack versions where option enforcement got stricter.

Related errors


AI-assisted analysis of rack/rack-attack@b771ea18af (2026-08-21). Data as JSON: /api/errors/3a8c17bf01ec91da. Report an issue: GitHub.