i18next-http-backend: Query-parameter injection through & and = in lng when loadPath/addPath puts {{lng}} in the query string
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-118258
Publishedi18next-http-backend is a backend layer for i18next that loads translation resources in Node.js, browsers, and Deno. Prior to 4.0.4, interpolateUrl inserts unencoded language and namespace values into loadPath or addPath templates without rejecting ampersand (&), equals (=), or semicolon (;) query separators. When a template places lng or ns in the query string and an attacker controls that value, the attacker can add or override parameters on browser or server-side translation requests, potentially exposing other namespaces or tenants or changing backend behavior through the application's network position and credentials. This vulnerability is the result of an incomplete fix for CVE-2026-41691. This issue is fixed in version 4.0.4.
- Source published
- 28 Oct 2026 18:33:30 UTC
- Source modified
- 3 Nov 2026 13:15:26 UTC
- Observed here
- 3 Nov 2026 14:57:17 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · 3.7 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
euvd · EUVD-2026-112360
Publishedi18next-http-backend is a backend layer for i18next that loads translation resources in Node.js, browsers, and Deno. Prior to 4.0.4, interpolateUrl inserts unencoded language and namespace values into loadPath or addPath templates without rejecting ampersand (&), equals (=), or semicolon (;) query separators. When a template places lng or ns in the query string and an attacker controls that value, the attacker can add or override parameters on browser or server-side translation requests, potentially exposing other namespaces or tenants or changing backend behavior through the application's network position and credentials. This vulnerability is the result of an incomplete fix for CVE-2026-41691. This issue is fixed in version 4.0.4.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Oct 28, 2026, 6:33:30 PM
dateUpdated: Nov 3, 2026, 1:15:26 PM
- Source published
- Oct 28, 2026, 6:33:30 PM (timezone not stated)
- Source modified
- Nov 3, 2026, 1:15:26 PM (timezone not stated)
- Observed here
- 6 Nov 2026 00:45:10 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 3.1 · 3.7 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
nvd · CVE-2026-118258
Publishedi18next-http-backend is a backend layer for i18next that loads translation resources in Node.js, browsers, and Deno. Prior to 4.0.4, interpolateUrl inserts unencoded language and namespace values into loadPath or addPath templates without rejecting ampersand (&), equals (=), or semicolon (;) query separators. When a template places lng or ns in the query string and an attacker controls that value, the attacker can add or override parameters on browser or server-side translation requests, potentially exposing other namespaces or tenants or changing backend behavior through the application's network position and credentials. This vulnerability is the result of an incomplete fix for CVE-2026-41691. This issue is fixed in version 4.0.4.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Deferred
Weakness classifications: CWE-74
Original publisher date fields
published: 2026-10-28T19:17:30.360
lastModified: 2026-11-03T14:17:25.400
- Source published
- 28 Oct 2026 19:17:30 UTC
- Source modified
- 3 Nov 2026 14:17:25 UTC
- Observed here
- 3 Nov 2026 23:58:59 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · [email protected] · Secondary · 3.7 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
osv · GHSA-3cmx-xjww-583x
Published### Summary The URL-segment check added for GHSA-q89c-q3h5-w34g blocks `?`, `#`, `%`, `:`, `/` and `@` in `lng`, but not `&` or `=`, and the checked value is inserted into the URL without encoding. The README documents a multiload loadPath of the form `/locales/resources.json?lng=de+en&ns=ns1+ns2`. With a template like that, an attacker who controls the language value can add or override query parameters on the request the backend sends. By default the language comes from `?lng=`, a cookie or `Accept-Language`. This is the "query-string injection" impact described in GHSA-q89c; only the `?` form was closed. Server side (Express + i18next-http-middleware, default options), `GET /?lng=de%26ns%3Dprivate%26admin%3Dtrue` makes the application request `…?lng=de&ns=private&admin=true&ns=common` from its own translations API. ### Details The checks are in `lib/utils.js`. The base check (https://github.com/i18next/i18next-http-backend/blob/v4.0.3/lib/utils.js#L26-L44) rejects: - empty or >128-character values and prototype keys; - `..` and `\`; - `/[?#%:\s]/`; - control characters. `isSafeLangUrlSegment` (https://github.com/i18next/i18next-http-backend/blob/v4.0.3/lib/utils.js#L51-L55) adds only `/` and `@`. **Neither function rejects `&` or `=`.** ```js if (/[?#%:\s]/.test(v)) return false // utils.js L40, no & or = ``` `interpolateUrl` then splits on `+`, checks each segment, and joins the **raw** segments into the template, with no encoding (https://github.com/i18next/i18next-http-backend/blob/v4.0.3/lib/utils.js#L159-L184, return at L174). Call sites: - `_readAny` builds the load URL with `interpolateUrl(resolvedLoadPath, { lng: languages.join('+'), ns: … })`: https://github.com/i18next/i18next-http-backend/blob/v4.0.3/lib/index.js#L64 - `create` builds the missing-key URL with `interpolateUrl(addPath, { lng, ns })`: https://github.com/i18next/i18next-http-backend/blob/v4.0.3/lib/index.js#L133 The README documents the query-shaped URL for multiloading (https://github.com/i18next/i18next-http-backend/blob/v4.0.3/README.md?plain=1#L130). The language value is attacker-controlled in normal setups, as GHSA-q89c itself says: detectors read `?lng=`, cookies, `localStorage` and request headers. i18next-http-middleware's `LanguageDetector` does the same on the server. Where `?` is placed in the template determines which characters are structural. The fix treated only the path position. In a query position, `&` and `=` are the separators, so they must be rejected or encoded too. **Suggested fix.** Reject `&` and `=` (and `;`) in both `lng` and `ns`; no BCP-47 language code or usable i18next namespace contains them. Alternatively, `encodeURIComponent` each `+`-joined segment when the placeholder appears after `?` in the template. ### Proof of concept How to run (Node 18+; everything runs on 127.0.0.1, with a fake translations API and an Express app): 1. `mkdir i18next-poc && cd i18next-poc` 2. `npm init -y` 3. `npm i [email protected] [email protected] [email protected] [email protected]` 4. Save the script below as `poc.mjs` 5. `node poc.mjs` ```js // i18next-http-backend 4.0.3: '&' and '=' in lng pass isSafeLangUrlSegment, so a loadPath that puts // {{lng}} in the query string (README multiload shape) gets extra parameters. // Everything runs on 127.0.0.1: a fake translations API and an Express app. import http from 'node:http' import express from 'express' import i18next from 'i18next' import Backend from 'i18next-http-backend' import middleware from 'i18next-http-middleware' const seen = [] const api = http.createServer((req, res) => { seen.push(req.url) res.setHeader('content-type', 'application/json') res.end('{"hello":"hi"}') }) await new Promise((r) => api.listen(0, '127.0.0.1', r)) const loadPath = `http://127.0.0.1:${api.address().port}/locales/resources.json?lng={{lng}}&ns={{ns}}` const wait = () => new Promise((r) => setTimeout(r, 200)) // 1) Direct: lng values passed to i18next as-is. console.log('[1] direct init') for (const lng of ['de', 'de?admin=true', 'de&ns=private&admin=true']) { seen.length = 0 await i18next.createInstance().use(Backend).init({ lng, fallbackLng: false, ns: ['common'], backend: { loadPath } }) console.log(` lng=${JSON.stringify(lng).padEnd(28)} -> API got: ${seen.length ? seen.join(' ') : '(no request, refused)'}`) } // 2) Server side: language taken from the request by i18next-http-middleware (default options). console.log('[2] Express + i18next-http-middleware LanguageDetector') const i18n = i18next.createInstance() await i18n.use(Backend).use(middleware.LanguageDetector) .init({ fallbackLng: 'en', preload: ['en'], ns: ['common'], defaultNS: 'common', backend: { loadPath } }) const app = express() app.use(middleware.handle(i18n)) app.get('/', (req, res) => res.send(req.t('hello'))) const web = app.listen(0, '127.0.0.1') await new Promise((r) => web.on('listening', r)) await wait() for (const q of ['de', 'de%26ns%3Dprivate%26admin%3Dtrue']) { seen.length = 0 await fetch(`http://127.0.0.1:${web.address().port}/?lng=${q}`).then((r) => r.text()) await wait() console.log(` GET /?lng=${q.padEnd(32)} -> API got: ${seen.join(' ')}`) } web.close() api.close() ``` Output (i18next-http-backend 4.0.3, i18next 26.5.1, i18next-http-middleware 3.9.9, Node 22): ``` [1] direct init lng="de" -> API got: /locales/resources.json?lng=de&ns=common lng="de?admin=true" -> API got: (no request, refused) lng="de&ns=private&admin=true" -> API got: /locales/resources.json?lng=de&ns=private&admin=true&ns=common [2] Express + i18next-http-middleware LanguageDetector GET /?lng=de -> API got: /locales/resources.json?lng=de&ns=common GET /?lng=de%26ns%3Dprivate%26admin%3Dtrue -> API got: /locales/resources.json?lng=de&ns=private&admin=true&ns=common ``` The `?` control is refused by the existing fix. The `&` form passes and reaches the API with injected `ns=private&admin=true`. ### Impact This is injection of query parameters (CWE-74, HTTP parameter pollution) into the backend's outgoing requests: - **Server side** (SSR, Node, i18next-http-middleware): an unauthenticated visitor can make the application server send requests to its internal or partner translations API with parameters of the visitor's choosing. The visitor can override existing ones (`ns`, version, tenant, feature flags, cache keys) using the server's network position and credentials. Depending on how that API branches on its parameters, this can expose other namespaces or tenants' resources, or change server-side behaviour. - **Browser side**: a crafted link (`?lng=…`) does the same to the victim's same-origin request. Applications using the default path template `/locales/{{lng}}/{{ns}}.json`, or a `supportedLngs` allowlist, are not affected. credit to Adrian Korwel. ## Maintainer note Confirmed and fixed in 4.0.4 (commit i18next/i18next-http-backend@b0eef9e). **Preconditions.** The `loadPath` / `addPath` template must put `{{lng}}` or `{{ns}}` in the query string (for example `/locales/resources.json?lng={{lng}}&ns={{ns}}`, the multiload shape), and the attacker must control the language or namespace value. The default `/locales/{{lng}}/{{ns}}.json` and other path-only templates are not affected, nor are applications that restrict languages with `supportedLngs`. The impact depends on what the translations API does with the injected parameters. **Fix.** `&`, `=` and `;` are now rejected in both `lng` and `ns` values, in addition to the `?`, `#`, `%` and `:` rejected before. No language code or usable namespace name contains them. `+`-joined multiload values such as `de+en` keep working, verified end to end with i18next-multiload-backend-adapter. **Affected range.** Versions before 3.0.5 had no value validation at all and are reachable through this vector too, so the affected range is `< 4.0.4`. **Related.** i18next-locize-backend had the same gap with custom query-shaped templates (its default paths are not affected). It is fixed in i18next-locize-backend 10.0.4.
- Source published
- 28 Oct 2026 21:10:08 UTC
- Source modified
- 28 Oct 2026 21:30:03 UTC
- Observed here
- 28 Oct 2026 21:55:29 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
Publisher rating · LOW