
Research
Shai-Hulud Descends to Hades: Miasma Worm Campaign Spreads with New PyPI Wave
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.
@n8n/expression-runtime
Advanced tools
Secure, isolated expression evaluation runtime for n8n workflows.
In progress — landing as a series of incremental PRs.
Implemented so far:
IsolatedVmBridge: V8 isolate management via isolated-vm (PR 3)ExpressionEvaluator: tournament integration, expression code caching (PR 4)Coming in later PRs:
N8N_EXPRESSION_ENGINE=vm flag (PR 5)This package provides a secure runtime for evaluating expressions in isolated contexts.
Currently supports:
isolated-vm for V8 isolate-based isolation with lazy data loadingFuture support (Phase 2+):
ThisSanitizer, PrototypeSanitizer, DollarSignValidator) validate expressions before executionThe runtime uses a three-layer architecture:
See ARCHITECTURE.md for detailed design documentation.
pnpm add @n8n/expression-runtime
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: 'test@example.com' }
}
}
);
console.log(result); // "test@example.com"
// Clean up
await evaluator.dispose();
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.
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.
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;
}
Abstract interface for bridge implementations.
interface RuntimeBridge {
initialize(): Promise<void>;
execute(code: string, data: Record<string, unknown>): unknown;
dispose(): Promise<void>;
isDisposed(): boolean;
}
E() error handler for tournament-generated try-catch codeinterface 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
}
# 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.
# 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
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
The runtime uses several optimizations (implemented in PRs 2–4):
Performance characteristics:
The runtime enforces strict security at multiple layers (implemented in PRs 2–4):
ThisSanitizer rewrites $json → this.$json; PrototypeSanitizer wraps computed property access in this.__sanitize(key) to block prototype chain attacks; DollarSignValidator enforces correct $-variable usage__sanitize() inside the isolate blocks access to __proto__, constructor, prototype, and other dangerous properties at runtimeFuture security features (Phase 2+):
See the main n8n repository for contribution guidelines.
See LICENSE.md in the n8n repository root.
FAQs
Secure, isolated expression evaluation runtime for n8n
The npm package @n8n/expression-runtime receives a total of 80,629 weekly downloads. As such, @n8n/expression-runtime popularity was classified as popular.
We found that @n8n/expression-runtime demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.