Safeguard
Security Guides•

Why is pip's extra-index-url unsafe for private packages?

pip's own documentation warns that extra-index-url is unsafe for private packages because of dependency confusion. Here is why and what to use instead.

Safeguard Security Team
3 min read

pip treats every index you give it as a source for any package name, so a public package with the same name as your private one can be chosen instead. pip's documentation warns that using extra-index-url for private packages is unsafe because of dependency confusion. Use one controlled index instead.

What the option does

The pip reference describes the index URL option as the base URL of the package index, defaulting to PyPI. The extra index URL option adds more indexes in addition to it. Pip then considers candidates from all of them together.

That is the issue. There is no rule that says "private names come only from the private index". If an attacker publishes a package on the public index with your internal name and a higher version number, a resolver that merges candidates can pick it.

An example of the failure

# risky: public PyPI plus a private index
pip install --extra-index-url https://pkgs.internal.example/simple acme-billing-utils

If acme-billing-utils exists privately at 1.2.0 and someone registers acme-billing-utils at 99.0.0 publicly, the highest version can win. The fix is not a cleverer version pin on one project. It is removing the ambiguity.

What to use instead

  1. A single index you control. Point pip at an internal index or repository manager that serves your private packages and proxies approved public ones. Pip only ever talks to that one URL.
  2. Hash pinning. Hashes in the requirements file mean a swapped package fails the check. See does pip run code from packages.
  3. Reserve the names. Where possible, register your internal names publicly so nobody else can.

What about PEP 708?

PEP 708 proposed metadata that would let installers tell when mixing repositories is expected, aimed at dependency confusion. Its page now lists the status as rejected, because the conditions for acceptance were never met, and it said that on its own it did not solve the problem. So do not wait on a client feature. Configure your environment.

Why a version pin is not the fix

Pinning one project to an exact version helps that project today, but it leaves every other project and every new install exposed. Fix the source configuration once, centrally, and the whole class of problem goes away. Put the index setting in a shared pip configuration file or in the environment of your CI image, and review changes to it.

A quick audit

Search your repositories, CI files and Dockerfiles for the extra index option and the equivalent environment variable. Each hit is a place where private and public names are mixed. For a broader walkthrough, see detect dependency confusion before it ships.

Quick check

Does index-url alone solve it?

Pointing to one index you control removes the mixing, as long as that index does not itself blindly merge public names ahead of private ones. Check how your repository manager prioritizes sources.

Is this a PyPI bug?

No. It is a consequence of how clients combine indexes, which is why pip documents the warning.

Safeguard does SBOM and dependency scanning, and a free scan shows the sources and versions in your Python tree.

Sources

Last reviewed: 19 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.