
Proof-of-concept and lab for an n8n 2.42.0 sandbox escape: hidden Function/FunctionItem nodes execute JS in the main process, giving authenticated members host RCE.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · n8n-function-vm2
n8n 2.42.0 - n8n GmbH
GHSA-j4p8-h8mh-rh8q moved n8n-nodes-base.code onto the JS task-runner child. Production spawn adds --disallow-code-generation-from-strings and --disable-proto=delete. The leftover is the nodes they hid instead of moving. Function.node.ts is hidden: true. So is FunctionItem. So is LangChain Code. Palette hide is a UI flag. Workflow JSON save/import still resolves the type. Default NODES_EXCLUDE is only Execute Command and Local File Trigger. The execute path is in-process vm2 NodeVM 3.12.2 in the n8n main process.
A default member who can create a workflow can run JS in the n8n main process. Hidden is not an ACL.
| ID | no CVE yet |
| CWE | CWE-94, CWE-269 |
| CVSS | High: 8.8 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Product | n8n |
| Affected | through 2.42.0 (86c23326); leftover of GHSA-j4p8 |
| Auth | authenticated member with workflow create/import/execute |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
As a default member, import or create a workflow whose node type is n8n-nodes-base.function (or functionItem, or @n8n/n8n-nodes-langchain.code) and execute it. The JS runs in the n8n main process.
That is host RCE as the n8n UID: instance secrets, other users' credential ciphertext on disk, the DB. Multi-tenant member to instance takeover. Not OS LPE. Not unauthenticated. Code v2 on the same instance does not see process that way.
Same product, sibling leftover: Databricks path join.
I read the 16 September 2026 GHSA wave first, then ten hunts on tag [email protected]. Expression sandbox, Code/Function/vm2, credentials, webhooks, HTTP/SSRF, Git node, community packages, auth/SSO, node injection, public API. Most of the Highs in that wave are closed on this pin. Code was not finished.
v3 breaking-change rules already mark Function / FunctionItem removed. On 2.42.0 they are leftover, not an intended long-term sandbox. Function also injects this.helpers, including helpers the JS runner lists as unsupported. I did not need a public vm2 gadget to prove the process boundary.
I stood up stock n8nio/n8n:2.42.0, signed up a member (not instance owner), imported n8n-nodes-base.function, executed it. Function pid=7 is the n8n main PID. Same JS in Code v2: hasProcess: false. Task-runner child is pid 26. Witness N8N-FUNCTION-VM2-WITNESS.
Wrong turns already recorded: Function type rejected as hidden/unknown (then the node is actually excluded); Function pid equal to the task-runner child (then it already moved); Code v2 seeing process (then the runner is not the control); a vm2 exploit gadget, a reverse shell, OS LPE. Theatre. The witness is main PID vs hasProcess: false.
cd lab
./run.sh
Target only http://127.0.0.1:18201. Default NODES_EXCLUDE. Attacker is a member.
authenticated-as=member
function-fields pid=7 witness=N8N-FUNCTION-VM2-WITNESS
code-fields hasProcess=False pid=None
compare function_pid=7 code_pid=None main_pid=7 runner_pid=26 who=member
SUCCESS N8N-FUNCTION-VM2 who=member function_pid=7 code_pid=None main_pid=7 runner_pid=26 N8N-FUNCTION-VM2-WITNESS
Add n8n-nodes-base.function, n8n-nodes-base.functionItem, and @n8n/n8n-nodes-langchain.code to default NODES_EXCLUDE, or route them through JsTaskRunnerSandbox the way Code v2 already does. Palette hidden is not enough.
Function.node.ts · Code.node.ts · nodes.config.ts · task-runner-process-js.tsGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.