consumeAuthAttempt runs before the password check (auth/index.ts:88-90). Ten attempts from any address block that username for 15 minutes, even with the correct password. auth-users.test.ts:356 asserts this behaviour.
Behind the reverse proxy the README recommends, every request appears to come from the proxy's IP, because compose doesn't pass ADDRESS_HEADER/XFF_DEPTH. The 30-per-IP limit becomes one shared limit for everybody.
reset-admin.mjs doesn't clear auth_rate_limits, so the recovery script doesn't get you back in either.
Fix
Pass the client-address settings (ADDRESS_HEADER, XFF_DEPTH) through compose
Apply the hard limit per account-and-IP pair
Let a device cookie from an earlier successful login skip the per-account limit (OWASP's recommended defense)
Clear the user's rate limits in reset-admin.mjs
Update auth-users.test.ts:356 to match
- `consumeAuthAttempt` runs before the password check (`auth/index.ts:88-90`). Ten attempts from any address block that username for 15 minutes, even with the correct password. `auth-users.test.ts:356` asserts this behaviour.
- Behind the reverse proxy the README recommends, every request appears to come from the proxy's IP, because compose doesn't pass `ADDRESS_HEADER`/`XFF_DEPTH`. The 30-per-IP limit becomes one shared limit for everybody.
- `reset-admin.mjs` doesn't clear `auth_rate_limits`, so the recovery script doesn't get you back in either.
**Fix**
- [x] Pass the client-address settings (`ADDRESS_HEADER`, `XFF_DEPTH`) through compose
- [x] Apply the hard limit per account-and-IP pair
- [x] Let a device cookie from an earlier successful login skip the per-account limit (OWASP's recommended defense)
- [x] Clear the user's rate limits in `reset-admin.mjs`
- [x] Update `auth-users.test.ts:356` to match
Reviewed 4501eba873. All five fix items are done, so I'm closing this.
Checklist
Pass client-address settings through Compose. Compose now forwards ADDRESS_HEADER and XFF_DEPTH. The example environment and setup/authentication guides explain trusted proxy header replacement, right-side hop selection, restricting direct controller access, and recreating the controller after configuration changes. The documented behavior matches the installed Node adapter.
Limit each account/address pair. Login uses a normalized username/address key instead of the old account-wide bucket. Exhausting one pair does not block the same account at another address, including when a legacy account-only bucket remains. The existing shared address ceiling and separate setup/password-change limits remain in place.
Recognize a previously successful login device. Successful password authentication issues a separate account-bound, hashed, 30-day device token. A valid proof skips only the pair limit; the password and shared address limit still apply. Invalid, expired, cross-account and session tokens do not qualify. Reuse preserves the original expiry. The Secure/HttpOnly/SameSite cookie is independent of remembered sessions, survives ordinary logout and is revoked on password/access changes or session-wide revocation.
Clear account limits during local recovery.reset-admin.mjs atomically resets the password, revokes sessions/device proofs, clears the selected account's new and legacy login buckets and password-change bucket, and records the recovery. It preserves other accounts and shared address buckets, supports databases without the device table, and rolls everything back if audit recording fails. Interactive-only secret entry remains enforced.
Update regression expectations.auth-users.test.ts now checks pair-local exhaustion, another-address success, legacy buckets, case normalization, persisted windows and expiry. New device, recovery and schema tests cover the related behavior and migration 28 without rewriting existing authentication data.
Review notes
Combined #63/#98/#112 validation at this HEAD: all 403 tests across 17 relevant files passed across the focused runs. The initial recovery CLI test hit sandbox spawnSync EPERM; rerunning its unchanged five-test suite with child-process execution allowed passed. Migration-runner checks also passed.
npm run check passed with zero Svelte errors or warnings; npm run lint passed. No application files were changed during review.
The deployment must opt into the appropriate client-address header and depth for its actual trusted proxy chain. This review verified configuration support and documentation, not production proxy settings.
Reviewed 4501eba8735ceacccc924138502ed424b3310020. All five fix items are done, so I'm closing this.
**Checklist**
- [x] **Pass client-address settings through Compose.** Compose now forwards `ADDRESS_HEADER` and `XFF_DEPTH`. The example environment and setup/authentication guides explain trusted proxy header replacement, right-side hop selection, restricting direct controller access, and recreating the controller after configuration changes. The documented behavior matches the installed Node adapter.
- [x] **Limit each account/address pair.** Login uses a normalized username/address key instead of the old account-wide bucket. Exhausting one pair does not block the same account at another address, including when a legacy account-only bucket remains. The existing shared address ceiling and separate setup/password-change limits remain in place.
- [x] **Recognize a previously successful login device.** Successful password authentication issues a separate account-bound, hashed, 30-day device token. A valid proof skips only the pair limit; the password and shared address limit still apply. Invalid, expired, cross-account and session tokens do not qualify. Reuse preserves the original expiry. The Secure/HttpOnly/SameSite cookie is independent of remembered sessions, survives ordinary logout and is revoked on password/access changes or session-wide revocation.
- [x] **Clear account limits during local recovery.** `reset-admin.mjs` atomically resets the password, revokes sessions/device proofs, clears the selected account's new and legacy login buckets and password-change bucket, and records the recovery. It preserves other accounts and shared address buckets, supports databases without the device table, and rolls everything back if audit recording fails. Interactive-only secret entry remains enforced.
- [x] **Update regression expectations.** `auth-users.test.ts` now checks pair-local exhaustion, another-address success, legacy buckets, case normalization, persisted windows and expiry. New device, recovery and schema tests cover the related behavior and migration 28 without rewriting existing authentication data.
**Review notes**
- Combined #63/#98/#112 validation at this HEAD: all 403 tests across 17 relevant files passed across the focused runs. The initial recovery CLI test hit sandbox `spawnSync EPERM`; rerunning its unchanged five-test suite with child-process execution allowed passed. Migration-runner checks also passed.
- `npm run check` passed with zero Svelte errors or warnings; `npm run lint` passed. No application files were changed during review.
- The deployment must opt into the appropriate client-address header and depth for its actual trusted proxy chain. This review verified configuration support and documentation, not production proxy settings.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
consumeAuthAttemptruns before the password check (auth/index.ts:88-90). Ten attempts from any address block that username for 15 minutes, even with the correct password.auth-users.test.ts:356asserts this behaviour.ADDRESS_HEADER/XFF_DEPTH. The 30-per-IP limit becomes one shared limit for everybody.reset-admin.mjsdoesn't clearauth_rate_limits, so the recovery script doesn't get you back in either.Fix
ADDRESS_HEADER,XFF_DEPTH) through composereset-admin.mjsauth-users.test.ts:356to matchReviewed
4501eba873. All five fix items are done, so I'm closing this.Checklist
ADDRESS_HEADERandXFF_DEPTH. The example environment and setup/authentication guides explain trusted proxy header replacement, right-side hop selection, restricting direct controller access, and recreating the controller after configuration changes. The documented behavior matches the installed Node adapter.reset-admin.mjsatomically resets the password, revokes sessions/device proofs, clears the selected account's new and legacy login buckets and password-change bucket, and records the recovery. It preserves other accounts and shared address buckets, supports databases without the device table, and rolls everything back if audit recording fails. Interactive-only secret entry remains enforced.auth-users.test.tsnow checks pair-local exhaustion, another-address success, legacy buckets, case normalization, persisted windows and expiry. New device, recovery and schema tests cover the related behavior and migration 28 without rewriting existing authentication data.Review notes
spawnSync EPERM; rerunning its unchanged five-test suite with child-process execution allowed passed. Migration-runner checks also passed.npm run checkpassed with zero Svelte errors or warnings;npm run lintpassed. No application files were changed during review.