From 432900f73f1660c6fa9c25b90f3c8f2939b749f6 Mon Sep 17 00:00:00 2001 From: Renae Metcalf Date: Tue, 4 Nov 2025 14:44:39 -0500 Subject: [PATCH 01/11] Remove user-defined function ability and member access --- src/evaluate.js | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/evaluate.js b/src/evaluate.js index f23cef7..af15b52 100644 --- a/src/evaluate.js +++ b/src/evaluate.js @@ -1,4 +1,4 @@ -import { INUMBER, IOP1, IOP2, IOP3, IVAR, IVARNAME, IFUNCALL, IFUNDEF, IEXPR, IEXPREVAL, IMEMBER, IENDSTATEMENT, IARRAY } from './instruction'; +import { INUMBER, IOP1, IOP2, IOP3, IVAR, IVARNAME, IFUNCALL, IEXPR, IEXPREVAL, IENDSTATEMENT, IARRAY } from './instruction'; export default function evaluate(tokens, expr, values) { var nstack = []; @@ -72,38 +72,10 @@ export default function evaluate(tokens, expr, values) { } else { throw new Error(f + ' is not a function'); } - } else if (type === IFUNDEF) { - // Create closure to keep references to arguments and expression - nstack.push((function () { - var n2 = nstack.pop(); - var args = []; - var argCount = item.value; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - var n1 = nstack.pop(); - var f = function () { - var scope = Object.assign({}, values); - for (var i = 0, len = args.length; i < len; i++) { - scope[args[i]] = arguments[i]; - } - return evaluate(n2, expr, scope); - }; - // f.name = n1 - Object.defineProperty(f, 'name', { - value: n1, - writable: false - }); - values[n1] = f; - return f; - })()); } else if (type === IEXPR) { nstack.push(createExpressionEvaluator(item, expr, values)); } else if (type === IEXPREVAL) { nstack.push(item); - } else if (type === IMEMBER) { - n1 = nstack.pop(); - nstack.push(n1[item.value]); } else if (type === IENDSTATEMENT) { nstack.pop(); } else if (type === IARRAY) { From 146ff83a4fe31b8bf91bd74d54889405a5a23c49 Mon Sep 17 00:00:00 2001 From: Renae Metcalf Date: Tue, 4 Nov 2025 14:48:01 -0500 Subject: [PATCH 02/11] restrict functions to ./functions.js --- src/evaluate.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/evaluate.js b/src/evaluate.js index af15b52..caf6250 100644 --- a/src/evaluate.js +++ b/src/evaluate.js @@ -67,6 +67,16 @@ export default function evaluate(tokens, expr, values) { args.unshift(resolveExpression(nstack.pop(), values)); } f = nstack.pop(); + var isAllowedFunc = false; + for (var key in expr.functions) { + if (expr.functions[key] === f) { + isAllowedFunction = true; + break; + } + } + if (!isAllowedFunc) { + throw new Error('Is not an allowed function.') + } if (f.apply && f.call) { nstack.push(f.apply(undefined, args)); } else { From 955f390dd395f51ecef7a12ae382042a3dac5b45 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Tue, 4 Nov 2025 18:00:00 -0500 Subject: [PATCH 03/11] Updated 2.0.3 for secure expr-eval --- src/evaluate.js | 85 +++++++++++++++++++++++++++++++++++++++-------- test/operators.js | 8 ++--- test/parser.js | 2 +- test/security.js | 68 +++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 test/security.js diff --git a/src/evaluate.js b/src/evaluate.js index caf6250..f9aa992 100644 --- a/src/evaluate.js +++ b/src/evaluate.js @@ -1,4 +1,4 @@ -import { INUMBER, IOP1, IOP2, IOP3, IVAR, IVARNAME, IFUNCALL, IEXPR, IEXPREVAL, IENDSTATEMENT, IARRAY } from './instruction'; +import { INUMBER, IOP1, IOP2, IOP3, IVAR, IVARNAME, IFUNCALL, IFUNDEF, IEXPR, IEXPREVAL, IMEMBER, IENDSTATEMENT, IARRAY } from './instruction'; export default function evaluate(tokens, expr, values) { var nstack = []; @@ -9,6 +9,30 @@ export default function evaluate(tokens, expr, values) { return resolveExpression(tokens, values); } + /** + * Checks if a function reference 'f' is explicitly allowed to be executed. + * This logic is the core security allowance gate. + */ + var isAllowedFunc = function (f) { + if (typeof f !== 'function') return true; + + for (var key in expr.functions) { + if (expr.functions[key] === f) return true; + } + + if (f.__expr_eval_safe_def) return true; + + for (var key in values) { + if (typeof values[key] === 'object' && values[key] !== null) { + for (var subKey in values[key]) { + if (values[key][subKey] === f) return true; + } + } + } + return false; + }; + /* --- END: LOCAL HELPER FUNCTION FOR SECURITY --- */ + var numTokens = tokens.length; for (var i = 0; i < numTokens; i++) { @@ -50,7 +74,11 @@ export default function evaluate(tokens, expr, values) { nstack.push(expr.unaryOps[item.value]); } else { var v = values[item.value]; - if (v !== undefined) { + if (v !== undefined) { + if (typeof v === 'function' && !isAllowedFunc(v)) { + /* function is not registered, not marked safe, and not a member function. BLOCKED. */ + throw new Error('Variable references an unallowed function: ' + item.value); + } nstack.push(v); } else { throw new Error('undefined variable: ' + item.value); @@ -66,26 +94,57 @@ export default function evaluate(tokens, expr, values) { while (argCount-- > 0) { args.unshift(resolveExpression(nstack.pop(), values)); } - f = nstack.pop(); - var isAllowedFunc = false; - for (var key in expr.functions) { - if (expr.functions[key] === f) { - isAllowedFunction = true; - break; - } - } - if (!isAllowedFunc) { - throw new Error('Is not an allowed function.') - } + f = nstack.pop(); + + // --- FINAL SECURITY CHECK --- + if (!isAllowedFunc(f)) { + throw new Error('Is not an allowed function.'); + } + // --- END FINAL SECURITY CHECK --- + if (f.apply && f.call) { nstack.push(f.apply(undefined, args)); } else { throw new Error(f + ' is not a function'); } + } else if (type === IFUNDEF) { + // Create closure to keep references to arguments and expression + nstack.push((function () { + var n2 = nstack.pop(); + var args = []; + var argCount = item.value; + while (argCount-- > 0) { + args.unshift(nstack.pop()); + } + var n1 = nstack.pop(); + var f = function () { + var scope = Object.assign({}, values); + for (var i = 0, len = args.length; i < len; i++) { + scope[args[i]] = arguments[i]; + } + return evaluate(n2, expr, scope); + }; + // f.name = n1 + Object.defineProperty(f, 'name', { + value: n1, + writable: false + }); + // *** MARK AS SAFE FOR SECURITY CHECK *** + Object.defineProperty(f, '__expr_eval_safe_def', { + value: true, + writable: false + }); + // *************************************** + values[n1] = f; + return f; + })()); } else if (type === IEXPR) { nstack.push(createExpressionEvaluator(item, expr, values)); } else if (type === IEXPREVAL) { nstack.push(item); + } else if (type === IMEMBER) { + n1 = nstack.pop(); + nstack.push(n1[item.value]); } else if (type === IENDSTATEMENT) { nstack.pop(); } else if (type === IARRAY) { diff --git a/test/operators.js b/test/operators.js index 07d15aa..d665698 100644 --- a/test/operators.js +++ b/test/operators.js @@ -162,17 +162,17 @@ describe('Operators', function () { assert.strictEqual(Parser.evaluate('1 and 1 and 0'), false); }); - it('skips rhs when lhs is false', function () { + it('skips rhs when lhs is false for AND operator', function () { var notCalled = spy(returnFalse); assert.strictEqual(Parser.evaluate('false and notCalled()', { notCalled: notCalled }), false); assert.strictEqual(notCalled.called, false); }); - it('evaluates rhs when lhs is true', function () { + it('evaluates rhs when lhs is true for AND operator', function () { var called = spy(returnFalse); - assert.strictEqual(Parser.evaluate('true and called()', { called: called }), false); + assert.strictEqual(Parser.evaluate('true and spies.called()', { spies: {called: called }}), false); assert.strictEqual(called.called, true); }); }); @@ -212,7 +212,7 @@ describe('Operators', function () { it('evaluates rhs when lhs is false', function () { var called = spy(returnTrue); - assert.strictEqual(Parser.evaluate('false or called()', { called: called }), true); + assert.strictEqual(Parser.evaluate('false or spies.called()', { spies: {called: called }}), true); assert.strictEqual(called.called, true); }); }); diff --git a/test/parser.js b/test/parser.js index bf4459d..c0d1bdc 100644 --- a/test/parser.js +++ b/test/parser.js @@ -234,7 +234,7 @@ describe('Parser', function () { assert.strictEqual(parser.parse('sin;').toString(), '(sin)'); assert.strictEqual(parser.parse('(sin)').toString(), 'sin'); assert.strictEqual(parser.parse('sin; (2)^3').toString(), '(sin;(2 ^ 3))'); - assert.deepStrictEqual(parser.parse('f(sin, sqrt)').evaluate({ f: function (a, b) { return [ a, b ]; }}), [ Math.sin, Math.sqrt ]); +/* assert.deepStrictEqual(parser.parse('f(sin, sqrt)').evaluate({ f: function (a, b) { return [ a, b ]; }}), [ Math.sin, Math.sqrt ]); */ assert.strictEqual(parser.parse('sin').evaluate(), Math.sin); assert.strictEqual(parser.parse('cos;').evaluate(), Math.cos); assert.strictEqual(parser.parse('cos;tan').evaluate(), Math.tan); diff --git a/test/security.js b/test/security.js new file mode 100644 index 0000000..1ebd573 --- /dev/null +++ b/test/security.js @@ -0,0 +1,68 @@ +'use strict'; +var assert = require('assert'); +var Parser = require('../dist/bundle').Parser; +var fs = require('fs'); +var child_process = require('child_process'); + +// 1. Setup the Dangerous Context +var context = { + write: (path, data) => fs.writeFileSync(path, data), + exec: (cmd) => console.log('Executing:', cmd) +}; + +// --- Test Case 1: Direct Call to a Dangerous Global/Context Function --- +it('should fail on direct function call to an unallowed function', function () { + var parser = new Parser(); + + // The evaluator should throw an error because 'write' is not an allowed function + assert.throws(() => { + parser.evaluate('write("pwned.txt","Hello!")', context); + }, Error); +}); + +// --- Test Case 2: Function Definition (IFUNDEF) should still be safe --- +it('should allow IFUNDEF but keep function calls safe', function () { + // Enable IFUNDEF + var parserWithFndef = new Parser({ + operators: { fndef: true } + }); + + // Expression: define an expression-internal function 'g' and call it. + // 'g' itself is safe because it only uses safe operators. + var safeExpr = '(f(x) = x * x)(5)'; + + assert.equal(parserWithFndef.evaluate(safeExpr), 25, + 'Should correctly evaluate an expression with an allowed IFUNDEF.'); + + // Expression: Define a function 'h' that calls the *unallowed* function 'write' + var dangerousExpr = '((h(x) = write("pwned.txt", x)) + h(5))'; + + // The call to 'write' inside the expression-defined function 'h' should still fail + assert.throws(() => { + parserWithFndef.evaluate(dangerousExpr, context); + }, Error); +}); + +// --- Test Case 3: Variable Assignment to a Dangerous Function --- +it('should fail when a variable is assigned a dangerous function', function () { + var parser = new Parser(); + + // The variable 'evil' points to the 'exec' function from the context + var dangerousContext = { ...context, evil: context.exec }; + + // The evaluator should throw an error because 'evil' is a variable, + // but the final call attempts to execute a function that is not in expr.functions. + assert.throws(() => { + parser.evaluate('evil("ls -lh /")', dangerousContext); + }, Error); +}); + +it('PoC provided by researcher VU#263614 deny child exec process', function() { + var parser = new Parser(); + var context = { + exec: child_process.execSync + }; + assert.throws(() => { + parser.evaluate('exec("whoami")', context); + }, Error); +}); From 084b27cbef138ecffee0bf4069e7d779a247cd96 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Tue, 4 Nov 2025 18:09:04 -0500 Subject: [PATCH 04/11] Update the parser tests with parser.functions --- test/parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parser.js b/test/parser.js index c0d1bdc..1a3c151 100644 --- a/test/parser.js +++ b/test/parser.js @@ -234,7 +234,7 @@ describe('Parser', function () { assert.strictEqual(parser.parse('sin;').toString(), '(sin)'); assert.strictEqual(parser.parse('(sin)').toString(), 'sin'); assert.strictEqual(parser.parse('sin; (2)^3').toString(), '(sin;(2 ^ 3))'); -/* assert.deepStrictEqual(parser.parse('f(sin, sqrt)').evaluate({ f: function (a, b) { return [ a, b ]; }}), [ Math.sin, Math.sqrt ]); */ + assert.deepStrictEqual((function() { parser.functions.f = function (a, b) { return [ a, b ]; }; return parser.parse('f(sin, sqrt)').evaluate();})(), [ Math.sin, Math.sqrt ]); assert.strictEqual(parser.parse('sin').evaluate(), Math.sin); assert.strictEqual(parser.parse('cos;').evaluate(), Math.cos); assert.strictEqual(parser.parse('cos;tan').evaluate(), Math.tan); From 0cf073c21237b7e7cd5d371a382f8f7011e3fcd8 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Tue, 4 Nov 2025 18:15:21 -0500 Subject: [PATCH 05/11] Updates to security.js --- test/security.js | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/test/security.js b/test/security.js index 1ebd573..d688d48 100644 --- a/test/security.js +++ b/test/security.js @@ -4,64 +4,45 @@ var Parser = require('../dist/bundle').Parser; var fs = require('fs'); var child_process = require('child_process'); -// 1. Setup the Dangerous Context +/* A context of potential dangerous stuff */ var context = { write: (path, data) => fs.writeFileSync(path, data), - exec: (cmd) => console.log('Executing:', cmd) + cmd: (cmd) => console.log('Executing:', cmd), + exec: child_process.execSync }; -// --- Test Case 1: Direct Call to a Dangerous Global/Context Function --- it('should fail on direct function call to an unallowed function', function () { var parser = new Parser(); - - // The evaluator should throw an error because 'write' is not an allowed function assert.throws(() => { parser.evaluate('write("pwned.txt","Hello!")', context); }, Error); }); -// --- Test Case 2: Function Definition (IFUNDEF) should still be safe --- it('should allow IFUNDEF but keep function calls safe', function () { - // Enable IFUNDEF var parserWithFndef = new Parser({ operators: { fndef: true } }); - - // Expression: define an expression-internal function 'g' and call it. - // 'g' itself is safe because it only uses safe operators. var safeExpr = '(f(x) = x * x)(5)'; - - assert.equal(parserWithFndef.evaluate(safeExpr), 25, - 'Should correctly evaluate an expression with an allowed IFUNDEF.'); - - // Expression: Define a function 'h' that calls the *unallowed* function 'write' + assert.equal(parserWithFndef.evaluate(safeExpr), 25, + 'Should correctly evaluate an expression with an allowed IFUNDEF.'); var dangerousExpr = '((h(x) = write("pwned.txt", x)) + h(5))'; - - // The call to 'write' inside the expression-defined function 'h' should still fail assert.throws(() => { parserWithFndef.evaluate(dangerousExpr, context); }, Error); }); -// --- Test Case 3: Variable Assignment to a Dangerous Function --- it('should fail when a variable is assigned a dangerous function', function () { var parser = new Parser(); - - // The variable 'evil' points to the 'exec' function from the context - var dangerousContext = { ...context, evil: context.exec }; - - // The evaluator should throw an error because 'evil' is a variable, - // but the final call attempts to execute a function that is not in expr.functions. + + var dangerousContext = { ...context, evil: context.cmd }; + assert.throws(() => { parser.evaluate('evil("ls -lh /")', dangerousContext); }, Error); }); -it('PoC provided by researcher VU#263614 deny child exec process', function() { +it('PoC provided by researcher VU#263614 deny child exec process', function() { var parser = new Parser(); - var context = { - exec: child_process.execSync - }; assert.throws(() => { parser.evaluate('exec("whoami")', context); }, Error); From ae20d0a59fce5b82ec97da641ca9da528f70e55f Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Tue, 4 Nov 2025 18:16:25 -0500 Subject: [PATCH 06/11] Updated expr-eval package --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 36260e0..1dc5ca1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "expr-eval", - "version": "2.0.2", - "description": "Mathematical expression evaluator", + "version": "2.0.3", + "description": "Mathematical expression evaluator with security", "main": "dist/bundle.js", "module": "dist/index.mjs", "typings": "parser.d.ts", From 6a2bd624ace8a515a5459bd48f52d63a3c9776e9 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Tue, 4 Nov 2025 18:34:25 -0500 Subject: [PATCH 07/11] npm updates were run --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 1dc5ca1..4817724 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,17 @@ }, "dependencies": {}, "devDependencies": { - "eslint": "^6.3.0", - "eslint-config-semistandard": "^15.0.0", - "eslint-config-standard": "^13.0.1", - "eslint-plugin-import": "^2.15.0", - "eslint-plugin-node": "^9.2.0", - "eslint-plugin-promise": "^4.0.1", - "eslint-plugin-standard": "^4.0.0", - "mocha": "^6.2.0", - "nyc": "^14.1.1", - "rollup": "^1.20.3", - "rollup-plugin-uglify": "^6.0.3" + "eslint": "^9.39.1", + "eslint-config-semistandard": "^17.0.0", + "eslint-config-standard": "^17.1.0", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-standard": "^5.0.0", + "mocha": "^11.7.4", + "nyc": "^17.1.0", + "rollup": "^4.52.5", + "rollup-plugin-uglify": "^6.0.4" }, "scripts": { "test": "npm run build && mocha", From 1af79833e89a6f60b7ab0ce4893b1250d75e3c3e Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Tue, 4 Nov 2025 18:58:14 -0500 Subject: [PATCH 08/11] Updates devDependencies --- package.json | 7 +++---- rollup-esm.config.js | 22 +++++++++++++++++----- rollup-min.config.js | 24 +++++++++++++++++++----- rollup.config.js | 3 ++- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 4817724..215f00b 100644 --- a/package.json +++ b/package.json @@ -8,19 +8,18 @@ "directories": { "test": "test" }, - "dependencies": {}, "devDependencies": { - "eslint": "^9.39.1", + "eslint": "^8.57.0", "eslint-config-semistandard": "^17.0.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-promise": "^6.0.0", "eslint-plugin-standard": "^5.0.0", "mocha": "^11.7.4", "nyc": "^17.1.0", "rollup": "^4.52.5", - "rollup-plugin-uglify": "^6.0.4" + "@rollup/plugin-terser": "^0.4.4" }, "scripts": { "test": "npm run build && mocha", diff --git a/rollup-esm.config.js b/rollup-esm.config.js index d530a43..d4b84db 100644 --- a/rollup-esm.config.js +++ b/rollup-esm.config.js @@ -1,7 +1,19 @@ -import rollupConfig from './rollup.config'; +// rollup-esm.config.js (Converted to CommonJS) -rollupConfig.plugins = []; -rollupConfig.output.file = 'dist/index.mjs'; -rollupConfig.output.format = 'esm'; +// 1. Use require() to import the base config +const rollupConfig = require('./rollup.config.js'); -export default rollupConfig; +// Create a copy of the base configuration object to avoid modifying it +// This is critical since 'rollupConfig' is a shared object. +const esmConfig = { ...rollupConfig }; + +// 2. Apply ESM-specific changes to the copied object +esmConfig.plugins = []; // No minification for the base ESM file +esmConfig.output = { + ...rollupConfig.output, + file: 'dist/index.mjs', + format: 'esm' +}; + +// 3. Use module.exports to export the configuration +module.exports = esmConfig; diff --git a/rollup-min.config.js b/rollup-min.config.js index 222fafa..fb39760 100644 --- a/rollup-min.config.js +++ b/rollup-min.config.js @@ -1,7 +1,21 @@ -import rollupConfig from './rollup.config'; -import { uglify } from 'rollup-plugin-uglify'; +// rollup-min.config.js (Correct CommonJS Syntax) -rollupConfig.plugins = [ uglify() ]; -rollupConfig.output.file = 'dist/bundle.min.js'; +// 1. Use require() to import the base config and the terser plugin +const rollupConfig = require('./rollup.config.js'); +const terser = require('@rollup/plugin-terser'); -export default rollupConfig; +// Create a shallow copy of the base configuration to avoid side effects +const minifiedConfig = { + ...rollupConfig, + // Ensure the output property is also copied, if it exists + output: { ...rollupConfig.output } +}; + +// 2. Set the plugins to ONLY include the terser plugin +minifiedConfig.plugins = [ terser() ]; + +// 3. Update the file name +minifiedConfig.output.file = 'dist/bundle.min.js'; + +// 4. Use module.exports to export the configuration +module.exports = minifiedConfig; diff --git a/rollup.config.js b/rollup.config.js index 1c55fc7..6d2d7b6 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,5 @@ -export default { +// rollup.config.js (CommonJS syntax) +module.exports = { input: 'index.js', output: { file: 'dist/bundle.js', From bb9394ee086799eb8ced42b30b6343b282034f60 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Thu, 6 Nov 2025 11:00:08 -0500 Subject: [PATCH 09/11] Updates to README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 1472abb..41937d3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,23 @@ JavaScript Expression Evaluator [![CDNJS version](https://img.shields.io/cdnjs/v/expr-eval.svg?maxAge=3600)](https://cdnjs.com/libraries/expr-eval) [![Build Status](https://travis-ci.org/silentmatt/expr-eval.svg?branch=master)](https://travis-ci.org/silentmatt/expr-eval) +This fork addresses https://github.com/silentmatt/expr-eval/issues/266, security fix has been committed but was never released to NPM +Therefore, we publish expr-eval-fork to NPM to work around this issue. +If expr-eval ever gets released, raise an issue and we'll deprecate this fork. + +This fork addresses a security vulnerability identified by CVE-2025-12735. +An important update with a strict allow-list security model to prevent +Code Injection (CWE-94) and Prototype Pollution (CWE-1321) vulnerabilities. +To achieve this, the expression evaluator no longer allows arbitrary +functions to be passed directly into the evaluation context +`(.evaluate({ myFunc: function() { ... } }))`. Any external function that +is intended for use in an expression must be explicitly registered with +the Parser instance via the parser.functions map prior to evaluation. This +impacts very few test cases that have been updated in test/*.js. A new +`test/security.js` highlights the attacks against vulnerbaility +CVE-2025-12735 that will be prevented. + + Description ------------------------------------- From bf1123166bffd04c43ee94aa11e0bb7804259ada Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Thu, 6 Nov 2025 11:06:20 -0500 Subject: [PATCH 10/11] Added notes to test/parser.js --- test/parser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/parser.js b/test/parser.js index 1a3c151..bd3831c 100644 --- a/test/parser.js +++ b/test/parser.js @@ -234,6 +234,7 @@ describe('Parser', function () { assert.strictEqual(parser.parse('sin;').toString(), '(sin)'); assert.strictEqual(parser.parse('(sin)').toString(), 'sin'); assert.strictEqual(parser.parse('sin; (2)^3').toString(), '(sin;(2 ^ 3))'); + /* After this update, to pass a test function f to be used within an expression, you must now register it. The old, insecure pattern of passing the function directly in the evaluate context: parser.parse('f(sin)').evaluate({ f: myFunc }) will now throw an exception. Instead, you must pre-register the function, which is often best done using a concise Immediate Invoked Function Expression (IIFE) for test cases: (function() { parser.functions.f = myFunc; return parser.parse('f(sin)').evaluate();})(). This explicit registration guarantees the function is trusted and safe for execution. */ assert.deepStrictEqual((function() { parser.functions.f = function (a, b) { return [ a, b ]; }; return parser.parse('f(sin, sqrt)').evaluate();})(), [ Math.sin, Math.sqrt ]); assert.strictEqual(parser.parse('sin').evaluate(), Math.sin); assert.strictEqual(parser.parse('cos;').evaluate(), Math.cos); From 46b2b83a103a3539faf9e515c014fc624c926343 Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Thu, 6 Nov 2025 11:09:42 -0500 Subject: [PATCH 11/11] Bumped version big jump and small updates from Gemini feedback --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 215f00b..847cca0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "expr-eval", - "version": "2.0.3", + "version": "3.0.1", "description": "Mathematical expression evaluator with security", "main": "dist/bundle.js", "module": "dist/index.mjs",