Summary
nginx-ignition ships a small i18n middleware that reads the
Accept-Language header on every request and picks the first language it
supports. An earlier advisory (GHSA-jr34-h97m-9hpx) showed that header
could be abused for a quadratic-cost CPU attack, and the maintainer added a
guard against it. That guard counts the wrong thing: it bounds the number
of -/_ characters in the header, not the number of comma-separated
language tags or the header’s total length. A header built from many
short, valid-but-unsupported tags sails straight past it and still costs
50x more CPU than the same number of bytes the server doesn’t parse.
Background
The middleware runs on every single request, unauthenticated, before routing even happens:
const maximumLanguageTags = 10
func i18nMiddleware(commands i18n.Commands) gin.HandlerFunc {
return func(ginCtx *gin.Context) {
lang := commands.DefaultLanguage()
langHeader := ginCtx.GetHeader("Accept-Language")
if strings.Count(langHeader, "-")+strings.Count(langHeader, "_") > maximumLanguageTags {
langHeader = ""
}
tags, _, err := language.ParseAcceptLanguage(langHeader)
if err == nil && len(tags) > 0 {
for _, tag := range tags {
if commands.Supports(tag) {
lang = tag
break
}
}
}
The maximumLanguageTags check was added specifically to close the prior
advisory’s exploit path — it assumes counting separator characters is a
good enough proxy for how expensive the header will be to process.
The vulnerability
It isn’t, for two reasons:
golang.org/x/text’sParseAcceptLanguagesplits the input on,before it ever looks at hyphens or underscores. A header made of thousands of comma-separated two-letter tags (aa,aa,aa,...) can have a hyphen/underscore count of exactly zero and still contain hundreds of thousands of tags.- When a tag parses successfully but doesn’t match any language the
server actually supports, the middleware’s loop never breaks early — it
calls
commands.Supports(tag)for every tag, and each of those calls scans all 11 supported-language dictionaries.
So a 1 MB header of aa, repeated has a separator count of zero (no
hyphens, no underscores — the guard never fires), parses as ~333,000
valid-but-unsupported tags, and makes the middleware do a full
11-dictionary scan for every single one of them before giving up and
falling back to the default language.
Measured against the official 2.45.1 release binary, that header costs
306 ms of server CPU per request — versus 6 ms for the same 1 MB
sent in a header nobody parses. A single connection sustaining that
traffic at 32.5 Mbit/s keeps 1.28 CPU cores busy on the server the
entire time.
Reachable path
No credentials, no session, no valid application state — a single HTTP request to any path (the middleware is global, so the target endpoint doesn’t even need to exist):
GET /api/i18n HTTP/1.1
Host: target
Accept-Language: aa,aa,aa,... (1 MB)
Impact
Unauthenticated, bandwidth-bounded CPU exhaustion against the
nginx-ignition API listener. One attacker connection uploading roughly
32.5 Mbit/s of Accept-Language header keeps more than one CPU core busy
on the server; two such connections saturate a two-vCPU host, three or
four saturate a four-core one — and since nginx-ignition typically shares
the box with the nginx instance it manages, that instance’s availability
degrades along with it. Confidentiality and integrity are unaffected —
this is purely an availability issue.
Suggested fix
Bound the two quantities the existing guard leaves open, before the header ever reaches the parser:
const (
maximumLanguageTags = 10 // existing: separator cap
maximumHeaderBytes = 128 // new: real Accept-Language values are tiny
)
langHeader := ginCtx.GetHeader("Accept-Language")
if len(langHeader) > maximumHeaderBytes ||
strings.Count(langHeader, "-")+strings.Count(langHeader, "_") > maximumLanguageTags ||
strings.Count(langHeader, ",") > maximumLanguageTags {
langHeader = ""
}
The middleware only ever needs the first tag it supports, so anything past a handful of bytes is already more than a legitimate client would ever send.
Timeline
- 2026-09-23 — Reported via GitHub’s private security-advisory reporting.
- 2026-09-23/24 — Fixed same day, shipped in 2.45.2.
- 2026-09-24 — Advisory published as
GHSA-969c-hr69-6q97, High (CVSS 3.1AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H= 7.5).
Disclosure
Full advisory: GHSA-969c-hr69-6q97.