ng-eval

August 22, 2026 ยท View on GitHub

A secure and performant expression evaluator for Angular applications.

๐Ÿš€ Recent Major Improvements (2025)

ng-eval has been significantly enhanced with systematic security and performance improvements:

โœ… Security Hardening

  • Complete prototype pollution prevention with comprehensive protection mechanisms
  • Enhanced call expression security with function sandboxing and validation
  • Robust type safety in binary expressions with edge case handling
  • Advanced async error handling with proper resource cleanup

โœ… Performance Optimizations

  • Property lookup caching with LRU eviction (O(n) โ†’ O(1) optimization)
  • Memory leak prevention with comprehensive resource management
  • Cross-platform compatibility (Node.js and browser environments)
  • Optimized allocation patterns for arrays and objects

โœ… Quality & Reliability

  • 511/511 tests passing with zero regressions
  • 102 core improvement tests validating all enhancements
  • Comprehensive error boundaries for production stability
  • Enhanced TypeScript support with improved type safety

Credits

Inspired by jse-eval, expression-eval and based on acorn, with thanks to their awesome work.

Many thanks to @Shelly and @donmccurdy for the initial packages.

JavaScript expression parsing and evaluation.

IMPORTANT: As mentioned under Security below, this library does not attempt to provide a secure sandbox for evaluation. Evaluation involving user inputs (expressions or values) may lead to unsafe behavior.

Why ng-eval?

I wanted an evaluator to be included in one of my other projects. I found some great libraries, but they didn't have the features I needed and I found some issues with Angular integration. So I decided to create my own.

Usage

Evaluates an estree expression from acorn.

Install

Install:

npm install --save @zvenigora/ng-eval-core

Import:

import { ParserService, EvalService, 
  CompilerService, DiscoveryService } from '@zvenigora/ng-eval-core';

API

Parsing

import { ParserService } from '@zvenigora/ng-eval-core';

private service: ParserService;
...
const ast = service.parse('1 + foo');

The result of the parse is an AST (abstract syntax tree), like:

{
	"type": "BinaryExpression",
	"start": 0,
	"end": 7,
	"left": {
		"type": "Literal",
		"start": 0,
		"end": 1,
		"value": 1,
		"raw": "1"
	},
	"operator": "+",
	"right": {
		"type": "Identifier",
		"start": 4,
		"end": 7,
		"name": "foo"
	}
}

Evaluation

Evaluation executes the AST using the given context simpleEval(ast, context). By default, the context is empty.

import { EvalService } from '@zvenigora/ng-eval-core';

private service: EvalService;
...
const expression = '2 + 3 * a';
const context = { a: 10 };
const result = service.simpleEval(expression, context); // 32

Since the default context is empty, it prevents using built-in JS functions. To allow those functions, they can be added to the context argument passed into the simpleEval method:

const context = {
  Date,
  Array,
  Object,
  encodeURI,
  decodeURI,
  isFinite,
  isNaN,
  JSON,
  Math,
  parseFloat,
  parseInt,
  RegExp,
  // ...myCustomPropertiesAndFunctions,
};

Compilation

import { CompilerService } from '@zvenigora/ng-eval-core';

private service: CompilerService;
...
const fn = service.compile('x + y');
const context = { x: 1, y: 2 };
const options = { caseInsensitive: false };
const value = service.simpleCall(fn, context, options); // 3

Async evaluation and compilation

Ng-eval has a limited support for asynchronous expression based on JavaScript promises.

import { EvalService } from '@zvenigora/ng-eval-core';

private service: EvalService;
...
const context = {
  one: 1,
  two: 2,
  asyncFunc: async (a: number, b: number) => { return await (a+b); }
};
const expr = 'asyncFunc(one, two)';
const value = await service.simpleEvalAsync(expr, context); // 3
import { CompilerService } from '@zvenigora/ng-eval-core';

private service: CompilerService;
...
const context = {
  one: 1,
  two: 2,
  promiseFunc: (a: number, b: number) => {
    return new Promise((resolve) => {
      setTimeout(() => {
        return resolve(a + b);
      }, 1000);
    });
  }
};
const expr = 'promiseFunc(one, two)';
const fn = service.compileAsync(expr);
const result = await service.simpleCallAsync(fn, context); // 3

Discovery

Discovery service finds all nodes for the given node type.

import { DiscoveryService } from '@zvenigora/ng-eval-core';

private service: DiscoveryService;
...
const expressions = service.extract('1 + 2 * a', 'BinaryExpression'); // 2 nodes

ESTree nodes supported:

The project has been tested with the following node types:

  • BinaryExpression
  • Identifier
  • Literal
  • CallExpression potentially unsafe
  • AwaitExpression
  • ConditionalExpression
  • MemberExpression
  • ArrayExpression
  • UnaryExpression
  • LogicalExpression
  • ThisExpression
  • NewExpression
  • TemplateLiteral
  • TaggedTemplateExpression
  • ObjectExpression
  • AssignmentExpression
  • UpdateExpression
  • ArrowFunctionExpression potentially unsafe (AssignmentPattern is not implemented)

Options

To change the default behavior of the evaluator, use options. Options may be provided as an argument to the function call of simpleEval.

Case-insensitive evaluation

While JavaScript is a case-sensitive language, some may find it hard to use. To provide case-insensitive evaluation, set caseInsensitive to true.

import { EvalService } from '@zvenigora/ng-eval-core';

private service: EvalService;
...
const options = {caseInsensitive: true};
...
const expression = '2 + 3 * A';
const context = { a: 10 };
const result = service.simpleEval(expression, context, options); // 32

Per-node timing

Set trackTime to true to accumulate per-node-type timings, read back from the state as nodeTimings. Documented in the package README โ€” that file is what ships in the npm tarball, so it is the copy that has to be right, and it is not duplicated here.

Evaluation hooks

Register callbacks that fire per AST node (state.hooks.on) and per resolved context read (state.hooks.onRead) โ€” the basis for dependency tracking. Documented in the package README, for the same reason as above.

Evaluation with state

Evaluation executes the AST using the given state eval(ast, state). The state object includes the context, result, and options. It is in use by visitors functions behind the scene. It could be used to extend the functionality of the evaluator. For example, it can provide the execution history and the time of execution.

import { EvalService } from '@zvenigora/ng-eval-core';

private service: EvalService;
...
const expression = '2 + 3 * a';
const context = { a: 10 };
const options = { caseInsensitive: false };
const state = service.createState(context, options);
... 
// update the state if required
...

const result = service.eval(expression, state); // 32
...
// read the state if required

console.table(state.result.trace); // display execution history

...

Evaluation with scope

Evaluation context may include prior scopes. Evaluation scopes may contain variables and functions.

import { EvalContext, EvalScope, EvalScopeOptions,
  EvalService } from '@zvenigora/ng-eval-core';

private service: EvalService;
...

const args = ['says', 'meow'];

const cat = {
  type: 'Cat',
  name: 'Miss Kitty',
  num: 3,
  action: function(args: string[], n: number, t: string) {
    return this.name + ' ' + args.join(' ') + ' ' + n + ' ' + t;
  }
}

// `args` is read as a bare identifier, so it belongs to the context itself
// rather than to the scope.
const evalContext = new EvalContext({ args }, {});

const catOptions: EvalScopeOptions = {
  global: false,
  caseInsensitive: false,
  namespace: 'cat',
  thisArg: cat
};
evalContext.priorScopes.push(EvalScope.fromObject(cat, catOptions));

const expression = 'cat.action(args, cat.num, "times")';
const result = service.simpleEval(expression, evalContext); // 'Miss Kitty says meow 3 times'

Packages in This Repo

This repository publishes two libraries. Everything above documents the first.

  • @zvenigora/ng-eval-core โ€” the expression parser and evaluator this README describes.
  • @zvenigora/ng-eval-signals โ€” Angular Signals for those expressions. createEvalSignal('price * quantity', { price, quantity }) returns a Signal that recomputes exactly when a key the expression read changes, using Angular's own dependency tracking. Built on ng-eval-core; see its README for the factory, the DI service, dependency introspection, lifetime and limitations.

A third, @zvenigora/ng-eval-forms, is planned โ€” see the roadmap.

Depending on your specific use-case, there are other related packages available, including:

Security

Enhanced Security (2025 Update): ng-eval now includes comprehensive security hardening:

๐Ÿ”’ Security Features Implemented

  • โœ… Complete prototype pollution prevention - All prototype modification attempts are blocked
  • โœ… Function call sandboxing - Dangerous function calls are validated and restricted
  • โœ… Enhanced type safety - Comprehensive type checking prevents injection attacks
  • โœ… Async operation security - Proper error boundaries and resource cleanup
  • โœ… Memory safety - Prevents memory-based attacks and resource exhaustion

๐Ÿ”Ž Reviewed Advisories

SECURITY.md records advisories raised against related projects and whether ng-eval shares the defect, with the reasoning and the regression tests that pin each verdict. Most recently reviewed: GHSA-pj3p-xpg7-h7gw (@zvenigora/jse-eval <= 1.10.0) โ€” does not apply to ng-eval, verified by running the advisory's proof-of-concept against this evaluator.

โš ๏ธ Important Security Notice

Although this package does avoid the use of eval() and now includes comprehensive security hardening against prototype pollution and injection attacks, it cannot guarantee complete isolation from all possible attack vectors.

For maximum security: Always validate and sanitize user inputs, limit execution time and memory usage, and consider running evaluations in isolated environments for high-security applications.

๐Ÿ›ก๏ธ Security Best Practices

  • Use the caseInsensitive: false option when security is paramount
  • Implement timeout controls for long-running evaluations
  • Validate all user-provided expressions and context data
  • Monitor memory usage in production environments

Documentation Structure

DocumentPurpose
README.mdMain project overview and quick start guide
IMPROVEMENTS.mdSystematic improvements implementation and status
PERFORMANCE.mdPerformance optimizations and benchmarking guide
SECURITY.mdSecurity enhancements and threat mitigation
modules/eval-core/README.mdComprehensive usage guide and API documentation
docs/Technical documentation and implementation details

Contributing

Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on the guidelines for contributing and then feel free to submit a PR with your contribution.

Code of Conduct

Help us keep this project open and inclusive. Please read and follow the Code of Conduct.

License

MIT License.


*Recent major improvements and documentation updates completed with assistance from GitHub Copilot on August 20, 2025.