Jul 23, 2026·6 min read·16 visits
A shared Node.js process and module cache in n8n's JavaScript task runner allows low-privileged users to dynamically hijack ('monkey-patch') globally imported Node.js and npm modules. This permits cross-tenant data theft and unauthorized execution boundaries breach within multi-user n8n environments.
A module-cache poisoning vulnerability exists in the n8n JavaScript task runner. In multi-tenant or multi-user n8n deployments, custom code executed inside Code nodes shares a single Node.js process and memory space. If custom module loading is enabled via configuration, an authorized user can dynamically patch (monkey-patch) globally cached modules. Subsequent executions of workflows by other tenants or users that load the same module will receive the poisoned reference, resulting in cross-tenant data exposure, credential hijacking, or integrity compromise.
The n8n workflow automation platform features a JavaScript Code node, allowing users to execute custom scripts as part of automated flows. In environments with multi-user configurations, multiple tenants construct and execute workflows within the same instance. To allow complex logic, administrators can enable the loading of built-in Node.js modules or external npm packages using specific environment parameters.
The vulnerability, registered under the identifier GHSA-9cmh-xcqm-5hqr, constitutes a cross-tenant module-cache poisoning flaw. The underlying flaw resides within the execution model of the JS Task Runner, which utilizes a single Node.js process to execute Code nodes from different tenants. Because the process space is shared, the module resolution caching mechanism remains global across distinct workflow execution scopes.
An authenticated user with permissions to create or execute workflows containing a Code node can manipulate shared resources within the process memory space. By monkey-patching methods of globally loaded modules, an attacker is capable of breaking tenant isolation barriers. Consequently, this leads to an isolation compromise affecting subsequent executions of workflows on the same worker process.
The Node.js runtime leverages an internal caching registry, known as require.cache for CommonJS modules, to optimize performance. When a script initiates a require() or import operation, Node.js compiles and executes the file on its initial invocation and caches the resulting exports object. Subsequent requests for the same module bypass disk access and directly return a pointer to the existing cached object.
In the vulnerable architecture of n8n, the JS Task Runner did not instantiate independent, clean virtual machine contexts or isolated processes for each user's execution scope. Instead, code blocks from varying users run inside the same underlying process structure. When an administrator enables module imports using NODE_FUNCTION_ALLOW_BUILTIN or NODE_FUNCTION_ALLOW_EXTERNAL, modules are loaded into this shared Node.js process space.
This architecture creates a shared mutable state. An attacker can write code that intercepts and rewrites properties on returned module exports. Since the runtime supplies the identical reference to any other execution thread inside the same process, the modifications persist globally. This enables unauthorized cross-tenant state interception and dynamic object-prototype manipulation.
To resolve this cache-poisoning vulnerability, the module-loading design within the task runner was redesigned. Rather than allowing standard, shared imports that reference the parent process cache, the execution environment must restrict or fully isolate the module resolution registry for each distinct tenant context.
The remediation introduces context-specific virtual machine sandboxes or distinct process isolations that construct a fresh module loader environment for each run. In isolated contexts, calling require constructs a separate, non-shared instance of the requested module, entirely independent of the parent process's global cache registries. This ensures that any monkey-patching attempts or runtime object modifications affect only the attacker's execution block.
Furthermore, the application of isolated task-running sandboxes ensures that custom functions do not leak references across executions. The patch isolates execution context so that the require loader is either virtualized per execution or process-isolated. This blocks cross-tenant module modification because the module exports are reinstantiated or cloned per execution block.
// Before patch (Vulnerable): Modules are resolved globally
const fs = require('fs');
fs.readFile = newProxyFunction; // Modifies global cache reference
// After patch (Patched): Context-isolated loader
// Inside isolated VM or subprocess:
const vmFs = isolatedRequire('fs');
// Modifications do not escape the tenant's execution contextAn attacker requires network access to an n8n deployment with workflow creation privileges. Additionally, the instance must have module-loading variables NODE_FUNCTION_ALLOW_BUILTIN or NODE_FUNCTION_ALLOW_EXTERNAL enabled. The attacker identifies target modules frequently utilized by other workflows, such as the built-in file system utility fs or HTTP client library axios.
The exploitation flow begins with the injection of a standard monkey-patching payload inside a standard Code node. The payload replaces a targeted method with an interceptor function designed to capture parameters. Once executed, this patch persists in the global memory space of the active runner process.
// Attacker's Code Node payload targeting the 'fs' module
const fs = require('fs');
if (!fs.__poisoned__) {
const originalReadFile = fs.readFile;
// Hijack standard readFile method to intercept parameters
fs.readFile = function(path, options, callback) {
// Intercept data and exfiltrate or log sensitive file access
console.log(`[EXFILTRATION] Intercepted access to file: ${path}`);
// Invoke original function to avoid operational disruption
return originalReadFile.apply(this, arguments);
};
fs.__poisoned__ = true;
}When a victim tenant triggers a workflow utilizing the same module, the victim's execution invokes the hijacked wrapper. The wrapper captures sensitive objects, including system paths, authentication keys, or internal API parameters. The captured data is then transmitted to an attacker-controlled endpoint or stored locally for subsequent retrieval.
The impact of this vulnerability is assessed as Medium, yielding a CVSS score of 5.8. The attack does not result in direct, host-level remote code execution outside the JS sandbox or complete host control. Instead, the core risk lies in the violation of the security boundaries separating individual tenants within the same n8n environment.
In a multi-tenant or shared enterprise instance, this flaw represents a total compromise of internal confidentiality and integrity. Attackers can intercept sensitive customer data, hijack database queries, or capture outbound API tokens. Additionally, because the hijacked code runs within the context of the victim's workflow, it executes with the victim's privileges and service connections.
This vulnerability poses no immediate threat to single-user deployments where tenant isolation boundaries are unnecessary. However, in software-as-a-service (SaaS) structures or shared agency environments, this issue presents a substantial compliance and isolation risk. Organizations must assume that any tenant with execution privileges could read or alter data belonging to other tenants running workflows on the same worker node.
The primary remediation action is to upgrade the n8n application to a patched release. For environments operating under 1.x, deploy version 1.123.67 or higher. For deployments running pre-releases within the 2.x range, upgrade to 2.31.5 or higher. For 2.32.x deployments, upgrade to 2.32.1 or higher.
If an upgrade cannot be executed immediately, administrators must disable custom module imports. This is achieved by unsetting or clearing the relevant environment variables in the runner container configuration. Setting these variables to empty values prevents users from importing built-in or external modules, eliminating the cache-poisoning attack vector:
NODE_FUNCTION_ALLOW_BUILTIN=
NODE_FUNCTION_ALLOW_EXTERNAL=Furthermore, deployments can implement architectural isolation by running external queue-based task workers. By ensuring that task runners are single-use or containerized per user group, the risk of a shared memory space is mitigated. Administrators should also limit workflow-creation privileges strictly to trusted operators.
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:H| Attribute | Detail |
|---|---|
| CWE ID | CWE-20 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 5.8 |
| Exploit Status | No Public PoC |
| Impact | Cross-Tenant Information Disclosure / Integrity Compromise |
| Privileges Required | Low (PR:L) |
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.