Gitea has disclosed a critical vulnerability that lets an unauthenticated remote user read local files through a public repository’s markup endpoint. Self-hosted Gitea operators running versions 1.22.1 through 1.27.0 are affected when at least one public repository exposes a normal readable code unit.
The direct fix is Gitea 1.27.1. The incident question is harder: a successful request can expose app.ini, including Gitea’s INTERNAL_TOKEN, and the vendor says that token can be used to reach command execution as the Gitea operating-system user. Upgrading closes the request path; it does not retract a token or remove a hook planted before the upgrade.
A renderer callback crosses the server’s file boundary
Gitea registers POST /{username}/{reponame}/markup with optional sign-in. For a public repository with its code unit enabled, an anonymous user passes the repository-unit read check. The handler then sends three attacker-controlled values, rendering mode, text, and file path, to the generic markup renderer.
Selecting file mode and a filename ending in .org chooses Gitea’s Org-mode renderer. In the affected implementation, Gitea initializes go-org without replacing its default ReadFile callback. The library accepts an Org #+INCLUDE directive containing an absolute path and passes that path to ioutil.ReadFile.
That sequence matters because the web request is not limited to content stored in the public repository. It asks the Gitea process to open a file using the service account’s own filesystem permissions, then returns the rendered result to an unauthenticated requester. The advisory lists configuration, internal bearer tokens, OAuth and JWT material, and deployment metadata among the exposed targets.

Figure details
An anonymous user submits file-mode Org markup to a public repository's markup endpoint. Gitea selects the Org renderer, whose default file callback follows an absolute INCLUDE path and reads a file as the Gitea service account. Disclosure of app.ini can expose the internal token. Under the advisory's stated storage-permission precondition, that token can be used through the internal logger to inject a Git hook; an anonymous clone then executes the hook as the Gitea operating-system user.
The internal token turns disclosure into an execution chain
File disclosure alone can expose database credentials, OAuth secrets, JWT material, repository deployment metadata, and other data readable by the service user. The vendor describes a further chain when the attacker obtains INTERNAL_TOKEN from app.ini and the service account can update Gitea’s managed global Git configuration.
With those conditions, the attacker can use the internal logger to inject a Git hook. The hook runs when an anonymous user clones a repository, giving the attacker command execution with the permissions of the Gitea OS user. This is not a claim that every vulnerable server is automatically compromised: the advisory specifies a readable public repository for the initial request and standard storage permissions for the hook chain. It also does not report active exploitation.
Those limits should shape triage. A server with no public repositories may not satisfy the published entry condition. A server that was public and vulnerable should not be cleared merely because no unusual login exists; the initial request requires no account, no repository write access, and no committed malicious file.
Preserve the request trail before rotating evidence away
Start with Gitea application access logs and any reverse-proxy, load-balancer, or web application firewall logs that retain the HTTP method and path. Search the vulnerable exposure window for POST requests to paths ending in /markup, then prioritize anonymous or unexplained requests against public repositories. Request-body evidence, if a security control retained it, can confirm file mode, an .org filename, or #+INCLUDE with an absolute path. Most default access logs will not contain that body, so its absence is not exculpatory.
Preserve app.ini, Gitea’s managed Git configuration, repository hook directories, service-unit definitions, startup scripts, scheduled jobs, and the Gitea service account’s process and file-change telemetry before remediation changes timestamps. Compare hooks and configuration against a known-good deployment. Review internal API and logger activity where retained, and correlate any suspicious markup request with subsequent anonymous clones, child processes launched by Gitea, outbound connections, or file writes by the service account.
If logs do not cover the period when a vulnerable instance exposed a qualifying public repository, record that as an evidence gap. Treat every secret readable by the Gitea service account as potentially exposed according to its actual placement and permissions; do not assume the internal token was the only valuable file.
Close on version, secrets, hooks, and process behavior
Upgrade every affected instance to 1.27.1 or later and verify the running process, not only the package inventory, reports the fixed version. Temporarily restrict the public markup route or public repository exposure if an immediate upgrade is impossible, but use that only as containment.
Rotate the Gitea internal token and any database, OAuth, JWT, deployment, or integration credentials that were readable during an uncovered or suspicious exposure window. Review repository and global Git hooks, managed Git configuration, service-account persistence, and child-process history. If a hook, unexplained configuration change, or service-account execution is present, rebuild from trusted media and investigate downstream systems reached with exposed credentials.
Close the incident only when all reachable instances run 1.27.1 or later, the vulnerable exposure window is covered by usable request and host evidence or explicitly treated as an uncertainty, unexpected markup requests and clones are resolved, hooks and managed Git configuration match known-good state, readable secrets are trusted again, and no unexplained process or network activity remains under the Gitea service account.
