npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@n8n/expression-runtime

v0.4.0

Published

Secure, isolated expression evaluation runtime for n8n

Readme

@n8n/expression-runtime

Secure, isolated expression evaluation runtime for n8n workflows.

Status

In progress — landing as a series of incremental PRs.

Implemented so far:

  • ✅ TypeScript interfaces and architecture design (PR 1)
  • ✅ Core architecture documentation (PR 1)
  • ✅ Runtime bundle: extension functions, deep lazy proxy system (PR 2)
  • IsolatedVmBridge: V8 isolate management via isolated-vm (PR 3)
  • ExpressionEvaluator: tournament integration, expression code caching (PR 4)
  • ✅ Integration tests (PR 4)

Coming in later PRs:

  • 🚧 Workflow integration behind N8N_EXPRESSION_ENGINE=vm flag (PR 5)
  • 🚧 Web Worker support (Phase 2+)
  • 🚧 Performance optimizations (Phase 3)

Overview

This package provides a secure runtime for evaluating expressions in isolated contexts.

Currently supports:

  • Node.js Backend: Uses isolated-vm for V8 isolate-based isolation with lazy data loading

Future support (Phase 2+):

  • Browser Frontend: Will use Web Workers for browser-based isolation
  • Task Runners: Will use IPC for separate process isolation

Features

  • 🔒 Secure: Expressions run in isolated V8 contexts with memory limits (128MB) and timeouts (5s)
  • 🚀 Performant: Lazy data loading via proxies, script compilation caching, and expression code caching
  • 📊 Observable: Built-in metrics, traces, and logs support (interfaces defined; providers coming later)
  • 🌐 Universal: Works in Node.js backend (browsers and task runners in Phase 2+)
  • 🛡️ AST Security: Tournament AST hooks (ThisSanitizer, PrototypeSanitizer, DollarSignValidator) validate expressions before execution

Architecture

The runtime uses a three-layer architecture:

  1. Runtime (Layer 1): Runs inside isolated context, provides expression execution environment
  2. Bridge (Layer 2): Manages communication between host and isolated context
  3. Evaluator (Layer 3): Public API with Tournament integration and observability

See ARCHITECTURE.md for detailed design documentation.

Installation

pnpm add @n8n/expression-runtime

Usage

Basic Example

import { ExpressionEvaluator, IsolatedVmBridge } from '@n8n/expression-runtime';

// Create bridge
const bridge = new IsolatedVmBridge({
  memoryLimit: 128,
  timeout: 5000,
});

// Create evaluator
const evaluator = new ExpressionEvaluator({
  bridge,
});

// Initialize
await evaluator.initialize();

// Evaluate expression using {{ }} template syntax
const result = evaluator.evaluate(
  '{{ $json.user.email }}',
  {
    $json: {
      user: { email: '[email protected]' }
    }
  }
);

console.log(result); // "[email protected]"

// Clean up
await evaluator.dispose();

With Security Hooks (Production)

Pass AST security hooks from expression-sandboxing.ts to enable full security validation. This is the pattern used by the workflow package:

import { ExpressionEvaluator, IsolatedVmBridge } from '@n8n/expression-runtime';
import {
  ThisSanitizer,
  PrototypeSanitizer,
  DollarSignValidator,
} from 'n8n-workflow/expression-sandboxing';

const bridge = new IsolatedVmBridge({ timeout: 5000 });
const evaluator = new ExpressionEvaluator({
  bridge,
  hooks: {
    before: [ThisSanitizer],
    after: [PrototypeSanitizer, DollarSignValidator],
  },
});

await evaluator.initialize();

When hooks is omitted the evaluator still runs tournament transformation (template parsing, this binding) but without AST security validation — suitable for development and testing.

With Observability (Not Yet Implemented)

import { OpenTelemetryProvider } from '@n8n/expression-runtime/observability';

const observability = new OpenTelemetryProvider({
  serviceName: 'n8n-expressions',
});

const evaluator = new ExpressionEvaluator({
  bridge,
  observability,
});

Note: Observability providers are not yet implemented. The ObservabilityProvider interface exists but no implementations are available yet.

API

ExpressionEvaluator

Main class for expression evaluation.

class ExpressionEvaluator {
  constructor(config: EvaluatorConfig);
  initialize(): Promise<void>;
  evaluate(expression: string, data: WorkflowData, options?: EvaluateOptions): unknown;
  dispose(): Promise<void>;
  isDisposed(): boolean;
}

RuntimeBridge

Abstract interface for bridge implementations.

interface RuntimeBridge {
  initialize(): Promise<void>;
  execute(code: string, data: Record<string, unknown>): unknown;
  dispose(): Promise<void>;
  isDisposed(): boolean;
}

Bridge Implementations

  • IsolatedVmBridge: ✅ For Node.js backend (isolated-vm with V8 isolates)
    • Memory isolation with hard 128MB limit
    • Timeout enforcement (5s default)
    • Deep lazy proxy system for workflow data
    • Synchronous callbacks via ivm.Reference
    • Security wrappers (SafeObject, SafeError)
    • E() error handler for tournament-generated try-catch code
  • WebWorkerBridge: 🚧 For browser frontend (Web Workers) - Phase 2+
  • Task Runner Integration: 🚧 TBD - May use IsolatedVmBridge locally or direct evaluation - Phase 2+

Configuration

interface EvaluatorConfig {
  bridge: RuntimeBridge;                   // required
  observability?: ObservabilityProvider;   // optional - interfaces defined, providers not yet implemented
  hooks?: TournamentHooks;                 // optional - AST security hooks for tournament
}

interface BridgeConfig {
  memoryLimit?: number;        // Default: 128 MB
  timeout?: number;            // Default: 5000 ms
  debug?: boolean;             // Default: false
}

Environment Variables (Not Yet Implemented)

# Bridge configuration (not yet implemented)
N8N_EXPRESSION_MEMORY_LIMIT_MB=128
N8N_EXPRESSION_TIMEOUT_MS=5000
N8N_EXPRESSION_DEBUG=false

# Code cache (not yet implemented - caches transformed code, not results)
N8N_EXPRESSION_CODE_CACHE_ENABLED=true
N8N_EXPRESSION_CODE_CACHE_MAX_SIZE=1000

# Observability (not yet implemented)
N8N_EXPRESSION_OBSERVABILITY_ENABLED=true
N8N_EXPRESSION_METRICS_ENABLED=true
N8N_EXPRESSION_TRACES_ENABLED=true
N8N_EXPRESSION_TRACE_SAMPLE_RATE=0.01

Note: Currently, configuration is passed via constructor options. Environment variable support will be added in future phases.

Development

# Install dependencies
pnpm install

# Build package
pnpm build

# Run tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Type check
pnpm typecheck

# Lint
pnpm lint

Testing

The package uses vitest for fast, isolated testing:

import { ExpressionEvaluator, IsolatedVmBridge } from '@n8n/expression-runtime';

describe('ExpressionEvaluator', () => {
  it('evaluates simple expression', async () => {
    const bridge = new IsolatedVmBridge({ timeout: 5000 });
    const evaluator = new ExpressionEvaluator({ bridge });

    await evaluator.initialize();

    const result = evaluator.evaluate('{{ $json.value }}', { $json: { value: 42 } });
    expect(result).toBe(42);

    await evaluator.dispose();
  });
});

Run tests:

pnpm test                # Run all tests
pnpm test integration    # Run integration tests only

Performance

The runtime uses several optimizations (implemented in PRs 2–4):

  • Lazy Loading: Only fetch data fields that expressions actually access via proxy traps
  • Script Compilation Caching: Compiled scripts are cached to avoid recompilation
  • Metadata-Driven: Only structure (keys, lengths) transferred across isolate boundary, not full data
  • Expression Code Caching: Tournament-transformed code is cached per evaluator instance (same expressions repeat within a workflow, so cache hit rate is high in practice)

Performance characteristics:

  • Arrays: Always lazy-loaded — only length transferred, elements fetched on demand
  • Objects: Always lazy-loaded — only keys transferred, values fetched on demand

Security

The runtime enforces strict security at multiple layers (implemented in PRs 2–4):

  • Memory limits: Hard 128MB limit via isolated-vm (configurable)
  • Execution timeouts: 5s default timeout (configurable)
  • Complete isolation: No access to Node.js APIs (require, fs, process, etc.)
  • Security wrappers: SafeObject and SafeError prevent dangerous method access
  • Native function blocking: Prevents access to native code
  • AST transforms: ThisSanitizer rewrites $jsonthis.$json; PrototypeSanitizer wraps computed property access in this.__sanitize(key) to block prototype chain attacks; DollarSignValidator enforces correct $-variable usage
  • Runtime sanitizer: __sanitize() inside the isolate blocks access to __proto__, constructor, prototype, and other dangerous properties at runtime

Future security features (Phase 2+):

  • 🚧 Additional sandboxing for browser environments

Contributing

See the main n8n repository for contribution guidelines.

License

See LICENSE.md in the n8n repository root.

Related