A AegiFlow
MEDIUMCVSS 6.5

CVE-2026-57897

Gitea: Cross-Repo Information Disclosure via Org-Level Actions Run/Job APIs

Published
2026-07-21
Modified
2026-07-21
Aliases
GHSA-frpw-3h2q-4jj6
Sources
github-advisory

Summary

**Author:** Prakhar Porwal **Date:** 2026-05-24 **Target:** Gitea (self-hosted Git service) **Branch tested:** `main` @ `b7e95cc48c` (development build, go1.26.3) **Component:** `routers/api/v1/org/action.go` (org-level Actions API) **OWASP:** API3:2023 Broken Object Property Level Authorization --- ## 1. Summary The org-level Actions REST endpoints ``` GET /api/v1/orgs/{org}/actions/runs GET /api/v1/orgs/{org}/actions/jobs ``` are gated only by **`reqOrgMembership()`** + `reqToken()`. They then call `shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)` / `shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0, nil)`, which selects **every** `action_run` / `action_run_job` row whose repository belongs to the org — with **no per-repository ACL check**. Result: any user who is a member of an organization can enumerate workflow runs and jobs from **every repository in that org**, including: * private repositories the caller has no team membership for, * repositories where the caller has been explicitly denied the `repo.actions` unit, * repositories created by other teams the caller is not part of. Direct per-repo equivalents (`GET /api/v1/repos/{owner}/{repo}/actions/runs`, `…/jobs/{job_id}/logs`, `…/runs/{run_id}/jobs`) correctly return `404` for the same caller — proving the org-level surface is the only path that leaks. --- ## 2. Affected Code ### 2.1 Route registration `routers/api/v1/api.go:1647-1652` ```go addActionsRoutes( m, reqOrgMembership(), // reqReaderCheck reqOrgOwnership(), // reqOwnerCheck org.NewAction(), ) ``` ### 2.2 Helper that registers run/job listing `routers/api/v1/api.go:908-941` ```go m.Group("/runs", reqToken(), reqReaderCheck, act.ListWorkflowRuns) m.Get("/runs", reqToken(), reqReaderCheck, act.ListWorkflowRuns) m.Get("/jobs", reqToken(), reqReaderCheck, act.ListWorkflowJobs) ``` `reqReaderCheck` for org-scope = `reqOrgMembership()` — bare org membership is enough; no per-repo permission is consulted. ### 2.3 Handler `routers/api/v1/org/action.go:595-683` ```go func (Action) ListWorkflowJobs(ctx *context.APIContext) { shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0, nil) } func (Action) ListWorkflowRuns(ctx *context.APIContext) { shared.ListRuns(ctx, ctx.Org.Organization.ID, 0) } ``` ### 2.4 Query construction (no ACL) `routers/api/v1/shared/action.go:138-215` ```go opts := actions_model.FindRunOptions{ OwnerID: ownerID, // ← org ID, NOT user ID RepoID: repoID, // = 0 at org level ListOptions: listOptions, } … runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts) ``` `models/actions/run_list.go:102-110` ```go func (opts FindRunOptions) ToJoins() []db.JoinFunc { if opts.OwnerID > 0 { return []db.JoinFunc{func(sess db.Engine) error { sess.Join("INNER", "repository", "repository.id = repo_id AND repository.owner_id = ?", opts.OwnerID) return nil }} } return nil } ``` The join only constrains `repository.owner_id = orgID`. There is no `access`/`team_repo`/`collaboration` join and no `access_model.GetDoerRepoPermission(...)` filter — every row in the org is returned. The same bug applies to `shared.ListJobs`, which calls `db.FindAndCount[actions_model.ActionRunJob](ctx, FindRunJobOptions{OwnerID: …})` using an analogous repository join. --- ## 3. Steps to Reproduce ### 3.1 Setup * Org **`1st-org`** with one **private** repo `1st-org-repo`. * Team **`Owners`** contains user **`admin`** (org owner). * Team **`1st-team`** has **zero repositories** assigned (units permission `none` for actions, no included repos). * User **`admin2`** is a regular user (`is_admin = false`), member of `1st-team` only — so org member, but **no team grants any access to `1st-org-repo`**. Verified that admin2 lacks direct access: ```bash $ curl -u admin2:admin@123 -w '[%{http_code}]\n' \ http://localhost:3001/api/v1/repos/1st-org/1st-org-repo {"errors":

Affected packages

EcosystemPackageAffected versionsFixed versions
Gocode.gitea.io/gitea1.27.0

Remediation: Upgrade to 1.27.0 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.

CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.