rack/rack-attack · error · ArgumentError
Must pass findtime option
Error message
Must pass findtime option
What it means
Fail2Ban.filter requires :findtime among its mandatory options - it is the sliding window (in seconds) during which failed attempts are counted toward maxretry. The line 'findtime = options[:findtime] or raise ArgumentError' fires when the key is absent or nil/false. Without findtime the retry counter (cache.count with period findtime) could not bucket counts, so the gem refuses to guess.
Source
Thrown at lib/rack/attack/fail2ban.rb:9
# 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)View on GitHub (pinned to b771ea18af)
Solutions
- Add the window: Rack::Attack::Fail2Ban.filter(req.ip, bantime: 3600, findtime: 600, maxretry: 5) { ... }.
- Centralize the triple in one constant (FAIL2BAN_OPTS = {bantime: 3600, findtime: 600, maxretry: 5}.freeze) and reuse it for every filter call so one key cannot go missing.
- Validate ENV-backed values at boot: findtime: Integer(ENV.fetch('FINDTIME', '600')).
- Spell-check keys against the API: exactly :bantime, :findtime, :maxretry.
Example fix
# before
Rack::Attack::Fail2Ban.filter(req.ip, bantime: 3600, maxretry: 5) { unauthorized?(req) }
# => ArgumentError: Must pass findtime option
# after
Rack::Attack::Fail2Ban.filter(req.ip, bantime: 3600, findtime: 600, maxretry: 5) { unauthorized?(req) } Defensive patterns
Strategy: validation
Validate before calling
missing = %i[bantime findtime maxretry].reject { |k| fail2ban_opts[k] }
raise "fail2ban opts incomplete: #{missing.inspect}" unless missing.empty?
Rack::Attack::Fail2Ban.filter(req.ip, fail2ban_opts) { ... } Type guard
def fail2ban_opts_complete?(opts)
opts.is_a?(Hash) && %i[bantime findtime maxretry].all? { |k| opts[k].is_a?(Integer) && opts[k].positive? }
end Try / catch
begin
Rack::Attack::Fail2Ban.filter(ip, opts) { ... }
rescue ArgumentError => e
raise unless e.message =~ /Must pass :?(bantime|findtime|maxretry) option/
log_and_alert("fail2ban misconfigured: #{e.message}")
false
end Prevention
- Centralize fail2ban options in one config object loaded from a single YAML/ENV mapping with a schema check at boot.
- Test each environment's config (dev/staging/prod) loads complete options - not just dev.
- Use Integer(...) coercion with defaults for every numeric option.
- Keep filter and reset call sites referencing the same shared constants.
When it happens
Trigger: Calling Rack::Attack::Fail2Ban.filter(ip, bantime: 3600, maxretry: 5) { ... } inside a blocklist, omitting findtime; passing findtime: nil (e.g. ENV['FINDTIME'] unset and coerced with to_i on nil-safe chain that returns nil); misspelling the key (find_time:, window:). Raises on the first request whose blocklist block runs.
Common situations: Trimmed-down copy of the README fail2ban example; config refactors that moved the three options into a YAML/ENV hash and lost one key; version drift from older examples that used positional or differently-named arguments; building the options hash conditionally (only adding findtime in some branch).
Related errors
- Must pass bantime option
- Must pass maxretry option
- Must pass #{opt.inspect} option
- Rack::Attack::MissingStoreError
AI-assisted analysis of rack/rack-attack@b771ea18af (2026-08-21).
Data as JSON: /api/errors/e685b04cb5ff7540.
Report an issue: GitHub.