CVE-2026-58436: Gitea Locale Middleware ReDoS via Accept-Language Underscore Bypass
Any unauthenticated attacker can send a single oversized Accept-Language header full of underscore separators to pin a Gitea server CPU for roughly two seconds per request, making a small flood…
The problem
Gitea's Locale middleware runs before every HTTP request, including unauthenticated ones, and passes the raw Accept-Language header straight into golang.org/x/text/language.ParseAcceptLanguage without any size or character filter.
The CVE-2022-32149 guard inside that parser caps inputs containing more than 1000 - (hyphen) characters, but the parser's internal scanner also treats _ (underscore) as an alias for - before parsing. A header built entirely from underscore separators contains zero hyphens, passes the guard, and then triggers the O(N^2) gobble path.
Go's default MaxHeaderBytes is 1 MiB, so an attacker can deliver up to one megabyte of malicious input per request. On a real server this burns roughly two seconds of CPU per request, and ten concurrent clients can saturate a ten-core box indefinitely.
Proof of concept
A working proof-of-concept for CVE-2026-58436 in code.gitea.io/gitea, with the exact payload below.
# Single request, times ~2 s on a standard server (tested against gitea/gitea:1.22.6)
# Build a 1 MiB Accept-Language value using _ separators only (zero - chars, bypasses CVE-2022-32149 guard)
PAYLOAD="en$(python3 -c 'print("_abcdefghi" * 100000, end="")')"
curl -sS -o /dev/null \
-w 'http=%{http_code} t=%{time_total}\n' \
-H "Accept-Language: ${PAYLOAD}" \
http://<gitea-host>:3000/Each 9-character _abcdefghi token exceeds the BCP 47 parser's 8-character tag-length limit, so the scanner calls gobble to discard it. gobble shifts the entire remaining buffer with runtime.memmove, giving O(N^2) total bytes moved for N tokens. A 1 MiB header holds roughly 104,857 such tokens, and the quadratic work amounts to several gigabytes of memory moves inside a single goroutine.
The existing CVE-2022-32149 guard counts only - characters and short-circuits at 1000. Because the attack payload uses _ exclusively, the guard sees zero hyphens, never fires, and allows the full O(N^2) scan to complete. The root cause is CWE-1333 (Inefficient Regular Expression Complexity) combined with an incomplete guard that missed the _ alias.
The patch in PR #38323 (commit f452c369) adds a pre-call check in modules/web/middleware/locale.go that sums strings.Count(al, "-") + strings.Count(al, "_") and clears the header value if the total exceeds 32, making the quadratic path unreachable for attacker-supplied input.
The fix
Upgrade to Gitea 1.27.0, which includes PR #38323 (commit f452c369acc9f1bd05ec6ef9c2e4399062dd6da1). The patch adds a combined hyphen-plus-underscore separator count in modules/web/middleware/locale.go before calling ParseAcceptLanguage; if the count exceeds 32 the header is discarded.
No legitimate browser sends more than ~10 separators in that header, so the cap has no practical impact on real users. There is no workaround short of placing a WAF or reverse proxy in front of Gitea that enforces a strict byte limit on the Accept-Language header (e.g. reject any value longer than 256 bytes).
Reported by tonghuaroot.
Related research
- high · 7.5CVE-2026-58419CVE-2026-58419: Gitea Notification API Private Issue Metadata Leak After Access Revocation
- highCVE-2026-58422CVE-2026-58422: Gitea OAuth2 Callback Improper Access Control Silently Re-enables Disabled Accounts
- high · 7.1CVE-2026-20779CVE-2026-20779: Gitea TOTP Passcode Capture-Replay via Basic-Auth and TOCTOU Race
- high · 7.5CVE-2026-24451CVE-2026-24451: Gitea Fork Sync Information Disclosure via merge-upstream