Setup PyPI Proxy with DevGuard Dependency Proxy
PyPI is one of the most actively abused package registries for supply chain attacks. The 2022 PyTorch supply chain attack saw attackers publish a malicious torchtriton package on PyPI that exfiltrated sensitive data from machines where PyTorch nightly builds were installed — a package that was installed automatically as a dependency. Thousands of similar attacks have followed, exploiting the fact that pip install executes code on download by default.
The DevGuard dependency proxy sits between your Python tooling and the upstream PyPI registry, checking every package against the OSV dataset before it reaches your environment.
- Registry URL:
<your-devguard-url>/api/v1/dependency-proxy/pypi/simple
Configuration
Using a configuration file
Create or edit pip.conf (Linux/macOS: ~/.config/pip/pip.conf, Windows: %APPDATA%\pip\pip.ini) to permanently redirect all pip installs through DevGuard:
Using environment variables
For CI/CD pipelines or ephemeral environments, set the proxy via environment variables instead. Most systems do not allow installing packages into the system Python, so create and activate a virtual environment first:
Then point pip at the proxy:
Testing
DevGuard ships a test package that is permanently flagged as malicious for all versions:
If the install is blocked, the proxy is working correctly. pip does not show the 403 Forbidden itself; it only reports No matching distribution found for fake-malicious-pypi-package. All other packages in the same environment will resolve normally from the upstream PyPI registry.
Testing name normalization (PEP 503)
PEP 503 defines a project name as case-insensitive, with any run of -, _ or . treated as equivalent. fake-malicious-pypi-package, fake_malicious_pypi_package, Fake.Malicious.Pypi.Package and FAKE--MALICIOUS--PYPI--PACKAGE all name the same project. A proxy that compares names as raw strings instead of normalizing them first can be bypassed by requesting the malicious package under an equivalent, differently-spelled name.
Testing version normalization (PEP 440)
PEP 440 defines version equality semantically, not textually: 1.0, 1.0.0, 1.0.0.0, 01.0 and 1.00 all denote the exact same version. A release's version on PyPI is just the first spelling that was used, and nothing requires every file attached to that release to repeat the same spelling in its filename - so a proxy that string-compares versions instead of parsing them can be bypassed by a distribution filename that uses a different, equivalent spelling of a flagged version.
DevGuard ships a second test package, fake-malicious-pypi-package-versioned, flagged at the specific version v1.0.0 rather than for all versions, so it can prove version comparison itself is correct. The proxy checks a distribution download against the malicious package database before ever fetching it from upstream, so the file below does not need to exist for this check:
Thanks a lot to https://github.com/IlyasMakari for bringing this issue to our attention.
Testing the minimum package age
The minimum package age is configured per repository and only applies to requests using the repository's proxy URL, which contains a secret. The proxy removes all files that are too new from the package index, so pip resolves to the newest version that is old enough instead of failing on the download.
To verify it, temporarily set the minimum age to 87600 hours (10 years). requests==2.32.3 was published in May 2024, so the proxy must reject it, while an unpinned requests still resolves to an older version: