
Proof-of-concept and lab for a Gitea Actions artifact v4 DoS: unbounded io.ReadAll in parseProtobufBody lets a job token POST oversized CreateArtifact bodies.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitea-artifact-v4-readall
Gitea 1.27.3 - Gitea
job_summary.go already caps at MaxJobSummarySize (1 MiB) with io.LimitReader. parseProtobufBody in artifact v4 does io.ReadAll(ctx.Req.Body) with no cap. CreateArtifact, Finalize, List, Get, Delete all go through it. A random user PAT is not enough. You need a running Actions job token. Fork-PR jobs still NeedApproval on 1.27.3.
Job-summary learned LimitReader. Artifact v4 did not copy it. A live job can POST a 12 MiB CreateArtifact and Gitea buffers the lot.
| ID | no CVE yet |
| CWE | CWE-400 |
| CVSS | Medium: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H |
| Product | Gitea |
| Affected | through v1.27.3 (146cc3e) with Actions enabled |
| Auth | running Actions job token |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
From a running Actions job (their own repo, or a fork-PR that already passed NeedApproval), POST an arbitrarily large v4 CreateArtifact body and force Gitea to buffer it all. Job-summary on the same instance already rejects anything over 1 MiB.
That is memory pressure on the Gitea process. Lab proved the missing cap (12 MiB -> 200). It did not kill the process. It is not RCE. It is not unauthenticated. A stolen user PAT without a live job does not reach this path.
The leftover map after the GHSA wave included Actions body handling. Fork-PR jobs still NeedApproval on 1.27.3, so this is not an unauth fork-PR skip. I looked at how the Actions API reads request bodies. Job-summary has a number. Artifact v4 does not.
I stood up gitea/act_runner:0.2.13, seeded a hold workflow so a job token exists, and POSTed a 12 MiB CreateArtifact. HTTP 200 ok plus signedUploadUrl. Then 8 MiB job-summary: 400 invalid summary. GET /api/v1/version still 200. Gitea stayed up. Lab mem_limit is 1g. Body capped at 12 MiB so the host is not the experiment. Host executor only. No docker.sock.
Wrong turns already recorded: CreateArtifact without a job token (401 is not the bug); process kill / OOM (SUCCESS is 12 MiB -> 200 while 8 MiB summary -> 400); treating signedUploadUrl as RCE; fork-PR unauth; a reverse shell. Theatre.
cd lab
./run.sh
Target only http://127.0.0.1:18135. In-compose ROOT_URL is http://gitea:3000/ so the runner can register.
v4-create status=200 sent=12582912
v4-unbounded-readall=1
job-summary status=400 sent=8388608 invalid summary cap=MaxJobSummarySize=1048576
gitea-still-up status=200
SUCCESS GITEA-ARTIFACT-V4-READALL
Wrap parseProtobufBody in io.LimitReader at a proto-sized cap, same pattern as MaxJobSummarySize. 12 MiB CreateArtifact must fail the same class of way 8 MiB job-summary already does.
GNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.