50. Supply chain: lockfiles, hashes, audits, and trusted publishing
PyPI hosts over half a million projects, and anyone can publish. Attacks
seen in the wild: typosquatting (reqeusts, python-dateutil vs
dateutil), dependency confusion (a public package with the same name
as your internal one, at a higher version), account takeover of a
maintainer, malicious code in setup.py that runs at install time, and
compromised release pipelines. The
Security guide Ch 47
and Security in Depth Ch 13
cover the theory; here is the Python practice.
# 1. Lock exact versions AND hashes of everything, including transitive dependencies.
uv lock # uv.lock records sha256 hashes for every artifact
uv sync --locked # install exactly that; fail if the lock is stale
# with pip: pip-compile --generate-hashes; pip install --require-hashes -r requirements.txt
# 2. Known vulnerabilities (PyPI advisory database / OSV).
uv tool run pip-audit # in CI, fail the build on findings
# 3. Prefer wheels: an sdist runs arbitrary build code on your machine at install time.
uv sync --no-build # refuse to build anything from source
# pip install --only-binary :all: ...
# 4. Internal packages: one index, explicitly. Never mix --extra-index-url with public PyPI for
# names you own (dependency confusion). In uv, pin each package to an index:
# [tool.uv.sources] mylib = { index = "internal" } + [[tool.uv.index]] name="internal" explicit=true
# 5. See what you actually pull in, and why.
uv tree --depth 2
Publishing your own packages: use PyPI Trusted Publishing from CI (GitHub Actions OIDC; no long-lived API token to steal), and PyPI now records attestations (Sigstore-signed provenance) for packages published this way. Turn on 2FA for every maintainer account.
Before adding a dependency, check: is it the real name (look at the
project's repository link on PyPI); how many maintainers; when it was last
released; how many dependencies it brings (uv tree after a trial add);
and whether the standard library already does it. Every dependency is code
that runs with your program's privileges, and in AI projects, often with
access to GPUs, datasets, and cloud credentials.
War story. In late 2022, a malicious package named torchtriton was
uploaded to public PyPI. PyTorch nightly builds depended on a package of
that name hosted on PyTorch's own index, but pip's resolution with
--extra-index-url let the public, higher-priority copy win. For several
days, people installing PyTorch nightly got a binary that read SSH keys,
.gitconfig, and other files and sent them out over DNS. It was textbook
dependency confusion. Lesson: when you use more than one package index,
pin each internal name to its index explicitly, and verify hashes.