Skip to main content

Security Overview

The expression evaluator is designed from the ground up to safely evaluate untrusted user input. It uses a defense-in-depth approach with multiple layers of protection.

Sandboxed Execution Model

Expressions run in a fully sandboxed interpreter. Unlike approaches that compile to JavaScript and use eval(), this library:

  • Parses expressions into an AST (Abstract Syntax Tree)
  • Interprets the AST directly with controlled semantics
  • Never generates or executes arbitrary JavaScript code

This means expressions can only do what the interpreter explicitly allows.

What Expressions Cannot Do

  • No side effects -- expressions cannot modify the context, environment, or any external state
  • No loops -- there are no for, while, or do constructs (array functions like map/filter are the only iteration mechanism and are bounded)
  • No dynamic code execution -- no eval(), Function(), or import()
  • No prototype access -- __proto__, constructor, and prototype are blocked by default
  • No arbitrary function calls -- only pre-registered built-in and custom functions can be called

Defense Layers

LayerProtectionDefault
Property Access ControlAllowlist/denylist for property namesPrototype blocked
TimeoutMaximum execution time1000ms
Complexity LimitMaximum AST nodes100
Call Stack DepthMaximum function nesting50
Loop IterationsMaximum iterations in array functions10000
Feature TogglesDisable arrays, template strings, arrow functionsArrays on; template strings and arrow functions off

For evaluating fully untrusted input, use restrictive settings:

const options = {
timeout: 500,
maxComplexity: 50,
maxCallStackDepth: 20,
maxLoopIterations: 1000,
enableTemplateStrings: false,
enableArrowFunctions: false,
deniedProperties: ['password', 'secret', 'token', 'key'],
};

For trusted internal use where you control the expressions, you can use more permissive settings:

const options = {
enableTemplateStrings: true,
enableArrowFunctions: true,
timeout: 5000,
maxComplexity: 500,
};