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 enough
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