Gitea
CVE-2026-58416
MEDIUM
Severity by source
AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N
Network vector via git-HTTP; AC:H for three simultaneous prerequisites; PR:L for fork contributor access; S:C and C:H for cross-repo private source disclosure; no integrity or availability impact.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N
Lifecycle Timeline
2DescriptionGitHub Advisory
Summary
GetActionsUserRepoPermission (models/perm/access/repo_permission.go) decides whether an Actions task token may access a target repo. Its cross-repo branches each enforce a fork-PR discriminator - except the collaborative-owner branch, which is missing the !task.IsForkPullRequest guard that its sibling has. As a result, when a private repo B lists owner A as a collaborative owner, an attacker-controlled fork pull-request workflow whose base repo is owned by A is granted code-read on B - i.e. the fork's YAML can clone a third private repository it has no rights to.
Details
// models/perm/access/repo_permission.go (v1.26.2), in GetActionsUserRepoPermission
if checkSameOwnerCrossRepoAccess(ctx, taskRepo, repo, task.IsForkPullRequest) { // passes isForkPR -> denies forks
return maxPerm, nil
}
...
if taskRepo.IsPrivate { // <-- NO IsForkPullRequest check here
actionsUnit := repo.MustGetUnit(ctx, unit.TypeActions)
if actionsUnit.ActionsConfig().IsCollaborativeOwner(taskRepo.OwnerID) {
return maxPerm, nil // grants code-read to target repo B
}
}The sibling same-owner path correctly denies fork PRs:
func checkSameOwnerCrossRepoAccess(ctx, taskRepo, targetRepo, isForkPR bool) bool {
if isForkPR {
return false // Fork PRs are never allowed cross-repo access to other private repositories.
}
...
}taskRepo = the repo whose workflow is running (the PR's base repo A); repo = the target being cloned (B). IsCollaborativeOwner(taskRepo.OwnerID) asks "does target B's Actions config trust A's owner for cross-repo read?" When B trusts ownerA, the branch returns maxPerm (code-read) even when task.IsForkPullRequest is true - i.e. when the executing YAML is the fork's, not A's.
Every sibling enforces the fork-PR discriminator; except for this branch: checkSameOwnerCrossRepoAccess denies forks; ComputeTaskTokenPermissions (models/actions/token_permissions.go) only clamps the token *ceiling* to read-only for fork/cross-repo (its own comment notes the access *decision* is in GetActionsUserRepoPermission, so it does not neutralize the gap - it just makes the leak read-only); secrets (models/secret/secret.go) and the approval gate (services/actions/notifier_helper.go) both correctly key on IsForkPullRequest.
Reachability - the runner clones target repo B over git-HTTP with the task token: routers/web/repo/githttp.go → GetDoerRepoPermission(ctx, repoB, ActionsUser) → GetActionsUserRepoPermission(ctx, repoB, actionsUser, taskID) with IsForkPullRequest == true → collaborative-owner branch returns code-read → p.CanAccess(Read, code) passes → private clone of B succeeds. (CheckRepoScopedToken in githttp is a no-op for the Actions token.)
PoC
Setup: private base repo A (usera/repoA), private third repo B (userb/repoB) with a planted SECRET.txt, B's Actions config trusting usera as a collaborative owner, and a genuine running fork-PR task token (token_hash computed with Gitea's own HashToken) presented as HTTP Basic. Requesting GET /userb/repoB.git/info/refs?service=git-upload-pack:
| Condition (same fork-PR token) | HTTP | Meaning |
|---|---|---|
| anonymous (no token) | 401 | auth required |
| token, A public, B trusts A | 404 | branch gated on taskRepo.IsPrivate ⇒ A public skips it |
| token, A private, B has no collab-owner config | 404 | no trust ⇒ denied |
| token, A private, B trusts A (collab-owner) | 200 | git clone of private B succeeds |
| config removed / restored | 404 / 200 | deterministic |
In the 200 case, git clone of private repo B succeeded and yielded its SECRET.txt - the full source of a third private repo the fork-PR author has no rights to.
Impact
Read-only confidentiality breach: discloses the full source of a *third* private repository (B) to an untrusted external fork-PR author. Read-only, not write/RCE.
Preconditions (honest):
- B is deliberately configured with a collaborative owner - but that is exactly the feature's intended
use, so realistic for any deployment using it.
- The fork PR's base repo A is itself private (the branch is gated on
taskRepo.IsPrivate). Forking a
private A already requires read on A, so this is a normal internal-contributor situation, not a weakening - the escalation is "read A (granted) → read a *different* private repo B (never granted)."
- The fork-PR workflow must actually run - most realistically via an attacker who had one earlier PR
approved (the "approved before" path in ifNeedApproval), after which fork PRs auto-run.
Suggested remediation
Add the same fork-PR guard the sibling path has (one line):
if taskRepo.IsPrivate && !task.IsForkPullRequest {
actionsUnit := repo.MustGetUnit(ctx, unit.TypeActions)
if actionsUnit.ActionsConfig().IsCollaborativeOwner(taskRepo.OwnerID) {
return maxPerm, nil
}
}This flips Vuln_ForkPR_LeaksThirdPrivateRepo to PASS, keeps Control_NonFork_Allowed PASS (legitimate collaborative-owner sharing still works), and leaves the existing TestGetActionsUserRepoPermission suite all green.
AnalysisAI
Gitea's Actions permission system (v1.26.2 and earlier) exposes full source code of third private repositories to untrusted fork-PR authors due to a missing fork-PR guard in the collaborative-owner permission branch of GetActionsUserRepoPermission. When a private target repo B configures a collaborative owner pointing to org/user A, an attacker with read access to any private repo under A can submit a fork PR, trigger the Actions workflow, and clone repo B's source code without authorization. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | Exploitation requires three simultaneous conditions to align: (1) the target private repo B must have the Gitea Actions collaborative-owner feature explicitly configured to trust A's owner - this is an opt-in configuration set in B's Actions unit config via IsCollaborativeOwner, not a default, but is the intended normal use of the feature; (2) the fork-PR's base repo A must itself be private, because the vulnerable branch is gated on taskRepo.IsPrivate - if A is public, this code path is not reached and exploitation is blocked; (3) the attacker's fork-PR workflow must auto-run, which requires having had at least one prior PR approved on repo A (via the 'approved before' path in ifNeedApproval in services/actions/notifier_helper.go). … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The vendor-assigned CVSS score of 6.3 (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N) accurately reflects the multi-condition exploitation path (AC:H) and high confidentiality impact across a changed scope (S:C/C:H). … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An attacker with read access to a private repo under org A forks it, submits one PR that gets approved (enabling future fork-PR auto-runs), then crafts a workflow YAML file that executes git clone against private repo B using the Actions task token provided at runtime. When the next fork PR triggers the workflow, GetActionsUserRepoPermission incorrectly grants code-read on repo B - because the collaborative-owner branch lacks the IsForkPullRequest check - and the full private source of repo B is cloned and accessible to the attacker. … |
| Remediation | Upgrade to Gitea v1.27.0, which contains the one-line fix adding the missing fork-PR guard (commit 1d43b736b5a16c5f80cfdcd9a9448a9c983ddaa0, PR #38214: https://github.com/go-gitea/gitea/pull/38214). … Detailed patch versions, workarounds, and compensating controls in full report. |
Threat intelligence, references, and detailed analysis are available after sign-in.
Broken access control in Gitea's Composer package registry (versions up to and including 1.26.1) lets remote attackers r
Gitea before 1.16.7 does not escape git fetch remote. Rated high severity (CVSS 7.5), this vulnerability is remotely exp
The git hook feature in Gitea 1.1.0 through 1.12.5 might allow for authenticated remote code execution in customer envir
Improper Neutralization of Input During Web Page Generation (XSS or 'Cross-site Scripting') vulnerability in Gitea Gitea
Container escape in Gitea act_runner (Docker backend, through act 0.262.0) lets an authenticated user with workflow-exec
models/repo_mirror.go in Gitea before 1.7.6 and 1.8.x before 1.8-RC3 mishandles mirror repo URL settings, leading to rem
An issue was discovered in Gitea through 1.11.5. Rated high severity (CVSS 7.5), this vulnerability is remotely exploita
Missing Authorization in GitHub repository go-gitea/gitea prior to 1.16.4. Rated high severity (CVSS 7.1), this vulnerab
Open Redirect on login in GitHub repository go-gitea/gitea prior to 1.16.5. Rated medium severity (CVSS 6.1), this vulne
Reverse-proxy authentication bypass in the official Gitea Docker image (versions up to and including 1.26.2) allows any
Branch-protection bypass in Gitea's self-hosted Git server (all versions before 1.26.0) allows a user with push access t
Migration transport protections in Gitea are bypassed for Git LFS operations, affecting all self-hosted instances before
Same technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-fj8v-hjwv-qm88