{"id":"CVE-2026-47763","aliases":["GHSA-ghq2-5c67-fprm","PYSEC-2026-2862"],"url":"https://o3.security/vulnerability/CVE-2026-47763","summary":"pdm: Project-Local State and Config Writes Follow Symlinks","details":"## Summary\n\nPDM 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.\n\nThis creates an arbitrary file clobber primitive relative to the privileges of the invoking user.\n\n## Affected Behavior\n\n- Project-local config writes can affect files outside the repository\n- The most stable demonstrated sink is `pdm.toml`\n- Related sinks include `.pdm-python` and `.python-version`\n\n## Affected Code\n\n- `src/pdm/project/config.py:303-350`\n- `src/pdm/project/core.py:209-217`\n- `src/pdm/cli/commands/use.py:187-189`\n\n## Technical Details\n\n`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.\n\nThe same general problem exists for other project-local persistence paths that are written directly with no `lstat` / `O_NOFOLLOW` protection.\n\nFor 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.\n\n## Impact\n\n- Arbitrary file clobber as the invoking user\n- Destructive modification of local files outside the repository root\n- Useful primitive for privilege abuse when `pdm` is run in elevated contexts\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)\ntarget=\"$tmpdir/clobbered-target.toml\"\n\ncat > \"$target\" <<'EOF'\n[seed]\nvalue = 1\nEOF\n\nln -s \"$target\" \"$tmpdir/pdm.toml\"\n\ncat > \"$tmpdir/pyproject.toml\" <<'EOF'\n[project]\nname = \"symlink-clobber-demo\"\nversion = \"0.0.1\"\nEOF\n\n(\n  cd \"$tmpdir\" &&\n  \"$PDM_PY\" -m pdm config -l venv.in_project false\n)\n\ncat \"$target\"\n```\n\nExpected result:\n\n- A temporary project is created\n- `pdm.toml` is a symlink to another TOML file\n- Running `pdm config -l venv.in_project false` modifies the symlink target\n\nObserved output from local validation:\n\n```text\n--- target ---\n[seed]\nvalue = 1\n\n[venv]\nin_project = false\n```\n\n## Severity\n\nMedium\n\n## CVSS v4.0\n\n- Base score: `6.8` (`Medium`)\n- 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`\n\nRationale:\n\n- `AV:L`: exploitation requires local execution of `pdm` against an attacker-prepared checkout\n- `AC:L`: there is no complex constraint once the symlink sink exists\n- `AT:N`: no extra prerequisite beyond the victim running the relevant command is required\n- `PR:N`: the attacker does not need prior privileges on the victim system\n- `UI:A`: the victim must actively run a command that writes project-local state or config\n- `VC:N`: the demonstrated issue is a write primitive, not a direct read primitive\n- `VI:H`: the attacker can cause unauthorized modification of files outside the repository root\n- `VA:L`: file clobber can disrupt local operation, but direct same-step availability impact is lower than a full RCE\n- `SC:N/SI:N/SA:N`: the base score is limited to the directly affected system\n\n## Root Cause\n\nProject-local file sinks are treated as trusted regular files and are written without symlink checks or guarded atomic replacement.\n\n## Recommended Remediation\n\n- Refuse to write project-local config/state files when the destination is a symlink\n- Use `lstat` and `O_NOFOLLOW` where available\n- Avoid resolving attacker-controlled project-local paths before writing\n- Use atomic temp-file replacement only after confirming the destination is a regular file\n\n## Disclosure Notes\n\nThis 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.","published":"2026-08-04T17:29:41.188Z","modified":"2026-10-21T02:58:16.256500170Z","cvss":null,"epss":{"score":0.00151,"percentile":0.0459,"asOf":"2026-09-02"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"PyPI","name":"pdm","fixedVersion":"2.27.0"}],"fix":null,"references":[{"type":"WEB","url":"https://github.com/pdm-project/pdm/releases/tag/2.27.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/47xxx/CVE-2026-47763.json"},{"type":"ADVISORY","url":"https://github.com/pdm-project/pdm/security/advisories/GHSA-ghq2-5c67-fprm"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-47763"},{"type":"PACKAGE","url":"https://github.com/pdm-project/pdm"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:58:16.256500170Z"}}