Repository navigation
Description
Problem
The docs site's locale middleware serves a language the client explicitly refused with q=0, and a malformed q value poisons its sort — the exact bug class fixed for the console in #1309, in a second, unfixed copy of the same Accept-Language parsing logic.
Evidence (main 07eab62f38)
packages/web/src/middleware.ts, localeFromAcceptLanguage (lines 52–84):
- Line 66:
q: q ? Number.parseFloat(q) : 1— no finiteness check, soq=abcbecomesNaNand the comparator at line 73 (b.q - a.q) returnsNaN, which sort treats as0: the refused/malformed entry keeps its original position instead of sorting last. - Lines 76–80: the parsed items are mapped to locales and the first match is returned — there is no refusal skip, so an entry with an explicit finite
q=0(orq=0.0, or negative) is still served if it is the first matching locale. - Lines 86–97 (
onRequest): the result drives a real user-visible redirect — a/docsor/docs/request is 302-redirected to/docs/<locale>/(with anoc_localecookie) based on this function.
The console's copy was fixed in #1309 (PR #1312, merged): packages/console/app/src/lib/language.ts detectFromAcceptLanguage (lines 267–303) now treats a malformed q as 0-weight last-resort and an explicit finite q<=0 as a refusal that is never served. The web middleware copy predates / escaped that fix.
Reproduction (laptop probe, this run)
Probe ~/git/fleet/probe-2026-11-01-web-accept-language/probe.ts on the laptop runs the middleware's function verbatim (with matchLocale stubbed to the real mapping in packages/web/src/i18n/locales.ts for the tested tags):
| Accept-Language | Result | Expected |
|---|---|---|
fr;q=0 |
fr |
root — fr was refused |
fr;q=0, de;q=0 |
fr |
root — both refused |
de;q=0.0, es;q=0 |
de |
root — both refused |
fr;q=0, de;q=0.5 |
de |
de (control, passes) |
fr |
fr |
fr (control, passes) |
fr;q=abc, de;q=0.5 |
fr |
de (or fr only as last resort) — NaN must not poison the sort |
No test covers this function: packages/web has src/i18n/locales.test.ts, src/lib/share-data.test.ts, and src/pages/s/_share-model-param.test.ts, but nothing for the middleware's Accept-Language handling, and localeFromAcceptLanguage is not exported.
Why it matters
q=0 in Accept-Language means "not acceptable" (RFC 9110 §12.4.2). A user (or a crawler / privacy tool) that explicitly refuses a language is nonetheless redirected into that language's docs and handed a one-year oc_locale cookie pinning the refused choice. Browsers rarely send q=0 for their top language, which is why this survives unnoticed — but the console copy was judged worth fixing in #1309, and the two copies disagreeing means the docs site's behavior depends on which surface a visitor hits.
Acceptance criteria
- An explicit finite
q<=0language is never served bylocaleFromAcceptLanguage: parsing falls through to the next accepted language, or toroot(no redirect) when none is accepted. - A malformed
qvalue is treated as a 0-weight last-resort offer (mirroring fix(console): detectFromAcceptLanguage serves a language the client explicitly refused with q=0 #1309's pinned console semantics), never asNaNin the sort comparator. - Normal headers behave exactly as before (controls: bare language defaults to
q=1; higher-qaccepted language wins). - Tests cover the refused, all-refused, malformed-
q, and control cases and pass in thepackages/webtest suite (export the helper or extract it the waypackages/web/src/pages/s/_share-model-param.test.tsisolates its logic, if the Astro middleware import blocks direct testing).
Related
- fix(console): detectFromAcceptLanguage serves a language the client explicitly refused with q=0 #1309 — the console fix this mirrors (
detectFromAcceptLanguage,packages/console/app/src/lib/language.ts) - fix(ci): check:numeric-boundaries still misses the console and function packages — client- and upstream-supplied conversions escape the regression net #1302 — malformed-
qNaN-poisoning disposition for the console/function packages
Activity
dezren39 commentedon Nov 1, 2026
🤖 Agent note · state: filed
dezren39 commentedon Nov 1, 2026
Parked: PR #1317 is open with the fix —
localeFromAcceptLanguageextracted topackages/web/src/lib/accept-language.tsmirroring the merged console semantics from #1309/PR #1312 (explicit finiteq<=0= refusal, never served; malformedq= 0-weight last-resort, noNaNin the comparator), with 16 new tests.packages/websuite: 48 pass / 0 fail on the laptop worktree; Prettier clean on the touched files.Task 5879 is parked until 2026-11-01T19:38Z for PR #1317 CI + Codex review; on resume it merges if green with no undispositioned Codex findings.
dezren39 commentedon Nov 1, 2026
agent-note:v1 -- repo-queue-worker-shift-opencode (lane A, task 5879, completed)