GitHub Security Advisory: GHSA-jfgf-83c5-2c4m
Package: i18next-http-middleware (NPM)
Severity: HIGH (CVSS 8.2)
CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: < 3.9.3
Patched in: 3.9.3

Summary: i18next-http-middleware has path traversal / SSRF via user-controlled language and namespace parameters

Details:
### Summary

Versions of `i18next-http-middleware` prior to 3.9.3 pass the user-controlled `lng` and `ns` values from `getResourcesHandler` directly into `i18next.services.backendConnector.load(languages, namespaces, …)` without any sanitisation. Depending on which backend is configured, the unvalidated path segments enable one of two attacks:

- **Filesystem path traversal** when the middleware is paired with `i18next-fs-backend` (or any backend that interpolates `lng` / `ns` into a filesystem path).
- **Server-Side Request Forgery (SSRF)** when the middleware is paired with `i18next-http-backend` (or any backend that interpolates into an HTTP URL).

Example request:

```
GET /locales/resources.json?lng=../../etc/passwd&ns=root
```

with `i18next-fs-backend` reads the attacker-chosen file from disk; with `i18next-http-backend` reshapes the outgoing URL to target an internal service.

### Impact

- **Arbitrary file read** via `fs`-style backends — any file the Node process can read becomes reachable (source, configuration, `.ssh` keys, `.env`, Docker secrets, etc.).
- **SSRF** via `http`-style backends — requests to internal IPs / hostnames not normally reachable from the internet; combined with cloud metadata endpoints this can escalate to credential theft.
- **Unbounded growth of `i18next.options.ns`** — a now-incidental amplification: the pre-patch `getResourcesHandler` pushed every unique `ns` value into the shared `i18next.options.ns` singleton array without validation or bounds, enabling memory exhaustion from repeated unique payloads.

The severity is bounded by the backend in place, but the middleware itself exposed the unsanitised path; this is the "weakest link" layer.

### Affected versions

`< 3.9.3`.

### Patch

Fixed in **3.9.3**. The patch introduces `utils.isSafeIdentifier` and applies it in `getResourcesHandler` before `lng` and `ns` reach the backend connector:

```js
languages  = languages.filter(utils.isSafeIdentifier)
namespaces = namespaces.filter(utils.isSafeIdentifier)
```

`isSafeIdentifier` uses a denylist approach — it still accepts any legitimate i18next language-code shape ([i18next FAQ](https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted)) — rejecting:

- `..` sequences (relative path traversal)
- path separators (`/`, `\`)
- control characters (C0/C1)
- prototype keys (`__proto__` / `constructor` / `prototype`)
- empty strings and values longer than 128 characters

Unsafe values are dropped; only safe values reach the backend. The fix is a defence-in-depth layer on top of any sanitisation the backend itself may apply.

### Workarounds

No workaround short of upgrading. Front-proxying the middleware with a WAF rule that rejects requests containing `..`, `/`, `\`, or URL-structure characters in `lng` / `ns` is a partial mitigation. Upgrading the configured backend (`i18next-fs-backend` ≥ 2.6.4, `i18next-http-backend` ≥ 3.0.5) also closes the same attack at the next layer.

### Related advisor