CSIRTS // UNIFIED SECURITY ADVISORY FEEDSYS ● ONLINE · POWERED BY INTELFUSIONS.COM

GHSA-vrhc-jjfc-m3m3: Gitea: OAuth2 sign-in reactivates an administrator-deactivated account on auth sources without refresh tokens (incomplete fix of #38009)

highCVSS 8.1CVE-2026-55987
Description Gitea's OAuth2 sign-in callback reactivates a deactivated user account (IsActive=false) when the user signs in through an authentication source that does not issue refresh tokens (notably GitHub, and any OIDC/OAuth2 source configured without offline_access). PR #38009 added a gate intended to reactivate users only when the OAuth2 auto-sync cron had disabled them, using "the stored refresh token is empty" as the signal. That signal is wrong: for sources that never issue refresh tokens, an empty refresh token is the normal state of every user, so the gate cannot distinguish a cron-disabled account from one an administrator deliberately deactivated. The next time the administrator-deactivated user signs in through the provider, Gitea sets IsActive=true and grants a full session, silently undoing the administrator's action. This is the exact behavior #38009 was written to prevent. (ProhibitLogin, the hard ban, is enforced separately and is not affected.) No special privileges are required beyond being the deactivated user and being able to sign in through the source. Root Cause routers/web/auth/oauth.go (the handleOAuth2SignIn reactivation gate): if !u.IsActive { extLogin, hasExt, err := user_model.GetExternalLogin(ctx, authSource.ID, gothUser.UserID) if err != nil { ctx.ServerError("GetExternalLogin", err); return } isDisabledByAutoSync := hasExt && extLogin.RefreshToken == "" // wrong signal if isDisabledByAutoSync { opts.IsActive = optional.Some(true) // reactivates the account } } The assumption that RefreshToken == "" is produced only by the auto-sync cron is false: - The cron's disable path is unreachable for sources without refresh tokens. services/auth/source/oauth2/source_sync.go returns early: if !provider.RefreshTokenAvailable() { return ... }, so it never disables (or touches the tokens of) such users. - The stored token is exactly what the provider returned, with no synthesizing: services/externalaccount/user.go stores RefreshToken: gothU

Details

Source
GitHub Security Advisories (INTL · database · site)
Severity
high — CVSS 8.1
Published
2026-07-21
Last updated
2026-07-21
Exploitation
Not in CISA KEV at last sync

Original advisory: https://github.com/advisories/GHSA-vrhc-jjfc-m3m3

Referenced CVEs

CVECSIRTS overviewExternal
CVE-2026-55987coverage & exploitation statusNVD · CVE.org

Same CVEs, other sources

How other CERTs, PSIRTs and databases cover the vulnerabilities in this advisory.

More from GitHub Security Advisories