expr-eval-fork

A security-patched fork of expr-eval for safely parsing and evaluating math expressions in JavaScript without native eval().

Library
npm
v3.0.3
7stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
19/100Needs Attention
Development Activity4
Maintenance0
Community24
Maturity48
Momentum0

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
64/100Good
Architecture75
Code Quality70
Innovation65
Learning Curve45

expr-eval-fork parses a string like “2 * x + 1” into an Expression object that can be evaluated with different variable bindings, compiled into a native JavaScript function, simplified, or substituted into other expressions. It supports the usual arithmetic, comparison, and logical operators plus array literals, indexing, and functions like map, filter, fold, and join, with a configurable operator set so a consuming app can disable assignment or logical operators it doesn’t want to expose to user input.

The fork exists specifically to close a gap upstream: the original silentmatt/expr-eval had a prototype-pollution and arbitrary-function-execution vulnerability (CVE-2025-12735 / GHSA-jc85-fpwf-qm7x) that was fixed in source but never published to npm. expr-eval-fork ships that fix plus a stricter evaluate() that blocks proto/prototype/constructor member access and only allows calling functions that are explicitly registered on the parser, defined inline in the expression, or part of a small Math allow-list — with a dedicated security test suite (including researcher-submitted PoCs) guarding those regressions.

What You Get

  • A compact expression parser exposed through Parser.parse / Parser.evaluate static helpers, or an instance API for repeated parsing with custom options
  • Prebuilt CJS (dist/bundle.js) and ESM (dist/index.mjs) builds selected automatically via a modern package.json exports map, plus hand-maintained TypeScript typings (parser.d.ts)
  • A hardened evaluate() path that blocks prototype-pollution member access and rejects calls to any function not explicitly registered, expression-defined, or on a small Math allow-list
  • Expression-level utilities — simplify() for constant folding, substitute() for composing expressions, toJSFunction() for compiling to a native JS function, and variables()/symbols() for introspection

Common Use Cases

  • Evaluating user-typed formulas in spreadsheet-like grid UIs
  • Running configurable pricing or discount formulas stored as strings without deploying new code
  • Letting non-technical users define derived-field formulas in a no-code form or report builder
  • Powering a web-based scientific or engineering calculator that accepts free-text math expressions

Under The Hood

Architecture Parser (src/parser.js) builds its operator, function, and constant tables in the constructor and exposes parse(expr), which drives TokenStream (src/token-stream.js) and ParserState (src/parser-state.js) to produce a flat, postfix instruction array. Expression (src/expression.js) wraps that instruction list and delegates each capability to a standalone module — evaluate.js (a stack-machine interpreter over the tokens), simplify.js (constant folding), substitute.js (expression composition), expression-to-string.js (used by toJSFunction), and get-symbols.js (variable/symbol extraction) — rather than putting all behavior directly on the Expression prototype, keeping each concern independently testable. Security enforcement is centralized entirely inside evaluate.js’s isAllowedFunc gate, so that one module is the single point where the sandboxing model lives or breaks.

Tech Stack Pure JavaScript with zero runtime dependencies, built with Rollup (rollup.config.js, rollup-min.config.js, rollup-esm.config.js) into CJS and ESM bundles selected through package.json’s exports map, with TypeScript consumers served by a hand-written parser.d.ts. Tests run under Mocha with nyc for coverage, and linting uses ESLint with eslint-config-semistandard. CI is still configured via a Travis file targeting Node 6 through 12 — stale relative to the package’s own engines requirement of Node >=16.9.0, suggesting the fork inherited its parent’s tooling without fully modernizing it.

Code Quality Test coverage is substantial: test/expression.js, test/parser.js, test/operators.js, and test/functions.js each run into the hundreds of lines, plus a dedicated test/security.js covering prototype-pollution and arbitrary-function-execution regressions with researcher-submitted proof-of-concept expressions tied to specific CVEs. Code is written in consistent ES5-style semistandard JavaScript (no classes, var-based constructor functions) rather than TypeScript source. Error handling is explicit — evaluate.js throws precise, specific messages (‘prototype access detected’, ‘Is not an allowed function’) at each unsafe branch rather than swallowing failures. There is no active CI gate running these tests automatically since the Travis configuration is defunct, leaving npm test as a manual pre-publish step.

API Design Parser.evaluate(expr, values) and the static Parser.parse/Parser.evaluate shortcuts take a caller from a string to a result in one call, while new Parser(options) supports the common progression of starting simple and later disabling specific operators or enabling member access. Naming mirrors JavaScript’s own Math object (sin, sqrt, atan2), and toJSFunction/simplify/substitute/variables/symbols each read as verbs matching what they return. Documentation is strong for a project this size, with a full operator-precedence table and per-function reference in the README, but the fork’s actual security-relevant breaking change — that evaluate() no longer accepts arbitrary context functions the way it once did — is explained only in the CHANGELOG and linked CVE, not in the README itself, so an upgrading consumer has to dig through commit history to understand it.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search