Developer platforms, tools and supply chain
Gitea: CVE-2026-109553 SSH certificate principal impersonation
Gitea check
Gitea resolved the principals of an SSH certificate by searching the content of all stored public keys instead of only the keys registered as principals. So a certificate that lists another user's public-key line as a principal authenticated as that user, through both the HTTP signature API and the built-in SSH server.
| CVE | CVE-2026-109553 (CWE-287, advisory GHSA-vcp6-2qc6-gvq3, published 18 October 2026) |
| Severity | High, CVSS 7.5 |
| Affected | Gitea up to and including 28.1.0 (the advisory confirmed it on 1.27.3) |
| Fixed | 28.2.0 (released 18 October 2026) |
| Only if | The instance trusts a user certificate authority: SSH_TRUSTED_USER_CA_KEYS is set in [server] of app.ini. The advisory says instances that do not trust a user CA are not affected |
| Worst case | SSH_AUTHORIZED_PRINCIPALS_ALLOW=anything, documented as performing no check on principal text: an ordinary user can register another person's public-key line as their own principal and authenticate as them |
| Otherwise | A holder of a certificate signed by your CA can list a victim's key line as a principal |
| Same release | 28.2.0 fixes eight other flaws that only the update fixes |
Forgejo is a separate project. This tool reads Gitea's version output and does not judge Forgejo.
Quick start
-
Download the script. The repository is mirrored on GitHub and Codeberg; pick a base URL.
# BASE from GitHub: https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main # BASE from Codeberg: https://codeberg.org/PrivacySafe/security-patches/raw/branch/main # curl -fLO "$BASE/gitea/gitea-check.sh" curl -fLO https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main/gitea/gitea-check.sh -
Read it before running it, then make it executable.
less gitea-check.sh chmod +x gitea-check.sh -
Check this host. It finds
giteaonPATH, in the usual locations and from running processes, and reads theapp.inithe process was started with. Run it as a user who can readapp.ini(often thegituser or root). To point it at a specific file, setGITEA_INI../gitea-check.sh check GITEA_INI=/etc/gitea/app.ini ./gitea-check.sh checkA running container is checked from the host (see the container note below). For an image that is not running, check its tag yourself.
Commands
| Command | What it does |
|---|---|
check [BINARY ...] (default) |
Reads the version from gitea --version, applies the rule, then reads [server] in app.ini for SSH_TRUSTED_USER_CA_KEYS and SSH_AUTHORIZED_PRINCIPALS_ALLOW (section and key names are matched case-insensitively; commented lines and keys in other sections are ignored) |
version |
Prints the script version |
Environment: GITEA_INI (space separated files) replaces app.ini discovery.
Exit codes: 0 nothing exposed, 1 an affected version that trusts a user CA, or whose configuration could not be read, 2 bad arguments.
Credentials are never printed
SSH_TRUSTED_USER_CA_KEYS holds public keys, which are not secret, but the script still never prints it and says only that a CA is set.
How to read the result
- Affected version, CA trusted, principals
anything: the worst case. Upgrade. - Affected version, CA trusted, other principal policy: still exposed to a holder of a CA-signed certificate. Upgrade.
- Affected version, no CA trusted: per the advisory the flaw does not apply, so the script exits
0, but it still tells you to update because the same release fixes eight other flaws and a CA may be added later. app.iniunreadable: the script cannot tell whether a CA is trusted, so it treats the instance as exposed.- Version numbers: Gitea moved from 1.x to 28.x, so
1.27.3is older than28.2.0and is affected. The comparison is numeric, so28.10.0is newer than28.2.0.
Fixing it
Upgrade to Gitea 28.2.0 or later and restart. If you do not need certificate login, emptying SSH_TRUSTED_USER_CA_KEYS takes the instance out of the affected configuration. This is PrivacySafe's recommendation. Afterwards, review recent SSH and API signature logins.
Sources
Before you run it
- The fix. Update to 28.2.0. The same release fixes eight other flaws that only the update fixes.
- It only applies to instances that trust a user certificate authority. That means
SSH_TRUSTED_USER_CA_KEYSis set inapp.ini, together with a principals policy that lets someone list another user's key line. An instance that does not trust a user CA is not affected. - Forgejo is a separate project. The script reads Gitea's
--versionoutput and does not judge Forgejo. - It never prints your CA keys. From
app.iniit reports whether a user CA is trusted and the principal policy word, and nothing else. - It also checks running docker and podman containers, unless you name a directory or set
SCAN_CONTAINERS=0. A read-only probe in each container captures the output ofgitea --versionand a reducedapp.inithat holds only the two[server]settings this check reads (SSH_AUTHORIZED_PRINCIPALS_ALLOW, and whetherSSH_TRUSTED_USER_CA_KEYSis set, as a barex). The script then runs once per container, in isolation, against that copy, so one container's configuration never changes another's result. The CA key, the database password and every otherapp.inivalue stay in the container. The copies are removed on exit. A path shown as.../docker_NAME/...is inside the container NAME. It is skipped when you name a binary or directory yourself.
The Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.