Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
MEDIUM severity

GHSA-fj8v-hjwv-qm88

MEDIUMFix: go-gitea/gitea#38214

GHSA-fj8v-hjwv-qm88 is a medium-severity (CVSS 6.3) CWE-280 vulnerability in gitea.dev. O3 Security confirms whether GHSA-fj8v-hjwv-qm88 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Gitea: Fork-PR Actions task can read a third private repository via the collaborative-owner branch (missing fork-PR guard)

Also known asCVE-2026-58416GO-2026-6052
Published
Jul 21, 2026
Updated
Jul 22, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 6, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for GHSA-fj8v-hjwv-qm88.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs17th percentile — riskier than 17% of all scored CVEsHighest risk
0.00%0.25%0.50%0.75%0.3%0.3%Sep 26Sep 26

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-fj8v-hjwv-qm88 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 0 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected
🐹gitea.dev

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

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.goGetDoerRepoPermission(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)HTTPMeaning
anonymous (no token)401auth required
token, A public, B trusts A404branch gated on taskRepo.IsPrivate ⇒ A public skips it
token, A private, B has no collab-owner config404no trust ⇒ denied
token, A private, B trusts A (collab-owner)200git clone of private B succeeds
config removed / restored404 / 200deterministic

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):

  1. B is deliberately configured with a collaborative owner — but that is exactly the feature's intended use, so realistic for any deployment using it.
  2. 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)."
  3. 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.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogitea.devall versions1.27.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for gitea.dev. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update gitea.dev to 1.27.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-fj8v-hjwv-qm88 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-fj8v-hjwv-qm88 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-fj8v-hjwv-qm88. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### 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
O3 Security · Impact-Aware SCA

Is GHSA-fj8v-hjwv-qm88 in your dependencies?

O3 detects GHSA-fj8v-hjwv-qm88 across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

GHSA-fj8v-hjwv-qm88: gitea.dev Remote… | O3 Security