Safeguard
Software Supply Chain•

How do you defend a Python project from PyPI typosquats and dependency confusion?

Use one trusted index, pin hashes, block source builds where you can, and review every new package name before it enters a requirements file.

Safeguard Security Team
4 min read

Defend a Python project from typosquatting and dependency confusion with four habits: install from one controlled index, pin hashes so a swapped file fails, prefer wheels over source builds, and review each new package name before it enters a requirements file. Together they close the two ways a wrong package gets in.

What are the two attacks?

Typosquatting relies on a human mistake. Someone types a name one character off from a real package, or picks a lookalike from search results, and installs an attacker's upload. Dependency confusion relies on tool behaviour. A company has an internal package called, say, acme-billing-utils. If the installer can also see a public index, an attacker publishes a public package with the same name and a higher version number, and the installer picks the "best" version.

The second one needs no mistake by a developer. It needs only a configuration that searches more than one place.

What does pip actually do with several indexes?

The pip documentation is blunt. Packages are searched in a number of places: the main index, the local file system, and any extra repositories given by --find-links or --extra-index-url. It says "there is no priority in the locations that are searched. Rather they are all checked, and the 'best' match for the requirements (in terms of version number) is selected."

That sentence is the dependency confusion risk in full. If your internal index is added as an extra index next to the public one, a higher public version wins. The fix is to avoid the pattern: point pip at one index, and make that index a proxy or mirror you control that serves internal packages and a vetted view of public ones. Then a name can only resolve to one owner.

How do hashes help?

pip's secure installs page describes hash-checking mode, enabled with the require-hashes option. Each requirement in a requirements file carries a hash, and pip refuses a file that does not match. The docs say it protects against remote tampering and network issues. The same page suggests disallowing source distributions with the only-binary option, since a source package can run build code on install.

Be clear about what hashes do not do. A hash pins what you reviewed. It does not tell you the package was safe when you first added it. If a typosquat is the first thing you hashed, you have pinned the attacker's file. So hashes protect later installs, and review protects the first one.

How do you review a new name?

Make adding a dependency a visible act. A constructed checklist for the pull request that adds a package:

  • Spell-check the name against the one in the project's own documentation, character by character.
  • Open the project page and compare the repository link, the release history and the maintainer with what you expect.
  • Be suspicious of a package with a very recent first release that claims to be a well-known tool.
  • Ask whether the standard library or an existing dependency already does the job.

Reviewers see names in diffs, so put the lockfile change in the same pull request.

What about internal names?

Keep internal packages under a distinctive prefix, and have your mirror refuse to serve a public package whose name matches an internal one. Some teams also register their internal names on the public index so nobody else can. Check your index's rules before you rely on that.

What to do

  1. Configure each environment, CI job and developer machine with a single index URL. Remove extra-index entries.
  2. Generate a requirements file with hashes and install with hash checking on.
  3. Install wheels only where possible, and review any package that needs a source build.
  4. Add a pull request check that flags new top-level dependencies for human review.
  5. Prefix internal package names and block clashes at the mirror.
  6. Scan the dependency tree regularly. Safeguard's free scan at gold.safeguard.sh/scan checks up to 100 dependencies.

Quick check

Does pip prefer my private index if I add it with the extra index option?

No. The pip docs say there is no priority between locations. All are checked and the best version match is chosen.

Do pinned hashes stop typosquatting?

Only for later installs. They pin whatever you first accepted, so you still need review at the moment a package is added.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.