mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-21 20:18:06 +02:00
Performance and precision pass (#64)
This commit is contained in:
parent
c7c5e0f3a1
commit
fb698d2c27
97 changed files with 9932 additions and 517 deletions
|
|
@ -0,0 +1,60 @@
|
|||
// Nyx CVE benchmark fixture (patched).
|
||||
//
|
||||
// CVE: CVE-2026-42353
|
||||
// GHSA: GHSA-jfgf-83c5-2c4m
|
||||
// Project: i18next-http-middleware (i18next/i18next-http-middleware)
|
||||
// License: MIT (https://github.com/i18next/i18next-http-middleware/blob/master/licence)
|
||||
// Patched: 65301c194593d46a84623b64e5fde2f51d3550f6 lib/utils.js:1-22, lib/index.js:243-250
|
||||
// Release: v3.9.3
|
||||
//
|
||||
// Patch adds `utils.isSafeIdentifier` (denylist allowing any legitimate
|
||||
// i18next language code shape, rejecting `..`, path separators, control
|
||||
// chars, prototype keys, empty strings, and values longer than 128) and
|
||||
// inserts `languages = languages.filter(utils.isSafeIdentifier)` and the
|
||||
// equivalent for `namespaces` before they reach the backend connector.
|
||||
//
|
||||
// Trims: same scaffolding trims as the vulnerable counterpart.
|
||||
//
|
||||
// Patched-form simplification: same template-literal inline of the
|
||||
// backend's interpolator + readFileSync as the vulnerable side. The
|
||||
// `utils.isSafeIdentifier` body is copied verbatim from
|
||||
// `lib/utils.js:13-22` of the patched commit; the prototype-pollution
|
||||
// denylist (UNSAFE_KEYS check) and length / control-char / `..` /
|
||||
// separator rejections are all load-bearing for the precision-side
|
||||
// claim.
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
const UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
|
||||
|
||||
function isSafeIdentifier (v) {
|
||||
if (typeof v !== 'string') return false;
|
||||
if (v.length === 0 || v.length > 128) return false;
|
||||
if (UNSAFE_KEYS.indexOf(v) > -1) return false;
|
||||
if (v.indexOf('..') > -1) return false;
|
||||
if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
if (/[\x00-\x1F\x7F]/.test(v)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
app.get('/locales/resources.json', (req, res) => {
|
||||
let languages = req.query.lng
|
||||
? req.query.lng.split(' ')
|
||||
: [];
|
||||
let namespaces = req.query.ns
|
||||
? req.query.ns.split(' ')
|
||||
: [];
|
||||
|
||||
// Drop user-supplied values containing patterns that could trigger
|
||||
// path traversal / SSRF / prototype pollution when forwarded to the
|
||||
// backend connector. See: https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted
|
||||
languages = languages.filter(isSafeIdentifier);
|
||||
namespaces = namespaces.filter(isSafeIdentifier);
|
||||
|
||||
const lng = languages[0];
|
||||
const ns = namespaces[0];
|
||||
const filename = `/locales/${lng}/${ns}.json`;
|
||||
fs.readFileSync(filename);
|
||||
});
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// Nyx CVE benchmark fixture.
|
||||
//
|
||||
// CVE: CVE-2026-42353
|
||||
// GHSA: GHSA-jfgf-83c5-2c4m
|
||||
// Project: i18next-http-middleware (i18next/i18next-http-middleware)
|
||||
// License: MIT (https://github.com/i18next/i18next-http-middleware/blob/master/licence)
|
||||
// Advisory: https://github.com/i18next/i18next-http-middleware/security/advisories/GHSA-jfgf-83c5-2c4m
|
||||
// Vulnerable: a1d92a8f03292644d1c6fa83f1b77121d39daf4d lib/index.js:229-234,246-261
|
||||
//
|
||||
// Pre-3.9.3 `getResourcesHandler` pulled `lng` and `ns` directly from
|
||||
// `options.getQuery(req)` (default: `req => req.query`) and forwarded the
|
||||
// split values into `i18next.services.backendConnector.load(...)` with no
|
||||
// sanitisation. Paired with `i18next-fs-backend`, the backend's
|
||||
// `Backend.read` calls `interpolator.interpolate(loadPath, { lng, ns })`
|
||||
// which substitutes the unsanitised values into a path template and then
|
||||
// `readFileSync(filename)`, so a request like
|
||||
// `GET /locales/resources.json?lng=../../etc/passwd&ns=root` reads
|
||||
// attacker-chosen files off disk. The advisory also flags the SSRF
|
||||
// variant when paired with `i18next-http-backend`; we model the
|
||||
// fs-backend path here because it is the more direct sink-flow shape.
|
||||
//
|
||||
// Trims: getResourcesHandler's caching headers (lib/index.js:213-227),
|
||||
// route-params fallback (L237-244), Response/JSON envelope branch
|
||||
// (L264-268), the full Backend class wrapper (read/save/create/queue/
|
||||
// debounce — only the inline interpolation + readFileSync are
|
||||
// load-bearing), `extendOptionsWithDefaults`, the Backend constructor
|
||||
// path, `loadPath` typeof-function escape hatch, getResourceBundle
|
||||
// roundtrip, and the express-router/middleware mount glue.
|
||||
//
|
||||
// Patched-form simplification: the upstream interpolator is
|
||||
// `i18next.services.interpolator.interpolate(loadPath, { lng, ns })`;
|
||||
// here it is inlined as a template literal because the interpolator
|
||||
// just substitutes `{{lng}}` and `{{ns}}` placeholders into `loadPath`
|
||||
// (the default loadPath is `/locales/{{lng}}/{{ns}}.json`). The
|
||||
// substitution is character-for-character equivalent for the load-
|
||||
// bearing flow path (lng/ns into the string).
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
app.get('/locales/resources.json', (req, res) => {
|
||||
let languages = req.query.lng
|
||||
? req.query.lng.split(' ')
|
||||
: [];
|
||||
let namespaces = req.query.ns
|
||||
? req.query.ns.split(' ')
|
||||
: [];
|
||||
|
||||
// Inline the backend's read() and forEach loop's body verbatim,
|
||||
// collapsing the call into the array-index access used by the
|
||||
// recall test (see disabled_reason in ground_truth.json).
|
||||
const lng = languages[0];
|
||||
const ns = namespaces[0];
|
||||
const filename = `/locales/${lng}/${ns}.json`;
|
||||
fs.readFileSync(filename);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue