expr-eval CVE-2025-12735 · CVE-2025-13204
Code execution and prototype pollution via unrestricted member access in expr-eval.
The problem
expr-eval <= 2.0.2 (all published versions) is affected by
CVE-2025-12735, CVE-2025-13204 (CWE-94 Code Injection / CWE-1321 Prototype Pollution). The published package has
no fix available — exactly what npm audit reports. `expr-eval` lets callers evaluate string expressions against a supplied scope, including member access such as `foo.bar`. Because member access was unrestricted, a crafted expression could reach JavaScript internals: `foo.__proto__` exposes (and can pollute) `Object.prototype`, and the `constructor` chain can reach the `Function` constructor to run arbitrary code. Any application that evaluates untrusted expressions is affected.
The fix — drop-in, no code changes
Add an overrides entry so every direct and transitive dependency on
expr-eval resolves to the patched fork, then reinstall:
{
"overrides": {
"expr-eval": "npm:@keep-lts/expr-eval@^2.0.3"
}
}
Equivalent for Yarn: use resolutions. The public API is unchanged — nothing else to do.
✓ Live on npm: @keep-lts/expr-eval · or install directly: npm i @keep-lts/expr-eval
What we changed
A `memberAccess(object, property)` guard wraps every runtime member-access site and throws when the property is `__proto__`, `constructor`, or `prototype`. Legitimate expressions — member access to ordinary properties, arithmetic, and built-in functions like `sqrt`/`max` — are unchanged. The same guard is present in `dist/bundle.js` (CommonJS), `dist/index.mjs` (ESM), and `dist/bundle.min.js` (regenerated from the patched source).
Proof of concept (the vulnerability)
const { Parser } = require('expr-eval');
const parser = new Parser();
parser.evaluate('foo.__proto__', { foo: {} }); // returns Object.prototype on the unpatched version
@keep-lts/expr-eval@2.0.3How we keep it trustworthy
- Minimal, surgical patch — security only, no feature changes.
- A regression test that fails on the original and passes on the patch; legitimate behaviour preserved.
- Published under the
@keep-ltsorg with the full advisory and tests inside the package; upstream license & attribution retained.
Full advisory and changelog ship inside the package (SECURITY.md, CHANGELOG.md).