i18next-http-middleware 3.9.4 → 3.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/index.js CHANGED
@@ -251,8 +251,8 @@ function getResourcesHandler(i18next) {
251
251
  languages = options.getParams(req)[options.lngParam || 'lng'] ? options.getParams(req)[options.lngParam || 'lng'].split(' ') : [];
252
252
  namespaces = options.getParams(req)[options.nsParam || 'ns'] ? options.getParams(req)[options.nsParam || 'ns'].split(' ') : [];
253
253
  }
254
- languages = languages.filter(utils.isSafeIdentifier);
255
- namespaces = namespaces.filter(utils.isSafeIdentifier);
254
+ languages = languages.filter(utils.isSafeLangIdentifier);
255
+ namespaces = namespaces.filter(utils.isSafeNsIdentifier);
256
256
  namespaces.forEach(function (ns) {
257
257
  if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
258
258
  i18next.options.ns.push(ns);
package/cjs/utils.js CHANGED
@@ -8,20 +8,31 @@ exports.defaults = defaults;
8
8
  exports.escape = escape;
9
9
  exports.extend = extend;
10
10
  exports.hasXSS = hasXSS;
11
- exports.isSafeIdentifier = isSafeIdentifier;
11
+ exports.isSafeIdentifier = void 0;
12
+ exports.isSafeLangIdentifier = isSafeLangIdentifier;
13
+ exports.isSafeNsIdentifier = isSafeNsIdentifier;
12
14
  exports.removeLngFromUrl = removeLngFromUrl;
13
15
  exports.sanitizeHeaderValue = sanitizeHeaderValue;
14
16
  exports.setPath = setPath;
15
17
  var UNSAFE_KEYS = exports.UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
16
- function isSafeIdentifier(v) {
18
+ function isSafeIdentifierBase(v) {
17
19
  if (typeof v !== 'string') return false;
18
20
  if (v.length === 0 || v.length > 128) return false;
19
21
  if (UNSAFE_KEYS.indexOf(v) > -1) return false;
20
22
  if (v.indexOf('..') > -1) return false;
21
- if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
23
+ if (v.indexOf('\\') > -1) return false;
22
24
  if (/[\x00-\x1F\x7F]/.test(v)) return false;
23
25
  return true;
24
26
  }
27
+ function isSafeLangIdentifier(v) {
28
+ if (!isSafeIdentifierBase(v)) return false;
29
+ if (v.indexOf('/') > -1) return false;
30
+ return true;
31
+ }
32
+ function isSafeNsIdentifier(v) {
33
+ return isSafeIdentifierBase(v);
34
+ }
35
+ var isSafeIdentifier = exports.isSafeIdentifier = isSafeLangIdentifier;
25
36
  function setPath(object, path, newValue) {
26
37
  var stack;
27
38
  if (typeof path !== 'string') stack = [].concat(path);
package/esm/index.js CHANGED
@@ -234,8 +234,8 @@ export function getResourcesHandler(i18next) {
234
234
  languages = options.getParams(req)[options.lngParam || 'lng'] ? options.getParams(req)[options.lngParam || 'lng'].split(' ') : [];
235
235
  namespaces = options.getParams(req)[options.nsParam || 'ns'] ? options.getParams(req)[options.nsParam || 'ns'].split(' ') : [];
236
236
  }
237
- languages = languages.filter(utils.isSafeIdentifier);
238
- namespaces = namespaces.filter(utils.isSafeIdentifier);
237
+ languages = languages.filter(utils.isSafeLangIdentifier);
238
+ namespaces = namespaces.filter(utils.isSafeNsIdentifier);
239
239
  namespaces.forEach(function (ns) {
240
240
  if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
241
241
  i18next.options.ns.push(ns);
package/esm/utils.js CHANGED
@@ -1,13 +1,22 @@
1
1
  export var UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
2
- export function isSafeIdentifier(v) {
2
+ function isSafeIdentifierBase(v) {
3
3
  if (typeof v !== 'string') return false;
4
4
  if (v.length === 0 || v.length > 128) return false;
5
5
  if (UNSAFE_KEYS.indexOf(v) > -1) return false;
6
6
  if (v.indexOf('..') > -1) return false;
7
- if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
7
+ if (v.indexOf('\\') > -1) return false;
8
8
  if (/[\x00-\x1F\x7F]/.test(v)) return false;
9
9
  return true;
10
10
  }
11
+ export function isSafeLangIdentifier(v) {
12
+ if (!isSafeIdentifierBase(v)) return false;
13
+ if (v.indexOf('/') > -1) return false;
14
+ return true;
15
+ }
16
+ export function isSafeNsIdentifier(v) {
17
+ return isSafeIdentifierBase(v);
18
+ }
19
+ export var isSafeIdentifier = isSafeLangIdentifier;
11
20
  export function setPath(object, path, newValue) {
12
21
  var stack;
13
22
  if (typeof path !== 'string') stack = [].concat(path);
package/lib/index.js CHANGED
@@ -258,8 +258,12 @@ export function getResourcesHandler (i18next, options = {}) {
258
258
  // connector. i18next itself permits arbitrary language codes, so we do
259
259
  // not impose a BCP-47 shape — we only block known-dangerous patterns.
260
260
  // See: https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted
261
- languages = languages.filter(utils.isSafeIdentifier)
262
- namespaces = namespaces.filter(utils.isSafeIdentifier)
261
+ // lng goes through the strict check (rejects `/`); ns goes through
262
+ // the loose check (allows `/` so nested-namespace layouts like `a/b`
263
+ // that map to `public/locales/en/a/b.json` keep working). Directory
264
+ // escape via `..` / `\` / control chars is still blocked on both.
265
+ languages = languages.filter(utils.isSafeLangIdentifier)
266
+ namespaces = namespaces.filter(utils.isSafeNsIdentifier)
263
267
 
264
268
  // extend ns
265
269
  namespaces.forEach(ns => {
package/lib/utils.js CHANGED
@@ -1,25 +1,48 @@
1
1
  export const UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype']
2
2
 
3
- // Returns true if `v` can be safely forwarded as a language code or namespace
4
- // identifier to a backend connector. Denylist approach (i18next permits any
5
- // language-code shape — https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted)
6
- // that blocks the concrete attack patterns without restricting legitimate use:
7
- // - not one of `__proto__` / `constructor` / `prototype` (prototype pollution)
8
- // - no path separators `/` or `\` (path traversal / SSRF in fs/http backends)
9
- // - no `..` sequence (relative path traversal)
10
- // - no control characters (header injection downstream, log forging)
11
- // - non-empty, length <= 128
12
- export function isSafeIdentifier (v) {
3
+ // Shared denylist: the patterns that are always unsafe to forward to a
4
+ // backend connector, regardless of whether the value is a language code
5
+ // or a namespace name. Blocks directory escape (`..`), Windows path
6
+ // separators (`\`), control characters, prototype keys, empty strings,
7
+ // and oversized inputs. The one dimension that differs per-key is whether
8
+ // `/` is allowed — see the two per-key helpers below.
9
+ function isSafeIdentifierBase (v) {
13
10
  if (typeof v !== 'string') return false
14
11
  if (v.length === 0 || v.length > 128) return false
15
12
  if (UNSAFE_KEYS.indexOf(v) > -1) return false
16
13
  if (v.indexOf('..') > -1) return false
17
- if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false
14
+ if (v.indexOf('\\') > -1) return false
18
15
  // eslint-disable-next-line no-control-regex
19
16
  if (/[\x00-\x1F\x7F]/.test(v)) return false
20
17
  return true
21
18
  }
22
19
 
20
+ // Strict — also rejects `/`. Applied to `lng`:
21
+ // no legitimate BCP-47 / i18next language-code shape
22
+ // (https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted)
23
+ // contains `/`, so allowing it would only enable attacks.
24
+ export function isSafeLangIdentifier (v) {
25
+ if (!isSafeIdentifierBase(v)) return false
26
+ if (v.indexOf('/') > -1) return false
27
+ return true
28
+ }
29
+
30
+ // Loose — allows `/`. Applied to `ns`:
31
+ // nested namespace names like `a/b` that map to subfolder locale layouts
32
+ // (`public/locales/en/a/b.json` on fs-backend, `/locales/en/a/b.json`
33
+ // on http-backend) are a documented i18next pattern and were rejected
34
+ // in 3.9.3 as an unintended regression. Directory escape is still
35
+ // prevented — `..` is still blocked, `\` is still blocked — so the
36
+ // security fix remains in force for every concrete attack pattern
37
+ // from the original advisory.
38
+ export function isSafeNsIdentifier (v) {
39
+ return isSafeIdentifierBase(v)
40
+ }
41
+
42
+ // Backwards-compatible alias for the strict check. Kept because 3.9.3
43
+ // exported this symbol; external callers (if any) get the pre-fix behaviour.
44
+ export const isSafeIdentifier = isSafeLangIdentifier
45
+
23
46
  export function setPath (object, path, newValue) {
24
47
  let stack
25
48
  if (typeof path !== 'string') stack = [].concat(path)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-http-middleware",
3
- "version": "3.9.4",
3
+ "version": "3.9.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./cjs/index.js",