CVE-2026-62160 — Vulnerability intelligence
— github.com/daptin/daptin
Published:
- CVSS
- 0
- CISA KEV
- Not listed; not proof of no exploitation
Original source text (not a translation)
Daptin: ParseAcceptLanguage `_` separator bypass enables >250x CPU amplification via Accept-Language header in LanguageMiddleware
### Summary
Daptin's `LanguageMiddleware` (registered as a global gin middleware) calls `golang.org/x/text/language.ParseAcceptLanguage` on the raw `Accept-Language` header of every HTTP request without imposing any 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 (only 256 KiB, not even the 1 MiB ceiling) consumes about 27 seconds of server CPU on the host running Daptin. A 1 MiB payload exceeds the 2-minute test client timeout. A single attacker on a 1 Mbit/s uplink can pin an entire Daptin host with one request every 30 seconds.
### Affected versions
`github.com/daptin/daptin` v0.12.15 and (per code inspection of `master`) all earlier 0.12.x versions that wire `LanguageMiddleware` on the global gin router without imposing their own size limit on `Accept-Language`. Verified on:
- the official release binary `daptin-linux-amd64` v0.12.15 built into a Docker image from the project's own `Dockerfile` (E2E below)
- `master` at commit `b021ac4ae1f72b7401e851fcf883bc970303c794` by reading `server/language.go` (the middleware is unchanged)
### Privilege required
Unauthenticated. The middleware is registered on the global gin router that serves both the public landing page and the unauthenticated `/api` style endpoints. Anyone who can reach the HTTP listener (port 8080 by default) is in scope.
### Vulnerable code
[`server/language.go`](https://github.com/daptin/daptin/blob/b021ac4ae1f72b7401e851fcf883bc970303c794/server/language.go) (blob SHA `b021ac4ae1f72b7401e851fcf883bc970303c794`):
```go
func (lm *LanguageMiddleware) LanguageMiddlewareFunc(c *gin.Context) {
pref := GetLanguagePreference(c.GetHeader("Accept-Language"), lm.defaultLanguage)
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "language_preference", pref))
}
func GetLanguagePreference(header string, defaultLanguage string) []string {
preferredLanguage := header
if preferredLanguage == "" || preferredLanguage == "undefined" || preferredLanguage == "null" {
preferredLanguage = defaultLanguage
}
languageTags, _, err := language.ParseAcceptLanguage(preferredLanguage)
resource.CheckErr(err, "Failed to parse Accept-Language header [%v]", preferredLanguage)
pref := make([]string, 0)
prefMap := make(map[string]bool)
if len(languageTags) == 1 && languageTags[0].String() == defaultLanguage {
// single tag matches default — skip
} else {
for _, tag := range languageTags {
base, conf := tag.Base()
if conf == 0 {
continue
}
if prefMap[base.String()] == true {
continue
}
prefMap[base.String()] = true
pref = append(pref, base.String())
}
}
return pref
}
```
`c.GetHeader("Accept-Language")` is the unfiltered HTTP header. Go's default `net/http` `MaxHeaderBytes` is `1 << 20` = 1 MiB and Daptin does not override it, so the parser is allowed to receive up to a megabyte of attacker-controlled data.
The `resource.CheckErr` call on the error path also formats the entire input string into a log line (`Failed to parse Accept-Language header [%v]`), amplifying the cost on attacker-supplied 256 KiB-1 MiB payloads.
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 256 KiB header full of 9-character `_abcdefghi` tokens contains zero `-` characters, passes the guard, and then drives the scanner into the O(N²) `gobble` path.
### How `Accept-Language` reaches `ParseAcceptLanguage`
Every HTTP request that hits the Daptin API server passes through `LanguageMiddleware` (registered as a global gin middleware). The middleware sequence is:
1. The request enters `LanguageMiddlewareFunc`.
2. `c.GetHeader("Accept-Language")` returns the full attacker-supplied header value.
3. `GetLanguagePreference(header, defaultLanguage)` is called.
4. The function passes the unfiltered header into `language.ParseAcceptLanguage(preferredLanguage)`.
No size or character-class filter is applied between (2) and (4). The middleware runs for every gin handler — including the unauthenticated landing page at `/` and the public API root at `/api`.
### Proof of concept
Single-line bash reproducer that crafts the malicious header and times one request against a fresh Daptin container built fr