🛡️ CVE-2026-58426 — gitea
Description
Gitea Actions Artifacts V4 signed URL HMAC ambiguity allows cross-repository artifact read and cross-task upload-state write
Summary
Gitea Actions Artifacts V4 signed upload/download URLs can be rewritten to access a different running task and repository context while preserving the original HMAC signature. An attacker with permission to run a Gitea Actions job can turn a signed URL for an attacker-controlled artifact into a URL that reads artifacts from another task context, or writes attacker-controlled data into another task's artifact upload staging context, including in a private repository.
This is one vulnerability with two exploit paths:
DownloadArtifact: cross-task/cross-repository artifact read, givingC:H.UploadArtifact: cross-task artifact staging write and metadata mutation, givingI:H.
Details
The root cause is that the V4 artifact signed URL signature is built from raw concatenated fields without delimiters or length-prefixing:
```go
func (r *artifactV4Routes) buildSignature(endpoint, expires, artifactName string, taskID, artifactID int64) []byte {
mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
mac.Write([]byte(endpoint))
mac.Write([]byte(expires))
mac.Write([]byte(artifactName))
_, _ = fmt.Fprint(mac, taskID)
_, _ = fmt.Fprint(mac, artifactID)
return mac.Sum(nil)
}
```
Affected code: routers/api/actions/artifactsv4.go:164-171.
Because artifactName, taskID, and artifactID are concatenated without boundaries, two different URL tuples can produce the same HMAC input. For example:
```text
signed tuple:
artifactName = "artifact-795-153"
taskID = 48
artifactID = <attacker artifact id>
forged tuple:
artifactName = "artifact-795-1"
taskID = 53
artifactID = 48<attacker artifact id>
```
The final HMAC input suffix is identical:
```text
artifact-795-15348<attacker artifact id>
```
The attacker does not need to know the target artifact's database artifactID. The forged URL's artifactID only needs to carry digits that preserve the original HMAC input. After verification, the actual target artifact is looked up by target task/run/attempt and artifactName, not by the signed artifactID.
The signed URL handlers are unauthenticated and use ArtifactV4Contexter() only:
```go
m.Group("", func() {
m.Put("UploadArtifact", r.uploadArtifact)
m.Get("DownloadArtifact", r.downloadArtifact)
}, ArtifactV4Contexter())
```
Affected code: routers/api/actions/artifactsv4.go:156-159.
After verifying the HMAC, verifySignature() trusts the URL-controlled taskID, loads that task, checks that it is running, and returns the URL-controlled artifactName. It parses artifactID for the HMAC, but does not load or bind the artifact by that signed artifact ID:
```go
task, err := actions_model.GetTaskByID(ctx, taskID)
...
if task.Status != actions_model.StatusRunning {
...
}
...
return task, artifactName, true
```
Affected code: routers/api/actions/artifactsv4.go:224-267.
The artifact lookup then uses the URL-selected task's run/attempt plus URL-selected artifact name:
```go
has, err := db.GetEngine(ctx).Where(builder.Eq{
"run_id": runID,
"run_attempt_id": runAttemptID,
"artifact_name": name,
}, builder.Like{"content_encoding", "%/%"}).Get(&art)
```
Affected code: routers/api/actions/artifactsv4.go:270-278.
For download, the forged URL reaches downloadArtifact(), which verifies the signature, resolves the artifact by the forged task/run/name context, and serves it:
```go
task, artifactName, ok := r.verifySignature(ctx, "DownloadArtifact")
...
artifact, err := r.getArtifactByName(ctx, task.Job.RunID, task.Job.RunAttemptID, artifactName)
...
err = actions.DownloadArtifactV4ReadStorage(ctx.Base, artifact)
```
Affected code: routers/api/actions/artifactsv4.go:674-693.
For upload, the forged URL reaches uploadArtifact(), which verifies the signature, resolves the target artifact by the forged task/run/name context, appends attacker-controlled data, and updates target artifact metadata:
```go
task, artifactName, ok := r.verifySignature(ctx, "UploadArtifact")
...
artifact, err := r.getArtifactByName(ctx, task.Job.RunID, task.Job.RunAttemptID, artifactName)
...
uploadedLength, err := appendUploadChunkV3(r.fs, ctx, artifact, artifact.RunID, artifact.FileSize)
...
artifact.FileCompressedSize += uploadedLength
artifact.FileSize += uploadedLength
...
actions_model.UpdateArtifactByID(ctx, artifact.ID, artifact)
```
Affected code: routers/api/actions/artifactsv4.go:382-422.
The strengthened dynamic PoC also opens the storage object created by the forged upload and verifies that the attacker-controlled bytes were written under the target run and target artifact ID staging path. This demonstrates an unauthorized write primitive into the target artifact upload context. The current PoC does not claim that a finalized artifact dow
How this vulnerability can be exploited
This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. No user interaction is required. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality high, integrity high, availability none.
Weakness class
CVE-2026-58426 is classified as CWE-347: Improper Verification of Cryptographic Signature. A signature is not checked correctly, so forged or modified content is accepted as genuine.
Affected software
CVE-2026-58426 is recorded against 2 packages.
- code.gitea.io/gitea
- unknown
Timeline and source
Published on 21 July 2026 and last revised on 27 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
References
github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Web)
blog.gitea.com (Web)
github.com (Package)
github.com (Web)
Details
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| code.gitea.io/gitea | — | — |
| unknown | — | — |
References
Similar Threats
- High CVE-2021-3382
- High CVE-2020-14144
- Medium CVE-2022-38183
- Medium CVE-2022-1928
- Unknown CVE-2019-1010261
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Exploit Protection
Are you running gitea?
CVE-2026-58426 carries CVSS 9.6 Critical rating. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.
Check My Site For CVE-2026-58426 →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.