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 : falseView on GitHub (pinned to b771ea18af)
Solutions
- Pass maxretry with the other two: Rack::Attack::Fail2Ban.filter(req.ip, bantime: 3600, findtime: 600, maxretry: 5) { ... }.
- Use one shared frozen options hash for all fail2ban call sites so the trio stays complete.
- Coerce ENV values eagerly: maxretry: Integer(ENV.fetch('MAXRETRY', '5')) so nil never reaches the API.
- 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
- Never assemble the options hash inline at multiple call sites; one frozen constant prevents drift.
- Add a boot spec: expect { Fail2Ban.filter('spec-ip', CONFIG) { true } }.not_to raise_error.
- Coerce and default ENV numerics with Integer(ENV.fetch('MAXRETRY', '5')).
- Remember falsy values (nil/false) raise like missing keys - treat 0/nil retries as config errors.
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
- Must pass bantime option
- Must pass findtime option
- Rack::Attack::MissingStoreError
- Must pass #{opt.inspect} option
AI-assisted analysis of rack/rack-attack@b771ea18af (2026-08-21).
Data as JSON: /api/errors/3a8c17bf01ec91da.
Report an issue: GitHub.