CVE-2026-58437: Gitea Repository Visibility Manipulation Vulnerability

HIGH
Advisory Identifier: GHSA-8p9h-49rc-qgxj
CVSS 7.1
CWE-284
Published
Advisory Summary

CVE-2026-58437 is a HIGH (CVSS 7.1) repository visibility manipulation vulnerability (CWE-284: Improper Access Control) affecting Gitea libraries in the Go ecosystem. The issue involves repository visibility manipulation via git push options. Remediation is available by upgrading affected packages to version 1.27.0.

CVE-2026-58437 at a Glance

CVE Identifier CVE-2026-58437
GHSA Identifier GHSA-8p9h-49rc-qgxj
Severity HIGH
CVSS v3.1 Base Score 7.1
CWE Classification CWE-284: Improper Access Control
Affected Product Gitea
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 7.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity High (I:H)
Availability None (A:N)

How to Fix CVE-2026-58437

To remediate this vulnerability, upgrade all affected Gitea dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

code.gitea.io/gitea fixed: 1.27.0
Upgrade code.gitea.io/gitea to 1.27.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for code.gitea.io/gitea

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-58437, inspect your project dependencies for Gitea:

inspect dependencies for code.gitea.io/gitea

Analyze the terminal output to locate any version of Gitea in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.27.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-284: Improper Access Control, where the product does not restrict or incorrectly restricts access to a resource from an unauthorized actor. In Gitea, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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 (`repository` event with `visibility_changed` action) - No org-level notification to owners - No team permission re-calculation - No email alert to watchers - The database update uses `UpdateRepositoryColsNoAutoTime`, which also suppresses the `updated_at` timestamp change --- #### Vulnerable Code **`routers/private/hook_post_receive.go:173–225`** ```go 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`: ```go 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 - `git` client with push access to the repository --- **Step 1 — Confirm the target repository is private** --- **Step 2 — Clone the repository** ```bash 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)* ```bash echo "$(date)" >> .gitkeep git add .gitkeep git commit -m "routine update" ``` --- **Step 4 — Execute the exploit push** ```bash # 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 | --- #### Recommended Fix **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: ```go // 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: ```go // 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.

CVE-2026-58437 Affected Gitea Versions

1 component(s)
Fixed Versions:
1.27.0
Product / Package Ecosystem Affected Versions Fixed Version
code.gitea.io/gitea
Go
< 1.27.0
1.27.0

References

Frequently Asked Questions

What is CVE-2026-58437?
CVE-2026-58437 is a security vulnerability in Gitea involving Repository Visibility Manipulation via Git Push Options under CWE-284: Improper Access Control.
Which Gitea versions are affected?
Affected versions include releases prior to 1.27.0 across affected packages: code.gitea.io/gitea.
What version fixes CVE-2026-58437?
Upgrading to 1.27.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is code.gitea.io/gitea affected?
Yes, code.gitea.io/gitea is affected in releases prior to 1.27.0. Upgrade your dependency to 1.27.0 or later.
What is the CVSS score of CVE-2026-58437?
CVE-2026-58437 has a CVSS v3.1 base score of 7.1 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N.
What CWE is associated with CVE-2026-58437?
CVE-2026-58437 is classified under CWE-284: Improper Access Control (The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.).
How can I check Go dependencies for CVE-2026-58437?
Run the dependency tree command: 'inspect dependencies for code.gitea.io/gitea'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-8p9h-49rc-qgxj",
  "modified" : "2026-07-21T21:02:44Z",
  "published" : "2026-07-21T21:02:44Z",
  "aliases" : [ "CVE-2026-58437" ],
  "summary" : "Gitea: Repository Visibility Manipulation via Git Push Options",
  "details" : "### Repository Visibility Manipulation via Git Push Options\n\n| Field | Value |\n|-------|-------|\n| **Affected File** | `routers/private/hook_post_receive.go` |\n| **Affected Function** | `HookPostReceive()` |\n| **Affected Lines** | 173–225 |\n| **Prerequisite** | Attacker must have owner-level or admin collaborator access to the target repository |\n\n---\n\n#### Description\n\nGitea'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.\n\nThis 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:\n\n- No entry written to the repository's audit/activity log\n- No webhook event fired (`repository` event with `visibility_changed` action)\n- No org-level notification to owners\n- No team permission re-calculation\n- No email alert to watchers\n- The database update uses `UpdateRepositoryColsNoAutoTime`, which also suppresses the `updated_at` timestamp change\n\n\n---\n\n#### Vulnerable Code\n\n**`routers/private/hook_post_receive.go:173–225`**\n\n```go\nisPrivate  := opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate)  // \"repo.private\"\nisTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate) // \"repo.template\"\n\nif isPrivate.Has() || isTemplate.Has() {\n    // ... loads repo and verifies pusher is owner or admin ...\n    if !perm.IsOwner() && !perm.IsAdmin() {\n        ctx.JSON(http.StatusNotFound, ...)\n        return\n    }\n\n    // FIXME: these options are not quite right, for example: changing visibility\n    //        should do more works than just setting the is_private flag\n    // These options should only be used for \"push-to-create\"\n    if isPrivate.Has() && repo.IsPrivate != isPrivate.Value() {\n        // TODO: it needs to do more work\n        repo.IsPrivate = isPrivate.Value()\n        repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, \"is_private\")\n        //         ^^^ bypasses updated_at timestamp, audit trail suppressed\n    }\n    if isTemplate.Has() && repo.IsTemplate != isTemplate.Value() {\n        repo.IsTemplate = isTemplate.Value()\n        repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, \"is_template\")\n    }\n}\n```\n\nThe push option constants are defined in `modules/private/pushoptions.go:18–19`:\n\n```go\nGitPushOptionRepoPrivate  = \"repo.private\"\nGitPushOptionRepoTemplate = \"repo.template\"\n```\n\n---\n\n#### Attack Scenario\n\n**Scenario A — Insider threat / rogue admin collaborator**\n\nAn 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.\n\n**Scenario B — Supply-chain template poisoning**\n\nA 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.\n\n---\n\n#### Step-by-Step Reproduction\n\n**Prerequisites:**\n- A Gitea user account with either owner or admin-collaborator access to a private repository\n- `git` client with push access to the repository\n\n---\n\n**Step 1 — Confirm the target repository is private**\n\n---\n\n**Step 2 — Clone the repository**\n\n```bash\ngit clone http://USER:PASSWORD@<gitea-host>/OWNER/REPO.git /tmp/target-repo\ncd /tmp/target-repo\n```\n\n---\n\n**Step 3 — Make any commit** *(the push option rides on a real push)*\n\n```bash\necho \"$(date)\" >> .gitkeep\ngit add .gitkeep\ngit commit -m \"routine update\"\n```\n\n---\n\n**Step 4 — Execute the exploit push**\n\n```bash\n# Make the repository public\ngit push http://USER:PASSWORD@<gitea-host>/OWNER/REPO.git main \\\n  -o repo.private=false\n\n# The push completes with a normal success message:\n#   remote: Processed 1 references in total\n#   To http://<gitea-host>/OWNER/REPO.git\n#      abc1234..def5678  main -> main\n```\n\n---\n\n**Step 5 — Verify the repository is now public**\n\n\n---\n\n**Step 6 — Restore and cover tracks**\n\nRe-make it private in the same session, leaving no visible audit trail\n\nThe repository activity feed shows only two normal push events. The visibility change is invisible.\n\n**Verification: confirm no activity log entry**\n\n\n---\n\n#### Impact Details\n\n| Impact | Description |\n|--------|-------------|\n| **Data exfiltration** | Private source code, CI/CD secrets in plain-text files, environment configs become publicly cloneable for the window the repo is public |\n| **No audit trail** | `UpdateRepositoryColsNoAutoTime` suppresses the `updated_at` change; no activity log entry; no webhook; no notification |\n| **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 |\n| **Scope** | Affects all repos where the attacker has admin-collaborator access — not only repos they own |\n\n---\n\n#### Recommended Fix\n\n**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:\n\n```go\n// routers/private/hook_post_receive.go\nif isPrivate.Has() || isTemplate.Has() {\n    if !wasEmpty {\n        // repo already existed — refuse these options on established repos\n        log.Warn(\"Repo push options repo.private/repo.template ignored for existing repo %s\", repoName)\n        // do not process\n    } else {\n        // original push-to-create path only\n        ...\n    }\n}\n```\n\n**Option 2 — Route through the full visibility-change service** so that audit events, webhooks, and team re-syncs are triggered:\n\n```go\n// Instead of the raw UpdateRepositoryColsNoAutoTime call:\nif err := repo_service.UpdateRepositoryVisibility(ctx, repo, isPrivate.Value()); err != nil {\n    ...\n}\n```\n\nWhere `UpdateRepositoryVisibility` fires the `repository` webhook event and writes an activity log entry.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "code.gitea.io/gitea"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.27.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/security/advisories/GHSA-8p9h-49rc-qgxj"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/go-gitea/gitea"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/go-gitea/gitea/releases/tag/v1.27.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-284" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-21T21:02:44Z",
    "nvd_published_at" : null
  }
}