This post continues my series on the security of the PyPI ecosystem. The other posts:
1: PyPI Upload Denial of Service
3: Distribution Confusion in PyPI
This time it’s about the potential for malicious lockfiles in the PDM package manager, assigned CVE-2023-45805. Thanks to Frost Ming, the PDM maintainer, for the quick response and patches. I have not seen this attack class before so I will call it Trojan Lockfiles (let me know if there is already another name for this).
Lockfiles are used to narrow down which dependencies to use for a build. The project file (e.g. pyproject.toml) might specify version ranges for dependencies. The package manager will typically resolve specific versions and hashes and then place those in a lockfile to enable reproducibility. See my previous post on Reproducibility in PyPI for why this is a difficult problem.
Ideally, a lockfile only narrows down the dependencies specified in the project file. So if you are doing a security review of a project’s dependencies, you only have to look at the project file to see the dependencies of a project. As we have seen with Distribution Confusion and Manifest Confusion this isn’t straightforward to achieve. Setting those aside, the lockfile can still be inconsistent with the project file, which is the case for PDM.
PDM has an option for static_urls (not used by default), which will point directly to the distribution files to download, rather than resolving it from an index like PyPI (which is the common approach). CVE-2023-45805 enabled an attacker to appear to depend on one project from PyPI, but actually fetch another. This could have enabled a malicious open source project or insider to covertly depend on a malicious project. The fix was to use the same check as PyPI introduced earlier this year (to fix a DoS issue). Furthermore the static URLs can point to any domain, including malicious ones, so this requires a code review to make sure that the domains are safe. To further mitigate this PDM will now disallow static URLs when static_urls is set to false in pdm.lock.