PyPI Lack of Version Pinning

ID

lack_version_pinning_pip

Severity

low

Family

Lack of Version Pinning

Tags

reachable

Description

Under certain circumstances (in application dependency descriptors) the versions should be pinned to avoid misbehaviour or injection of malicious dependencies (direct or indirect).

For example, under PyPI, a publishable Pipfile.lock makes dependencies pinned and avoids future injections, but existing vulnerabilities for the pinned versions should be handled.

This detector checks if the pinning file exists and is added to control version.

Security

If the project has not version pinning files as:

- Pipfile.lock
- poetry.lock

or requirements.txt file with not all versions pinned (with == operator), then attackers can create new public malicious packages with higher version number, and the PyPI installer could use the new version.

Examples

requirements.txt
 ...
 nice-package >= 1.0.0
 ...

If the attacker gains access to publish new versions of nice-package, he/she can create a new version "99.9.9" with malware, and pip will happily resolve this new malicious version.

Mitigation / Fix

Create a lock file to fix dependencies and sub-dependencies. Some tools like Poetry automatically generates poetry.lock from dependencies listed in pyproject.toml. It is recommended to keep the lock file under version control, so it is shared with team members, and when version updates are done on the dependencies manifest, the lock file could be regenerated.

Modern version management with pip follows the Repeatable Installs and Secure Installs approach, where package versions are pinned and hash-checking add integrity protection against remote tampering. The requirements file containing pinned package versions can be generated using pip freeze, which pins not only direct dependencies, but also all of their transitive dependencies.

If you are using poetry, you can generate a lockfile with pinned versions for direct and transitive dependencies with poetry lock --no-update (--no-update prevents updating dependencies).

If you are releasing a library on PyPI, you should declare whatever dependencies you know about, but not pin to a specific version: the library author does not know about versions of additional libraries that consumers of the library will use.

If you do not distribute the software as a package or wheel to third-parties, such as with application software, then version pinning is recommended, so version updates should follow a controlled process, where versions of dependencies (direct and transitive) are pinned but updated for improvements or security fixes. This ensures repeatable version graphs and protects against malicious versions of existing packages, if you receive alerts about potential malicious versions in the dependencies of your software projects.

Of course, reviewing dependencies is always recommended. But at least an attack based on introducing malware on new versions of existing packages will not affect you immediately.

Malware packages are detected by the community or by Xygeni Malware Early Warning, and known vulnerabilities are also detected and patched regularly. You have to trade off the rate of version updates for getting security and quality patches but without opening the door to supply-chain attacks.

Version pinning helps with reproducible builds, and forces version upgrades not to be fully automatic.

References: