Brute-Force Protection
Relevant source files
The following files were used as context for generating this wiki page:
Hodor protects the single shared password gate from automated discovery and credential stuffing through multi-layered brute-force protection. This system tracks login attempts per client IP in memory and applies escalating penalties to suspicious traffic.
The protection mechanism resides primarily within the auth module and relies on shared state in AppState to maintain a registry of login records across asynchronous request handlers.
Sources: AGENTS.md, src/state.rs:38
Core Defense Mechanisms
Hodor implements four primary strategies to mitigate brute-force attacks: per-IP rate limiting, exponential lockout, artificial response delays, and strict request body constraints.
Rate Limiting and Lockouts
The system enforces a sliding-window rate limit and an escalating lockout policy for consecutive failures.
- Rate Limiting: Limits clients to 5 login attempts within any 60-second window.
- Escalating Lockouts: Triggers a lockout after 10 consecutive failed attempts. The initial lockout lasts 60 seconds and doubles with each subsequent failure, capped at 1 hour.
- State Management: Hodor stores these records in an in-memory
HashMap<IpAddr, LoginRecord>protected by anArc<Mutex<_>>. To prevent memory exhaustion, the system caps the registry at 10,000 tracked IPs, evicting the oldest entries when the limit is reached.
Sources: AGENTS.md, README.md, src/auth.rs:104-106
Response Delay and Timeouts
Hodor intentionally slows down the feedback loop for attackers and prevents resource exhaustion.
- Failure Delay: Every failed login attempt incurs a mandatory 500ms delay before the server responds. This slows down automated guessing tools.
- Body Deadline: The server requires the complete login form body to arrive within 10 seconds.
- Size Limit: The system rejects login form bodies exceeding 16 KiB.
Sources: src/auth.rs:32, src/tests/auth/form.rs:51-64, README.md
Login Request Flow
When a POST request arrives at /_gate/login, Hodor executes a sequence of checks before performing the cryptographic password comparison.
The diagram shows the logical progression of a login attempt, highlighting the early-exit points for rate limiting and body validation.
Sources: src/auth.rs:59-99, README.md
Client Identification Logic
Accurate brute-force protection depends on correctly identifying the client's IP address, especially when Hodor operates behind other proxies.
Trust Configuration
- Standard Behavior: Uses the TCP peer address as the client IP.
- Proxy Trust: If
TRUST_PROXYis set totrue, Hodor parses theX-Forwarded-Forheader. - Header Resolution: The system extracts the rightmost entry from
X-Forwarded-For, which represents the client address recorded by the immediate upstream proxy. - CIDR Restriction: The
TRUSTED_PROXY_CIDRSsetting allows administrators to restrict which proxy networks are permitted to supply forwarding headers.
Sources: AGENTS.md, README.md, src/config.rs:114-118
Configuration Summary
The following settings control the behavior and enforcement of brute-force protections.
| Option | Env Var | Default | Description |
|---|---|---|---|
trust_proxy | TRUST_PROXY | false | Enables parsing of X-Forwarded-For for IP resolution. |
trusted_proxy_cidrs | TRUSTED_PROXY_CIDRS | [] | List of proxy IPs allowed to send forwarding headers. |
bypass_cidrs | BYPASS_CIDRS | [] | Networks that skip the password gate entirely. |
| - | FAILED_LOGIN_DELAY | 500ms | Internal constant for response delay. |
| - | MAX_LOGIN_BODY_SIZE | 16 KiB | Internal constant for login form size. |
| Sources: README.md, src/auth.rs:32, src/tests/auth/form.rs:13-14 |
Implementation Detail: Constant-Time Comparison
Hodor uses constant-time comparison to prevent timing attacks that could reveal information about the correct password. The comparison does not exit early when a mismatch is found.
// Sources: [src/auth.rs:80-86]
if !bool::from(password.as_bytes().ct_eq(state.password.as_slice())) {
if let Some(lockout) = record_login_failure(&state, client_ip) {
warn!(client_ip = %client_ip, lockout_secs = lockout.as_secs(), "locking out ip after repeated failed logins");
}
info!(client_ip = %client_ip, success = false, rate_limited = false, "login attempt");
tokio::time::sleep(FAILED_LOGIN_DELAY).await;
return login_page_response(&state, true);
}
Sources: src/auth.rs:80-86
Conclusion
Brute-force protection in Hodor is a self-contained, in-memory system that balances security with operational simplicity. By combining rate limiting, exponential lockouts, and artificial delays, it makes online password guessing computationally expensive while remaining resource-efficient through capped tracking and strict request body validation.