Skip to content

πŸ›‘οΈ Sentinel: [HIGH] Throttle current-password guesses on /account/password - #136

Merged
jnnngs merged 1 commit into
mainfrom
claude/magical-carson-pmple0
Nov 6, 2026
Merged

jnnngs merged 1 commit into
mainfrom
claude/magical-carson-pmple0

Conversation

@jnnngs

@jnnngs jnnngs commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

🚨 Severity

High β€” session-theft escalation to account takeover.

πŸ’‘ Vulnerability

/account/password re-authenticates the caller by hashing a submitted current password, but had no rate limit on failures. The login form has had loginLimiter since local accounts existed; the password-change form does not share it. A session held by somebody who should not have it (device left unlocked, exfiltrated cookie, shared browser) was therefore an unlimited oracle for the account's current password.

🎯 Impact

Once the current password is guessed, the attacker sets a new one. SetPassword deletes every other live session for the account, so the rightful owner is signed out of their own console and locked out until an administrator resets it β€” if there is a second administrator at all. On a single-admin deployment this is a full account loss.

πŸ”§ Fix

doChangePassword now asks the shared loginLimiter before verifying the current password:

  • Keys change:user:<lower(username)> and change:ip:<client>, distinct prefixes from the sign-in limiter so a password-change bucket cannot also lock legitimate login attempts for the same account or IP.
  • RecordFailure on a wrong current password (same semantics as doLogin).
  • Reset once the change succeeds, so legitimate re-changes are never blocked by old failures.
  • Throttled callers get 429 Too Many Requests, matching the login form's shape, and the refusal is logged.

No behavior change for AUTH_MODE=none (the whole /account/password route is gated on separatesUsers(), which is false there). The default open-API deployment is untouched.

βœ… Verification

  • gofmt -l authlogin.go auth_test.go β€” clean.
  • go vet ./... β€” clean.
  • go test -short ./... β€” all pass.
  • New TestChangePasswordIsThrottledOnWrongCurrentPassword exercises the 429 after loginFreeAttempts wrong guesses, and that a correct password is also refused while the lockout stands.

Diff: 16 production lines, 34 test lines (50 total).


Generated by Claude Code

The sign-in form has throttled wrong-password guesses since local accounts
existed; the password-change form had no such limit. That left a stolen
session as an unlimited guess at the current password, and a correct one
is what lets an attacker set a new password and lock the owner out of
their own account.

The limiter is the same one the sign-in form uses, with distinct key
prefixes so a password-change bucket does not also lock legitimate login
attempts from the same address or account.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MNNzqJv2o2hxYWojiGqSs
@jnnngs
jnnngs marked this pull request as ready for review November 6, 2026 19:36
@jnnngs
jnnngs merged commit f6e2f71 into main Nov 6, 2026
3 checks passed
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants