safe-expr-eval: Mitigation Library for the expr-eval Unsafe eval() RCE (CVE-2025-12735)
by Alejandro Castrillon (alecasg555) · 2026-07-06
- Severity
- Critical
- CVE
- CVE-2025-12735
- Category
- misc
- Affected product
- expr-eval npm package (mathematical/logical expression evaluator)
- Affected versions
- All versions of expr-eval, per this repository's SECURITY.md
- Disclosed
- 2026-07-06
- Patch status
- unpatched
Tags
References
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-06 |
| Last Updated | 2026-07-06 |
| Author / Researcher | Alejandro Castrillon (alecasg555) |
| CVE / Advisory | CVE-2025-12735 |
| Category | misc |
| Severity | Critical |
| CVSS Score | 9.8 (per NVD) |
| Status | Patched |
| Tags | expr-eval, eval-injection, arbitrary-code-execution, rce, cwe-95, javascript, typescript, npm, expression-parser, mitigation-library, drop-in-replacement |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | expr-eval npm package (mathematical/logical expression evaluator) |
| Versions Affected | All versions of expr-eval, per this repository’s SECURITY.md |
| Language / Platform | JavaScript / TypeScript, Node.js and browser bundles that embed expr-eval |
| Authentication Required | No (library-level flaw — exploitability depends on the host app exposing an expression string to untrusted input) |
| Network Access Required | Not required for the flaw itself; typically Yes in practice, since expr-eval is normally reached through a web-facing feature (formula fields, pricing rules, dynamic filters, etc.) |
Summary
CVE-2025-12735 is a critical arbitrary code execution vulnerability in the expr-eval npm package: instead of tokenizing and walking expressions through a restricted interpreter, expr-eval’s evaluation path ultimately reaches JavaScript’s eval()/Function() machinery, so a string such as require("child_process").execSync("...") submitted as an “expression” is executed as real JavaScript rather than rejected as an invalid formula. Any application that lets users (directly or indirectly) supply expression strings to expr-eval’s Parser.evaluate() is therefore exposed to full RCE, filesystem access, and process control. This repository, safe-expr-eval, is not an attack PoC — it is a from-scratch, zero-dependency TypeScript reimplementation of the same public API (Parser, evaluate, compile) that never touches eval() or the Function constructor, instead tokenizing input (tokenizer.ts) and walking a recursive-descent parser/evaluator (parser.ts, evaluator.ts) over a closed set of arithmetic/comparison/logical operators and an explicit function/constant whitelist. It is published as a drop-in, API-compatible replacement so that vulnerable applications can migrate away from expr-eval without rewriting call sites.
Vulnerability Details
Root Cause
The vulnerable pattern, as documented in this repo’s SECURITY.md, is that expr-eval evaluates parsed expressions by ultimately invoking JavaScript’s dynamic-code-execution primitives on attacker-influenced input:
| |
Because the “expression” string is not restricted to a safe grammar before being executed, any JavaScript expression — including require(...) calls that reach Node’s built-in modules — runs with the privileges of the host process (CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code).
safe-expr-eval avoids this entirely: evaluator.ts’s ExpressionEvaluator only recognizes a fixed token grammar (NUMBER, STRING, BOOLEAN, IDENTIFIER, OPERATOR, parens, commas) produced by tokenizer.ts, and function calls are resolved only against an explicit functions registry the host application populates (parser.functions.max = Math.max, etc.) — there is no code path that can reach require, process, or the Function constructor.
Attack Vector
Original expr-eval exploitation (what this library mitigates):
- A vulnerable application exposes a feature (e.g., a pricing/formula engine, custom filter, or rule builder) that passes user-controlled input as an “expression” string into
expr-eval’sParser.evaluate(). - The attacker submits a payload crafted to look like an expression but that actually resolves to executable JavaScript, e.g.
require("child_process").execSync("id"). expr-eval’s evaluation path invokeseval()/Function()semantics on this input, executing the attacker’s JavaScript in the Node.js process.- The attacker achieves arbitrary command execution, file read/write, or full process compromise — no authentication is inherently required beyond whatever gate the host app puts in front of the expression feature.
Mitigation path implemented by safe-expr-eval:
- The host application replaces
require('expr-eval')withrequire('safe-expr-eval')(API-compatible:Parser,evaluate,compile). - Expressions are tokenized (
Tokenizer) and parsed into an AST-walking evaluation (ExpressionEvaluator.parseOr → parseAnd → parseNot → parseComparison → parseAdditive → parseMultiplicative → parsePrimary) that only supports arithmetic, comparison, and logical operators plus whitelisted variables/functions/constants explicitly registered by the host app. - Because no
eval()/Function()call ever occurs, a payload likerequire("child_process").execSync(...)is parsed as an undefined-identifier/function-call and throwsUndefined function: require/Undefined variable: requireinstead of executing.
Impact
Unmitigated expr-eval usage allows an attacker to achieve arbitrary code execution in the context of the Node.js process handling expression evaluation — including reading/writing arbitrary files, spawning OS commands, and fully compromising the host application and any data it can reach. safe-expr-eval eliminates this impact class for applications that migrate to it.
Environment / Lab Setup
Vulnerable component: expr-eval (any version), Node.js host application exposing Parser.evaluate() to user input
Mitigation library: safe-expr-eval v1.0.0+ (Node.js >= 14.0.0, TypeScript, zero runtime dependencies)
Install: npm install safe-expr-eval
Build/test: npm run build (tsc) / npm test (jest) / npm run test:coverage
Proof of Concept
PoC Script
This repository contains no offensive exploit script — see
src/evaluator.ts,src/parser.ts,src/tokenizer.ts, andsrc/index.tsfor the mitigation implementation, andSECURITY.mdfor the documented vulnerable pattern reproduced above.
| |
| |
Detection & Indicators of Compromise
- Application logs showing expression strings containing require(, process., child_process, exec(, Function(
- Unexpected process spawns (child_process) originating from a Node.js process that only exposes a "formula"/"expression" feature
- Crashes or restarts coinciding with expression-field input such as process.exit()
Signs of compromise:
- Expression/formula input fields receiving strings that reference
require,process,child_process, orFunction. - Unexplained child processes, file reads (e.g.,
/etc/passwd), or outbound connections spawned by the Node.js process handling expression evaluation. package.jsonstill listingexpr-evalas a dependency in an internet-facing service without migration to a safe evaluator.
Remediation
| Action | Detail |
|---|---|
| Primary fix | Remove expr-eval and adopt a safe-eval alternative such as this repo’s safe-expr-eval v1.0.0+ (or an equivalent token/AST-based evaluator that never calls eval()/Function()) — the package’s own SECURITY.md frames it as “the official secure replacement for the vulnerable expr-eval library.” |
| Interim mitigation | If migration isn’t immediately possible: never pass fully untrusted strings to expr-eval, strip/deny any expression containing require, process, Function, or backticks, run the evaluation in a sandboxed/isolated worker with no access to Node built-ins, and rate-limit/validate expression length. |
References
Notes
Mirrored from https://github.com/alecasg555/safe-expr-eval on 2026-07-06. This is genuine hand-written TypeScript defensive/mitigation code, not an attack PoC — it is a real, functional, zero-dependency safe-eval replacement library published in direct response to CVE-2025-12735 (an eval()-based RCE in the expr-eval package), not an exploit script against a live target. Status is marked “Patched” to reflect that this repo represents the fix/mitigation path rather than a weaponized reproduction of the vulnerability; the vulnerable expr-eval code path itself is only referenced (not included) in this repo’s SECURITY.md.
| |