Auth bypass with one header, explained by building the bug
In July 2026 attackers began exploiting CVE-2026-20896 in Gitea: a single crafted HTTP header let them bypass authentication and reach private repositories and secrets. A CVSS-critical bug, and the underlying mistake is one that shows up in ordinary application code all the time: trusting something the client sent to decide who the client is.
You can rebuild the class of bug in a few lines and, more usefully, feel exactly why it happens.
The one idea
HTTP headers are attacker-controlled. Every one of them. The browser sets some by convention, but nothing stops a client from sending whatever it likes, curl -H will send any header you type. So the moment a server reads a header and treats its value as proof of identity, the identity check is decorative. Anything the client can set, the client can forge.
Build the mistake
Here is an auth check that looks reasonable at a glance. A trusted front-end proxy is supposed to strip and re-set X-User, and the app trusts it:
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET_FILES = {"admin": "prod database password: hunter2"}
class App(BaseHTTPRequestHandler):
def do_GET(self):
# BUG: identity taken straight from a client-controllable header.
user = self.headers.get("X-User", "guest")
if user in SECRET_FILES:
self.respond(200, SECRET_FILES[user])
else:
self.respond(403, "forbidden")
def respond(self, code, body):
self.send_response(code)
self.end_headers()
self.wfile.write(body.encode())
HTTPServer(("127.0.0.1", 8099), App).serve_forever()
The developer's mental model is "only our proxy sets X-User, so it is trustworthy." The flaw is that the app is reachable by anyone who can send it bytes, and the header is just bytes.
Exploit it
A normal request is a guest and gets nothing:
$ curl -s localhost:8099
forbidden
Now send the header yourself:
$ curl -s -H "X-User: admin" localhost:8099
prod database password: hunter2
One header, full compromise. No password, no token, no exploit chain. That is the shape of the Gitea bug: a request attribute the server should have derived itself was instead read from the request.
Why real systems fall for it
Almost never on purpose. It creeps in through architecture:
- A proxy really does inject a trusted header, and the app is later exposed directly (a new route, a container port, an internal service reached from outside), so the "only our proxy sets this" assumption quietly breaks.
- A framework middleware sets
req.userfrom a header for local testing, and the toggle ships to production. - An SSO integration trusts a header the identity provider sets, but forgets that the provider is not the only thing that can reach the endpoint.
The Gitea case was a specific header the server treated as authenticated context. Same category.
The fix: derive identity, do not read it
Identity must come from something the client cannot forge: a signed token or a server-side session, verified on every request.
import hmac, hashlib
SIGNING_KEY = b"server-only-secret"
def verify_session(token):
try:
user, sig = token.split(".", 1)
except (ValueError, AttributeError):
return None
good = hmac.new(SIGNING_KEY, user.encode(), hashlib.sha256).hexdigest()
return user if hmac.compare_digest(good, sig) else None
Now X-User: admin proves nothing. To claim to be admin you need a token whose signature matches, and producing that signature requires SIGNING_KEY, which never leaves the server. Note hmac.compare_digest: a plain == on the signature can leak the answer through timing, so constant-time comparison matters here too.
The rules that would have prevented the CVE:
- Never trust a request-supplied value to establish identity or authorization. Derive it server-side from a verified credential.
- Assume every endpoint is reachable by an attacker, proxy or no proxy. Defense cannot depend on network topology you do not control.
- Sign what you must send to the client, and verify it when it comes back.
That instinct, treat every byte from the client as hostile until proven otherwise, is the spine of the cybersecurity track. The one-header bypass is a memorable way to earn it.