mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
feat(core): Make the vm expression engine the default on 1.x (#36498)
Some checks are pending
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.13.1) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (25.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
Some checks are pending
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.13.1) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (25.x) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4ee48e61a1
commit
75bedd8eaf
|
|
@ -16,6 +16,10 @@ describe('ExpressionEngineConfig', () => {
|
|||
});
|
||||
|
||||
describe('defaults', () => {
|
||||
test('engine defaults to vm', () => {
|
||||
expect(Container.get(ExpressionEngineConfig).engine).toBe('vm');
|
||||
});
|
||||
|
||||
test('bridgeTimeout defaults to 5000', () => {
|
||||
expect(Container.get(ExpressionEngineConfig).bridgeTimeout).toBe(5000);
|
||||
});
|
||||
|
|
@ -25,6 +29,21 @@ describe('ExpressionEngineConfig', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('N8N_EXPRESSION_ENGINE', () => {
|
||||
test('overrides engine to legacy', () => {
|
||||
process.env.N8N_EXPRESSION_ENGINE = 'legacy';
|
||||
expect(Container.get(ExpressionEngineConfig).engine).toBe('legacy');
|
||||
});
|
||||
|
||||
test('falls back to default on invalid value', () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
process.env.N8N_EXPRESSION_ENGINE = 'not-an-engine';
|
||||
expect(Container.get(ExpressionEngineConfig).engine).toBe('vm');
|
||||
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('N8N_EXPRESSION_ENGINE_IDLE_TIMEOUT', () => {
|
||||
test('overrides idleTimeout', () => {
|
||||
process.env.N8N_EXPRESSION_ENGINE_IDLE_TIMEOUT = '60';
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ const expressionEngineSchema = z.enum(['legacy', 'vm']);
|
|||
export class ExpressionEngineConfig {
|
||||
/**
|
||||
* Which expression engine to use.
|
||||
* - `legacy` runs expressions without isolation.
|
||||
* - `vm` runs expressions in a V8 isolate.
|
||||
*
|
||||
* `vm` is currently **experimental**. Use at your own risk.
|
||||
* - `vm` (default) runs expressions in a V8 isolate.
|
||||
* - `legacy` runs expressions without isolation. Less secure and soon to be deprecated.
|
||||
*/
|
||||
@Env('N8N_EXPRESSION_ENGINE', expressionEngineSchema)
|
||||
engine: 'legacy' | 'vm' = 'legacy';
|
||||
engine: 'legacy' | 'vm' = 'vm';
|
||||
|
||||
/** Number of V8 isolates ready in the pool. */
|
||||
@Env('N8N_EXPRESSION_ENGINE_POOL_SIZE')
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ describe('GlobalConfig', () => {
|
|||
},
|
||||
},
|
||||
expressionEngine: {
|
||||
engine: 'legacy',
|
||||
engine: 'vm',
|
||||
poolSize: 1,
|
||||
maxCodeCacheSize: 1024,
|
||||
bridgeTimeout: 5000,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Implemented so far:
|
|||
- ✅ Integration tests (PR 4)
|
||||
|
||||
Coming in later PRs:
|
||||
- 🚧 Workflow integration behind `N8N_EXPRESSION_ENGINE=vm` flag (PR 5)
|
||||
- ✅ Workflow integration via `N8N_EXPRESSION_ENGINE` (default: `vm`; set `legacy` to opt out) (PR 5)
|
||||
- 🚧 Web Worker support (Phase 2+)
|
||||
- 🚧 Performance optimizations (Phase 3)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { ActiveExecutions } from '@/active-executions';
|
|||
import { DeprecationService } from '@/deprecation/deprecation.service';
|
||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||
import { TelemetryEventRelay } from '@/events/relays/telemetry.event-relay';
|
||||
import { ExpressionObservabilityProvider } from '@/expression-observability/expression-observability.provider';
|
||||
import { ExternalHooks } from '@/external-hooks';
|
||||
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
|
||||
import { CommunityPackagesService } from '@/modules/community-packages/community-packages.service';
|
||||
|
|
@ -31,6 +32,7 @@ const loadNodesAndCredentials = mockInstance(LoadNodesAndCredentials);
|
|||
const shutdownService = mockInstance(ShutdownService);
|
||||
const deprecationService = mockInstance(DeprecationService);
|
||||
mockInstance(MessageEventBus);
|
||||
mockInstance(ExpressionObservabilityProvider);
|
||||
const posthogClient = mockInstance(PostHogClient);
|
||||
const telemetryEventRelay = mockInstance(TelemetryEventRelay);
|
||||
const externalHooks = mockInstance(ExternalHooks);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { ActiveExecutions } from '@/active-executions';
|
|||
import { DeprecationService } from '@/deprecation/deprecation.service';
|
||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||
import { TelemetryEventRelay } from '@/events/relays/telemetry.event-relay';
|
||||
import { ExpressionObservabilityProvider } from '@/expression-observability/expression-observability.provider';
|
||||
import { ExternalHooks } from '@/external-hooks';
|
||||
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
|
||||
import { CommunityPackagesService } from '@/modules/community-packages/community-packages.service';
|
||||
|
|
@ -30,6 +31,7 @@ const loadNodesAndCredentials = mockInstance(LoadNodesAndCredentials);
|
|||
const shutdownService = mockInstance(ShutdownService);
|
||||
const deprecationService = mockInstance(DeprecationService);
|
||||
mockInstance(MessageEventBus);
|
||||
mockInstance(ExpressionObservabilityProvider);
|
||||
const posthogClient = mockInstance(PostHogClient);
|
||||
const telemetryEventRelay = mockInstance(TelemetryEventRelay);
|
||||
const externalHooks = mockInstance(ExternalHooks);
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ describe('DeprecationService', () => {
|
|||
['EXECUTIONS_DATA_PRUNE_TIMEOUT', '1', true],
|
||||
['N8N_CONFIG_FILES', '1', true],
|
||||
['N8N_SKIP_WEBHOOK_DEREGISTRATION_SHUTDOWN', '1', true],
|
||||
['N8N_EXPRESSION_ENGINE', 'legacy', true],
|
||||
['N8N_EXPRESSION_ENGINE', 'vm', false],
|
||||
])('should detect when %s is `%s`', (envVar, value, mustWarn) => {
|
||||
toTest(envVar, value, mustWarn);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -91,6 +91,12 @@ export class DeprecationService {
|
|||
'n8n does not support `own` mode since May 2023. Please remove this environment variable to allow n8n to start. If you need the isolation and performance gains, please consider queue mode: https://docs.n8n.io/hosting/scaling/queue-mode/',
|
||||
checkValue: (value: string) => value === 'own',
|
||||
},
|
||||
{
|
||||
envVar: 'N8N_EXPRESSION_ENGINE',
|
||||
message:
|
||||
'The `legacy` expression engine runs expressions without isolation, is no longer considered secure, and will be removed in a future version. Remove this environment variable to use the default `vm` engine.',
|
||||
checkValue: (value?: string) => value === 'legacy',
|
||||
},
|
||||
{
|
||||
envVar: 'N8N_BLOCK_ENV_ACCESS_IN_NODE',
|
||||
message:
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ export class Expression {
|
|||
}
|
||||
|
||||
/**
|
||||
* Initialize the VM evaluator (if feature flag is enabled).
|
||||
* Initialize the VM evaluator (no-op when the legacy engine is selected).
|
||||
* Should be called once during application startup.
|
||||
* Only available in Node.js environments (not in browser).
|
||||
*/
|
||||
|
|
@ -660,7 +660,7 @@ export class Expression {
|
|||
if (Expression.expressionEngine === 'vm' && !IS_FRONTEND) {
|
||||
if (!Expression.vmEvaluator) {
|
||||
throw new ApplicationError(
|
||||
'N8N_EXPRESSION_ENGINE=vm is enabled but VM evaluator is not initialized. Call Expression.initExpressionEngine() during application startup.',
|
||||
'The VM expression engine has not been initialized. Call Expression.initExpressionEngine() during application startup.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,14 +15,15 @@ export default defineConfig({
|
|||
{
|
||||
test: {
|
||||
...sharedTestConfig,
|
||||
name: 'legacy-engine',
|
||||
name: 'vm-engine',
|
||||
env: { N8N_EXPRESSION_ENGINE: 'vm' },
|
||||
},
|
||||
},
|
||||
{
|
||||
test: {
|
||||
...sharedTestConfig,
|
||||
name: 'vm-engine',
|
||||
env: { N8N_EXPRESSION_ENGINE: 'vm' },
|
||||
name: 'legacy-engine',
|
||||
env: { N8N_EXPRESSION_ENGINE: 'legacy' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user