Skip to content

chore(deps): update dependency pdm to v2.27.0 [security]#1602

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-pdm-vulnerability
Open

chore(deps): update dependency pdm to v2.27.0 [security]#1602
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/pypi-pdm-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
pdm (changelog) ==2.26.9==2.27.0 age confidence

PDM: Project-Local State and Config Writes Follow Symlinks

CVE-2026-47763 / GHSA-ghq2-5c67-fprm

More information

Details

Summary

PDM writes several project-local state or configuration files without symlink protection. If a malicious repository places those files as symlinks, local PDM operations can overwrite the symlink targets.

This creates an arbitrary file clobber primitive relative to the privileges of the invoking user.

Affected Behavior
  • Project-local config writes can affect files outside the repository
  • The most stable demonstrated sink is pdm.toml
  • Related sinks include .pdm-python and .python-version
Affected Code
  • src/pdm/project/config.py:303-350
  • src/pdm/project/core.py:209-217
  • src/pdm/cli/commands/use.py:187-189
Technical Details

Config.__init__() resolves the project-local pdm.toml path and _save_config() writes to the resolved target. If PROJECT_ROOT/pdm.toml is a symlink to another file, pdm config -l ... updates the target file instead of refusing the write.

The same general problem exists for other project-local persistence paths that are written directly with no lstat / O_NOFOLLOW protection.

For the pdm.toml PoC specifically, the target file must already contain parseable TOML. Otherwise the load step fails before the write path is reached. That parser constraint does not apply to the .pdm-python or .python-version sinks.

Impact
  • Arbitrary file clobber as the invoking user
  • Destructive modification of local files outside the repository root
  • Useful primitive for privilege abuse when pdm is run in elevated contexts
Reproduction

PoC:

##### Replace this with a Python interpreter that can run `python -m pdm`.
PDM_PY=/path/to/python-with-pdm
tmpdir=$(mktemp -d)
target="$tmpdir/clobbered-target.toml"

cat > "$target" <<'EOF'
[seed]
value = 1
EOF

ln -s "$target" "$tmpdir/pdm.toml"

cat > "$tmpdir/pyproject.toml" <<'EOF'
[project]
name = "symlink-clobber-demo"
version = "0.0.1"
EOF

(
  cd "$tmpdir" &&
  "$PDM_PY" -m pdm config -l venv.in_project false
)

cat "$target"

Expected result:

  • A temporary project is created
  • pdm.toml is a symlink to another TOML file
  • Running pdm config -l venv.in_project false modifies the symlink target

Observed output from local validation:

--- target ---
[seed]
value = 1

[venv]
in_project = false
Severity

Medium

CVSS v4.0
  • Base score: 6.8 (Medium)
  • Vector: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N

Rationale:

  • AV:L: exploitation requires local execution of pdm against an attacker-prepared checkout
  • AC:L: there is no complex constraint once the symlink sink exists
  • AT:N: no extra prerequisite beyond the victim running the relevant command is required
  • PR:N: the attacker does not need prior privileges on the victim system
  • UI:A: the victim must actively run a command that writes project-local state or config
  • VC:N: the demonstrated issue is a write primitive, not a direct read primitive
  • VI:H: the attacker can cause unauthorized modification of files outside the repository root
  • VA:L: file clobber can disrupt local operation, but direct same-step availability impact is lower than a full RCE
  • SC:N/SI:N/SA:N: the base score is limited to the directly affected system
Root Cause

Project-local file sinks are treated as trusted regular files and are written without symlink checks or guarded atomic replacement.

Recommended Remediation
  • Refuse to write project-local config/state files when the destination is a symlink
  • Use lstat and O_NOFOLLOW where available
  • Avoid resolving attacker-controlled project-local paths before writing
  • Use atomic temp-file replacement only after confirming the destination is a regular file
Disclosure Notes

This issue is independent from the code-execution issues above. It is best tracked as a separate CVE candidate because the root cause and remediation are different.

Severity

  • CVSS Score: 6.8 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


PDM: Project-Controlled .pdm-plugins Content Executes Before CLI Parsing

CVE-2026-47781 / GHSA-qq6c-99pv-prvf

More information

Details

Summary

PDM 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.

This allows arbitrary code execution with the privileges of the user running pdm from an untrusted repository checkout.

Affected Behavior
  • Trigger does not require pdm install --plugins
  • A low-impact command such as pdm --version is sufficient
  • Impact is strongest in CI, privileged shells, and automation contexts
Affected Code
  • src/pdm/core.py:74-82
  • src/pdm/core.py:310-333
  • src/pdm/core.py:335-352
Technical Details

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().

On 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.

Impact
  • Arbitrary code execution as the invoking user
  • Potential credential theft, persistence, or workspace tampering
  • Potential privilege escalation when pdm is run via sudo, root-owned CI jobs, or privileged service accounts
Reproduction

PoC:

##### Replace this with a Python interpreter that can run `python -m pdm`.
PDM_PY=/path/to/python-with-pdm
tmpdir=$(mktemp -d)

cat > "$tmpdir/pyproject.toml" <<'EOF'
[project]
name = "plugin-autoload-demo"
version = "0.0.1"
EOF

purelib=$(TMPDIR_ROOT="$tmpdir/.pdm-plugins" "$PDM_PY" - <<'PY'
import os
import sys
import sysconfig

base = os.environ["TMPDIR_ROOT"]
scheme_names = sysconfig.get_scheme_names()
if (sys.platform == "darwin" and "osx_framework_library" in scheme_names) or sys.platform == "linux":
    scheme = "posix_prefix"
elif sys.version_info < (3, 10):
    scheme = "nt" if os.name == "nt" else "posix_prefix"
else:
    scheme = sysconfig.get_default_scheme()
replace_vars = {"base": base, "platbase": base}
print(sysconfig.get_path("purelib", scheme, replace_vars))
PY
)

mkdir -p "$purelib"
marker="$tmpdir/plugin-autoload-marker.txt"
printf '%s\n' "import pathlib; pathlib.Path(r'$marker').write_text('project plugin autoload executed', encoding='utf-8')" > "$purelib/evil.pth"

(
  cd "$tmpdir" &&
  "$PDM_PY" -m pdm --version
)

cat "$marker"

Expected result:

  • A temporary project is created
  • An evil.pth file is placed under .pdm-plugins
  • Running pdm --version creates a marker file before CLI exit

Observed output from local validation:

PDM, version 2.26.9

--- marker ---
project plugin autoload executed
Severity

High

CVSS v4.0
  • Base score: 8.4 (High)
  • 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

Rationale:

  • AV:L: exploitation occurs through local execution of pdm against attacker-controlled repository content
  • AC:L: no special bypass or race is required
  • AT:N: no external precondition beyond the vulnerable workflow is required
  • PR:N: the attacker does not need privileges on the victim host
  • UI:A: the victim must actively run a pdm command in the malicious checkout
  • VC:H/VI:H/VA:H: successful exploitation yields arbitrary code execution as the invoking user
  • SC:N/SI:N/SA:N: the score is kept to same-system impact only
Root Cause

Project-local plugin paths are implicitly trusted and loaded too early, and .pth processing is inherited from site.addsitedir().

Recommended Remediation
  • Do not auto-load project-local .pdm-plugins by default
  • Avoid site.addsitedir() for project-controlled plugin paths
  • If project plugins must be supported, require explicit opt-in such as --enable-project-plugins
  • Explicitly prevent .pth execution when loading project plugin paths
Disclosure Notes

This 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.

Severity

  • CVSS Score: 8.4 / 10 (High)
  • Vector String: 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

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

pdm-project/pdm (pdm)

v2.27.0

Compare Source

Breaking Changes
  • Update the minimum required Python version to 3.10. (#​3787)
Features & Improvements
  • Respect existing values of pyproject.toml when running pdm init or pdm new. (#​3786)
  • Move project plugin installations from .pdm-plugins under the project root to an isolated cache directory, and add a fixer to migrate existing plugin directories. (#​3790)
  • Remove legacy importlib compatibility wrappers and use standard-library importlib.metadata and importlib.resources APIs directly. (#​3796)
Bug Fixes
  • Fix a security issue with the installer to disallow installing to paths outside of the scheme directory. (#​3787)
  • Refuse to write project-local config and state files (pdm.toml, .pdm-python, .python-version) when the destination is a symlink, preventing an untrusted repository from clobbering files outside the project root. (#​3788)
  • Fix a regression issue that PDM_LOCKFILE env var is not respected. (#​3794)
  • Allow configuring the default lock --exclude-newer value with strategy.exclude-newer. (#​3795)

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot force-pushed the renovate/pypi-pdm-vulnerability branch 3 times, most recently from 1daa7fd to 30df2a7 Compare June 13, 2026 07:14
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@renovate renovate Bot force-pushed the renovate/pypi-pdm-vulnerability branch 2 times, most recently from c3efad2 to 743a2b3 Compare June 20, 2026 04:40
@renovate renovate Bot force-pushed the renovate/pypi-pdm-vulnerability branch from 743a2b3 to ae599fd Compare June 20, 2026 08:37
@renovate renovate Bot force-pushed the renovate/pypi-pdm-vulnerability branch from ae599fd to 56fc19b Compare June 20, 2026 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants