GHSA-Q89C-Q3H5-W34G
Vulnerability from github – Published: 2026-04-22 17:41 – Updated: 2026-05-11 13:29Summary
Versions of i18next-http-backend prior to 3.0.5 interpolate the lng and ns values directly into the configured loadPath / addPath URL template without any encoding, validation, or path sanitisation. When an application exposes the language-code selection to user-controlled input (the default — i18next-browser-languagedetector reads ?lng= query params, cookies, localStorage, and request headers), an attacker can inject characters that change the structure of the outgoing request URL.
Affected call sites:
_readAny—lib/index.js:64:interpolate(resolvedLoadPath, { lng: languages.join('+'), ns: namespaces.join('+') })create—lib/index.js:123(pre-patch):interpolate(addPath, { lng, ns: namespace })
The helper interpolate (lib/utils.js) previously returned the raw value with no encoding. In contrast, addQueryString already correctly uses encodeURIComponent for each query-string param — only the URL-path substitution was unprotected.
Impact
An attacker who can influence the resolved lng or ns value can alter the URL in several ways:
- Path traversal —
lng = '../../config'turns/locales/{{lng}}/{{ns}}.jsoninto/locales/../../config/translation.json. On a misconfigured web server, this can cause the request to target a different resource than intended; in SSR pipelines that usefile://or similar schemes forloadPath, it can read arbitrary files from the host filesystem. - Query-string injection —
lng = 'en?admin=true'turns/locales/{{lng}}/{{ns}}.jsoninto/locales/en?admin=true/translation.json. Some server frameworks parse the query portion with higher priority than the path and branch on attacker-controlled flags. - Fragment truncation —
lng = 'en#anything'silently discards the rest of the path in browser fetches (client cannot see the final URL). - URL-encoded bypasses —
lng = 'en%2F..', after server-side URL decoding, resolves toen/..— the attacker bypasses the absence of a literal/in their input.
The practical worst case is SSRF when loadPath is an internal or file-scheme URL, and path-based authorisation bypass against servers that segment access by URL prefix.
Also fixed in 3.0.5
- Per-instance
omitFetchOptions. A module-level boolean inlib/request.jswas flipped totruethe first time any backend instance hit a "not implemented" fetch error. Once flipped, all subsequent requests from all backend instances in the same module silently stripped every user-configured fetch option — including security-relevantcredentials,mode, andcache. One misbehaving instance (for example during SSR hydration or in React Native) permanently removed these protections process-wide. 3.0.5 scopes the flag to the backend'soptionsobject (options._omitFetchOptions) so one instance's fallback cannot pollute siblings. - Log forging via control characters in
lng/ns. Error callbacks embedded the rawlng/ns/URL in the message string. Crafted CR/LF values could inject fake log lines into file-backed log aggregators (CWE-117). 3.0.5 strips C0/C1 control chars before concatenation. - Basic-auth credentials leaked into error callbacks. If
loadPathcontained auser:password@hostauthority, the full URL (including the credentials) ended up in the error message strings returned to the caller. 3.0.5 redactsuser:password@before logging. - Prototype-pollution amplification via
for...in.addQueryStringand the XHRcustomHeadersloop usedfor...inwhich walks the prototype chain. PollutedObject.prototypeentries could leak into URL query parameters and request headers. 3.0.5 usesObject.keysand an explicit prototype-key guard.
Affected versions
All versions of i18next-http-backend prior to 3.0.5.
Patch
Fixed in 3.0.5. Summary of the hardening:
- New
utils.interpolateUrl(used by_readAnyandcreate) returnsnullif any substitution fails the URL-segment safety check (blocks..,/,\,?,#,%,@, whitespace, control chars, prototype keys, and values > 128 chars). Multi-language joins (en+de) are validated per-segment. The call sites now refuse to issue a request when the check fails and call back with a clear error. omitFetchOptionsis stored per-instance onoptions._omitFetchOptions.- Error-callback messages sanitise strings and redact URL credentials.
for...inover untrusted objects replaced withObject.keys+ prototype-key guard.
Workarounds
No workaround short of upgrading. If you cannot upgrade immediately, sanitise lng / ns yourself before they reach i18next (strip .., /, \, ?, #, %, whitespace, and control characters; cap the length).
Credits
Discovered via an internal security audit of the i18next ecosystem.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "i18next-http-backend"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.0.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-41691"
],
"database_specific": {
"cwe_ids": [
"CWE-22",
"CWE-74"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-22T17:41:24Z",
"nvd_published_at": "2026-05-07T21:16:29Z",
"severity": "MODERATE"
},
"details": "### Summary\n\nVersions of `i18next-http-backend` prior to 3.0.5 interpolate the `lng` and `ns` values directly into the configured `loadPath` / `addPath` URL template without any encoding, validation, or path sanitisation. When an application exposes the language-code selection to user-controlled input (the default \u2014 `i18next-browser-languagedetector` reads `?lng=` query params, cookies, `localStorage`, and request headers), an attacker can inject characters that change the structure of the outgoing request URL.\n\nAffected call sites:\n\n- `_readAny` \u2014 `lib/index.js:64`: `interpolate(resolvedLoadPath, { lng: languages.join(\u0027+\u0027), ns: namespaces.join(\u0027+\u0027) })`\n- `create` \u2014 `lib/index.js:123` (pre-patch): `interpolate(addPath, { lng, ns: namespace })`\n\nThe helper `interpolate` (`lib/utils.js`) previously returned the raw value with no encoding. In contrast, `addQueryString` already correctly uses `encodeURIComponent` for each query-string param \u2014 only the URL-path substitution was unprotected.\n\n### Impact\n\nAn attacker who can influence the resolved `lng` or `ns` value can alter the URL in several ways:\n\n- **Path traversal** \u2014 `lng = \u0027../../config\u0027` turns `/locales/{{lng}}/{{ns}}.json` into `/locales/../../config/translation.json`. On a misconfigured web server, this can cause the request to target a different resource than intended; in SSR pipelines that use `file://` or similar schemes for `loadPath`, it can read arbitrary files from the host filesystem.\n- **Query-string injection** \u2014 `lng = \u0027en?admin=true\u0027` turns `/locales/{{lng}}/{{ns}}.json` into `/locales/en?admin=true/translation.json`. Some server frameworks parse the query portion with higher priority than the path and branch on attacker-controlled flags.\n- **Fragment truncation** \u2014 `lng = \u0027en#anything\u0027` silently discards the rest of the path in browser fetches (client cannot see the final URL).\n- **URL-encoded bypasses** \u2014 `lng = \u0027en%2F..\u0027`, after server-side URL decoding, resolves to `en/..` \u2014 the attacker bypasses the absence of a literal `/` in their input.\n\nThe practical worst case is **SSRF** when `loadPath` is an internal or file-scheme URL, and **path-based authorisation bypass** against servers that segment access by URL prefix.\n\n### Also fixed in 3.0.5\n\n- **Per-instance `omitFetchOptions`.** A module-level boolean in `lib/request.js` was flipped to `true` the first time any backend instance hit a \"not implemented\" fetch error. Once flipped, **all** subsequent requests from **all** backend instances in the same module silently stripped every user-configured fetch option \u2014 including security-relevant `credentials`, `mode`, and `cache`. One misbehaving instance (for example during SSR hydration or in React Native) permanently removed these protections process-wide. 3.0.5 scopes the flag to the backend\u0027s `options` object (`options._omitFetchOptions`) so one instance\u0027s fallback cannot pollute siblings.\n- **Log forging via control characters in `lng`/`ns`.** Error callbacks embedded the raw `lng`/`ns`/URL in the message string. Crafted CR/LF values could inject fake log lines into file-backed log aggregators (CWE-117). 3.0.5 strips C0/C1 control chars before concatenation.\n- **Basic-auth credentials leaked into error callbacks.** If `loadPath` contained a `user:password@host` authority, the full URL (including the credentials) ended up in the error message strings returned to the caller. 3.0.5 redacts `user:password@` before logging.\n- **Prototype-pollution amplification via `for...in`.** `addQueryString` and the XHR `customHeaders` loop used `for...in` which walks the prototype chain. Polluted `Object.prototype` entries could leak into URL query parameters and request headers. 3.0.5 uses `Object.keys` and an explicit prototype-key guard.\n\n### Affected versions\n\nAll versions of `i18next-http-backend` prior to **3.0.5**.\n\n### Patch\n\nFixed in **3.0.5**. Summary of the hardening:\n\n1. New `utils.interpolateUrl` (used by `_readAny` and `create`) returns `null` if any substitution fails the URL-segment safety check (blocks `..`, `/`, `\\`, `?`, `#`, `%`, `@`, whitespace, control chars, prototype keys, and values \u003e 128 chars). Multi-language joins (`en+de`) are validated per-segment. The call sites now refuse to issue a request when the check fails and call back with a clear error.\n2. `omitFetchOptions` is stored per-instance on `options._omitFetchOptions`.\n3. Error-callback messages sanitise strings and redact URL credentials.\n4. `for...in` over untrusted objects replaced with `Object.keys` + prototype-key guard.\n\n### Workarounds\n\nNo workaround short of upgrading. If you cannot upgrade immediately, sanitise `lng` / `ns` yourself before they reach i18next (strip `..`, `/`, `\\`, `?`, `#`, `%`, whitespace, and control characters; cap the length).\n\n### Credits\n\nDiscovered via an internal security audit of the i18next ecosystem.",
"id": "GHSA-q89c-q3h5-w34g",
"modified": "2026-05-11T13:29:17Z",
"published": "2026-04-22T17:41:24Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/i18next/i18next-http-backend/security/advisories/GHSA-q89c-q3h5-w34g"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41691"
},
{
"type": "WEB",
"url": "https://github.com/i18next/i18next-http-backend/commit/4cee84f229c637b9c182366d3156f726d407a621"
},
{
"type": "PACKAGE",
"url": "https://github.com/i18next/i18next-http-backend"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": " i18next-http-backend has Path Traversal \u0026 URL Injection via Unsanitised lng/ns"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.