
Public PoC and detector for CVE-2026-20896 ("Gitea Docker: One Header, Any User")
Gitea's official Docker image (up to and including 1.26.2) ships
REVERSE_PROXY_TRUSTED_PROXIES = * in its default config. If you turn on
reverse-proxy login, that wildcard means every source IP is treated as a trusted
proxy, so anyone who can reach the port can send an X-WEBAUTH-USER header and
get logged in as whoever they want. No password, no token.
I reported this to Gitea and it's fixed in 1.26.3 / 1.26.4. This repo is my own writeup, a working PoC, and a checker. It's for a patched, public bug.
Gitea supports reverse-proxy authentication: you put it behind a proxy that sets
X-WEBAUTH-USER, and Gitea trusts that header for the username. That's fine as
long as only your proxy can set it. The setting that's supposed to enforce that
is REVERSE_PROXY_TRUSTED_PROXIES, an IP allowlist. Gitea only honors the header
when the request's source IP falls inside it.
The documented-safe default, the one in app.example.ini, is
127.0.0.0/8,::1/128: loopback only, so out of the box just the local proxy is
trusted. The official Docker image doesn't use that. Its app.ini template
hard-codes * (docker/root/etc/templates/app.ini:55, and
docker/rootless/etc/templates/app.ini:52 for the rootless image). * matches
every source IP, so the allowlist check does nothing. Turn on reverse-proxy login
and now anyone who can reach the port can send the header, not just your proxy.
With auto-registration on, the account is created on the spot. Send an admin's
username and you're the admin.
So the code isn't wrong, the packaged default is, and it's specific to the Docker
images. A binary or self-built install that follows app.example.ini keeps the
loopback default and isn't affected.
Needs Docker and Python 3 (standard library only, nothing to install).
docker compose up -d # boots vulnerable gitea/gitea:1.26.2
# give it ~30-60s to finish first-run setup, then:
python3 poc.py # random new victim, shows auto-registration
python3 poc.py http://localhost:3000 admin # impersonate a chosen username
docker compose down -v # clean up
What it looks like against the bundled image:
1) /user/settings with no header -> HTTP 303 (redirect to login = not authed)
2) /user/settings with X-WEBAUTH-USER -> HTTP 200
logged in as 'pocadmin' - no password, no token, any source IP
3) /pocadmin profile page -> HTTP 200 (account created on the fly)
The bypass works on the web session, not the token API at /api/v1/..., which
ignores the header.
detect.py sends one harmless probe and compares it to a normal request. It
doesn't touch anything.
python3 detect.py https://gitea.example.com
It prints VULNERABLE, looks-safe, or inconclusive. Only run it against something you own or are allowed to test.
Upgrade to 1.26.3 / 1.26.4 or later. Reverse-proxy auth is opt-in now and the
image no longer ships the wildcard. If you can't upgrade yet, set
REVERSE_PROXY_TRUSTED_PROXIES to your proxy's actual IP or CIDR (never *), or
turn off ENABLE_REVERSE_PROXY_AUTHENTICATION if you don't use it.
I found this bug and reported it to Gitea on 2026-05-26. I'm the reporter named in Gitea's advisory, GHSA-f75j-4cw6-rmx4.
Some coverage credited the Exploitarium repo for this instead of me. That's wrong, and it's a separate thing from what that repo published. I've since gotten several of those blogs corrected; a few are still wrong.
MIT licensed. See LICENSE.