n8n: the CVE-2026-86083 flaw in the legacy expression engine — what it is and how to migrate to vm
By Clément Lacaille (Tech-Bharat) · Published 11 September 2026 · 5 min read · how we build and test
Complete FlowKit Bundle — €269
12 ready-to-import n8n workflows, setup guide in French, 14-day guarantee
A self-hosted n8n instance that's been quietly running in the background for months, untouched because "it works," is a classic of no-code automation. That's exactly the scenario CVE-2026-86083 targets: a high-severity security flaw (CVSS 7.7) found in n8n's "legacy" expression engine, the one that interprets every {{ $json.field }} dropped into a node. If your workflows — whether they came from a FlowKit pack or you built them yourself — rely on n8n expressions (true of almost every installation), this patch deserves priority treatment rather than a routine update.
What n8n's expression engine is, and why there are two versions
Every time an n8n node holds a value between double curly braces — {{ $json.email }}, {{ $now.toISO() }} — that string is sent to an expression engine that interprets it and returns a value. It's the mechanism described in our guide to n8n expression syntax, and it runs continuously, even in workflows that contain no Code node at all.
n8n has long shipped two implementations of this engine: the legacy engine, older, which dynamically builds JavaScript source text and then executes it; and the vm engine, newer, which evaluates the expression inside an isolated V8 context via isolated-vm, with much stricter separation between the evaluated code and the rest of the n8n process. On patched versions, vm is now the default engine, and legacy is officially deprecated.
CVE-2026-86083: an expression that rewrites what it executes
The technical detail, documented in the security advisory published on the GitLab Advisory Database, is instructive: the legacy engine generated its source text by calling JSON.stringify, a mutable global function, to print synthetic string literals and interpolate timezone data. A malicious expression could replace that global reference to JSON.stringify — and cause the source text generated afterward to contain attacker-controlled executable code. In other words, the engine meant to isolate the expression from its environment could be turned against itself to escape the sandbox.
The flaw is classified as CWE-94 ("Improper Control of Generation of Code") with a CVSS 4.0 score of 7.7, corresponding to a network vector, low required privilege, and no user interaction needed — as soon as untrusted data (a webhook field, automatically processed email content, a form input) ends up inside an expression evaluated by an unpatched instance. The fix ships in versions 1.123.76, 2.37.7 and 2.38.2: it routes both code-generation stages through a reference captured at module load time, so a later change to global state can no longer alter the generated source. Only instances still running the legacy engine are affected — the vm engine, not exposed to this mechanism, was already safe even before the patch.
Why this kind of flaw isn't a one-off accident
This isn't the first time a JavaScript sandbox built "at the language level" — without process isolation — has been bypassed through clever abuse of shared primitives. A study by Abdullah AlHamdan and Cristian-Alexandru Staicu, presented at the USENIX Security Symposium 2023 ("SandDriller: A Fully-Automated Approach for Testing Language-Based JavaScript Sandboxes"), automatically tested six widely used JavaScript sandboxing systems and uncovered eight previously unknown escape vulnerabilities. The authors' conclusion matches exactly what CVE-2026-86083 illustrates: a sandbox built on language-level tricks (proxies, prototype freezing, reference capture) rather than process isolation remains structurally fragile, no matter how careful the implementation. That same logic is what drove n8n's move to the vm engine, as well as isolating the Code node in separate Task Runners since n8n 2.0 — two distinct hardening efforts, driven by the same underlying principle.
Is your instance affected?
If you use n8n Cloud, there's nothing to do: the vendor applies patches server-side. If you're self-hosted — Docker, VPS, Kubernetes — check your version under Settings > About, then check whether the N8N_EXPRESSION_ENGINE environment variable explicitly forces the legacy engine in your configuration; that's the only case where an up-to-date instance could still be vulnerable. Before any update, follow the usual method rather than a blind docker compose pull: back up your database (PostgreSQL backup guide for self-hosted n8n), pin an explicit tag, and follow our procedure for updating n8n on Docker without breaking anything for a clean rollback path if something goes wrong.
Migrating to the vm engine
The migration comes down to three steps:
- Update n8n to 1.123.76, 2.37.7, 2.38.2 or later. On these versions, the vm engine is enabled by default, no extra configuration needed.
- Remove any
legacyvalue from theN8N_EXPRESSION_ENGINEvariable in your docker-compose file,.envfile, or Kubernetes secrets. Never set it tolegacy, even temporarily to "test" old behavior. - Test your critical workflows after restarting, especially ones that manipulate dates, timezones, or dynamically built strings — these are the usages most likely to reveal a behavior gap between the two engines.
Good expression hygiene, beyond the patch
The update closes this specific flaw, but two habits remain worthwhile regardless of which engine you run. First, never let fully untrusted data (a public webhook payload, an email attachment, an open form field) feed directly into a complex expression or a Code node without intermediate validation — the same principle behind securing a publicly exposed n8n webhook. Second, keep credentials out of expressions themselves: they belong in n8n's encrypted credential manager, never concatenated into a dynamically evaluated string.
Checklist
- n8n version checked: 1.123.76 / 2.37.7 / 2.38.2 or higher.
N8N_EXPRESSION_ENGINEunset or explicitlyvm, neverlegacy.- PostgreSQL backup taken before the update, Docker tag pinned.
- Date- and dynamic-string-sensitive workflows re-tested after migration.
- Untrusted data never fed as-is into an expression or a Code node.
FlowKit pack workflows rely, like any n8n workflow, on standard expressions: they run unmodified on an up-to-date instance using the vm engine. If traceability is part of your requirements — knowing who changed what, and when, on an instance that just got patched — the Compliance & Audit Pack (149 €) includes a ready-to-use audit trail, a complement to this security update rather than a substitute for it.
FAQ
Frequently asked questions
Are n8n Cloud instances affected by CVE-2026-86083?
Not on your end: n8n Cloud is managed by the vendor, which applies security patches to its infrastructure without any action on your part. This flaw affects self-hosted instances (Docker, VPS, Kubernetes) that you administer yourself and whose version hasn't yet been updated to a patched release.
How do I know which expression engine my n8n instance is using?
First check your version number under Settings > About: if you're below 1.123.76, 2.37.7 or 2.38.2, you may still be on the vulnerable legacy engine. Then check the N8N_EXPRESSION_ENGINE environment variable in your deployment: if it's explicitly set to "legacy", change or remove it — on a patched version, leaving that variable unset switches you automatically to vm, the default engine.
Will migrating to the vm engine break my existing expressions?
In the vast majority of cases, no: the vm engine follows the same n8n expression syntax and the same variables (`$json`, `$node`, `$now`…). The rare divergences involve advanced usages that relied on undocumented legacy-engine behavior. Test your critical workflows after updating, especially ones that manipulate dates or dynamically built strings, before considering the migration finished.
What should I do if I can't update my instance right away?
Until you update, minimize anything that can influence the content of an expression evaluated by the instance: unauthenticated webhook payloads, public form fields, automatically processed email content. This is only a temporary mitigation — the real fix is updating to a patched version, and it should be scheduled as a priority rather than left as background work.