CVE-2026-77561 Overview
CVE-2026-77561 is a denial-of-service vulnerability in Tinyauth, an authentication and authorization server. Versions prior to 5.1.0 allow an unauthenticated remote attacker to trigger a global login lockdown by submitting POST /api/user/login requests with 257 distinct nonexistent usernames. Once the internal MaxLoginAttemptRecords map reaches capacity, the server enters lockdownMode and returns HTTP 429 to all subsequent login attempts, including those from legitimate users. The lockdown persists for the duration of auth.loginTimeout, roughly 300 seconds by default, and can be repeated indefinitely. The issue is classified under [CWE-307] Improper Restriction of Excessive Authentication Attempts and is fixed in Tinyauth 5.1.0.
Critical Impact
An unauthenticated attacker can lock out all valid users of a Tinyauth deployment for approximately 300 seconds per attack cycle, disrupting authentication to any downstream service protected by Tinyauth.
Affected Products
- Tinyauth versions prior to 5.1.0
- Deployments using default auth.loginTimeout (300 seconds)
- Any downstream applications relying on Tinyauth for authentication
Discovery Timeline
- 2026-09-21 - CVE-2026-77561 published to NVD
- 2026-09-24 - Last updated in NVD database
Technical Details for CVE-2026-77561
Vulnerability Analysis
The flaw exists in Tinyauth's rate limiting subsystem. The loginHandler function in internal/controller/user_controller.go accepts an attacker-supplied Username field and forwards it to RecordLoginAttempt in internal/service/auth_service.go regardless of whether the account exists. Each unique identifier consumes one slot in a bounded map tracking failed attempts. When the map reaches its MaxLoginAttemptRecords cap of 256 entries, the 257th distinct username triggers lockdownMode, a global state flag intended as a brute-force defense.
The check in IsAccountLocked evaluates this global state before validating any specific account, so once lockdown activates, every subsequent login request receives HTTP 429 with a Too many failed login attempts message. Existing authenticated sessions remain valid, but no user can log in until the timeout expires. The attack requires no authentication, no user interaction, and only network reachability to the login endpoint.
Root Cause
The root cause is a design flaw in how per-account rate limiting escalates into global lockdown. The system tracks failed attempts in a fixed-size map keyed on attacker-controlled input and treats map saturation as evidence of a systemic brute-force attack. Because unauthenticated identifiers are recorded without validation, an attacker controls when the cap is reached and can weaponize the defensive mechanism to deny service to legitimate users.
Attack Vector
An attacker sends 257 POST /api/user/login requests to the target Tinyauth instance, each with a distinct random username and any password value. No credentials, session tokens, or prior access are required. Once lockdown activates, legitimate authentication is blocked for approximately 300 seconds. The attacker can loop this behavior indefinitely to maintain a persistent outage window.
// Vulnerable flow in internal/controller/user_controller.go (pre-5.1.0)
// Every attempted username, including nonexistent ones, is recorded:
controller.auth.DummyPasswordCheck(req.Password)
controller.log.App.Warn().Str("username", req.Username).Msg("User not found during login attempt")
controller.auth.RecordLoginAttempt(req.Username, false)
// After 257 distinct usernames, IsAccountLocked returns true globally,
// causing HTTP 429 for all users until auth.loginTimeout expires.
Source: GitHub Commit dade1e2
Detect Identity Attacks the Moment Authentication Fails
Singularity™ Identity surfaces credential misuse, lateral movement, and privilege escalation in real time, so you can contain the breach before it spreads.
Stop Identity AttacksDetection Methods for CVE-2026-77561
Indicators of Compromise
- High volume of POST /api/user/login requests from a single source containing many distinct, random, or nonexistent usernames within a short time window.
- HTTP 429 responses containing the header x-tinyauth-lock-locked: true served to legitimate users.
- Repeated audit log entries with reason user not found followed by account locked events across unrelated accounts.
Detection Strategies
- Baseline the normal ratio of failed-to-successful logins per source IP and alert on sudden spikes of failed attempts with unique usernames.
- Correlate the appearance of the x-tinyauth-lock-locked response header with authentication outages reported by downstream services.
- Monitor Tinyauth application logs for bursts of RecordLoginAttempt calls tied to distinct, nonexistent identifiers.
Monitoring Recommendations
- Ingest Tinyauth audit and application logs into a centralized log store and alert on account locked events not associated with a real user.
- Track POST /api/user/login request rates at the reverse proxy or WAF layer and flag deviations from baseline.
- Alert when the auth.loginTimeout window is triggered more than once within a rolling hour, indicating repeated abuse.
How to Mitigate CVE-2026-77561
Immediate Actions Required
- Upgrade Tinyauth to version 5.1.0 or later, which reworks the rate limit logic and removes the global lockdown pathway.
- Restrict access to the /api/user/login endpoint using upstream rate limiting on a reverse proxy or WAF.
- Review authentication logs for prior evidence of lockout abuse and document any outages caused by this class of activity.
Patch Information
The fix ships in Tinyauth v5.1.0. Two changes are relevant: commit 654b5cc tightens lockdown limits to shrink the DoS window (PR #943), and commit dade1e2 reworks the rate limit logic to remove the pre-validation IsAccountLocked check and the unconditional RecordLoginAttempt call for unknown users (PR #1008). Full advisory: GHSA-9xhm-w3wj-xhqh.
// Patch in internal/controller/user_controller.go (dade1e2)
// Removed: pre-validation global lockdown check and RecordLoginAttempt
// for users that do not exist.
- isLocked, remaining := controller.auth.IsAccountLocked(req.Username)
- if isLocked {
- // ... returns 429 globally ...
- }
- controller.auth.RecordLoginAttempt(req.Username, false)
Source: GitHub Commit dade1e2
Workarounds
- Place Tinyauth behind a reverse proxy that enforces per-source-IP rate limits on /api/user/login before requests reach the application.
- Reduce TINYAUTH_AUTH_LOGINTIMEOUT to shorten the lockout window until an upgrade to 5.1.0 is possible, accepting the trade-off in brute-force resistance.
- Block traffic from source IPs generating high volumes of failed logins with nonexistent usernames at the network edge.
# Example environment configuration for a Tinyauth deployment on v5.1.0+
TINYAUTH_AUTH_LOGINTIMEOUT=300
TINYAUTH_AUTH_LOGINMAXRETRIES=3
# Note: TINYAUTH_AUTH_LOCKDOWNENABLED was removed in the fix;
# rate limit logic is now handled internally without the global flag.
TINYAUTH_AUTH_TRUSTEDPROXIES=10.0.0.0/8
Source: GitHub Commit dade1e2
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

