skip to content

How do pip's `--index-url` and `--extra-index-url` differ when a private index is in play?

level: seniorimportance: should knowfreq 44%

answer

  1. One replaces, the other appends
  2. pip pools candidates from every index
  3. Highest version wins, not the closest index
  4. An unclaimed internal name is the whole exploit
  5. Prefer one proxying index over an extra

basics

~20 s

--index-url replaces the default index; --extra-index-url adds a second index that is searched alongside it. pip pools candidates from every configured index and picks the best version overall, so it has no notion of one index being more trusted than another.

solid answer

~40 s

`--index-url` **replaces** the index pip queries; `--extra-index-url` **adds** one, so the public index stays in the search set. The dangerous part is what pip does next: it collects every candidate file for a project name from *all* configured indexes and selects by version and compatibility, not by which index answered. If an internal distribution name also exists publicly, a higher public version wins — the dependency-confusion failure. The safe shape for a private index is a single `--index-url` pointing at a private repository that also proxies the public one, so exactly one source is authoritative and ordering never matters. Reserve the name publicly as well, or use `--no-index` with `--find-links` for fully controlled installs.

code

console · 2 lines
console
python -m pip install --index-url https://pypi.internal.example.com/simple fraudscore-features
python -m pip install --extra-index-url https://pypi.internal.example.com/simple fraudscore-features

go deeper

for a junior

Remember the plain distinction: one flag replaces the index pip searches, the other adds another one to it. Knowing that both exist and that they are not interchangeable is enough at this level.

for a middle

Explain the mechanic that makes the difference matter: pip unions candidates from all configured indexes and picks by version and compatibility, never by which index served the file. That single fact drives the whole answer.

for a senior

Demonstrate the production judgement: name the shadowing failure, propose a single proxying index as the fix, and mention name reservation, hash-pinned installs and credential handling in index URLs. Explain how you would detect that a distribution changed origin.

for a principal

Own the supply-chain posture: who runs the internal repository, whether public packages are proxied or mirrored and vetted, how internal names are reserved, and what the build is allowed to reach on the network at all.

### Two flags that look symmetric and are not pip's index configuration has one *primary* index and any number of *extra* indexes. `--index-url` sets the primary, replacing the default public index. `--extra-index-url` leaves the primary in place and appends another. Both can also come from a pip configuration file or from a line at the top of a requirements file, which is where they usually live on a real team — and that is part of why the difference is easy to miss in review. ### What pip does with more than one index For each project name it needs, pip queries every configured index, unions the candidate files it gets back, and then chooses among them by version, by compatibility with the running interpreter and platform, and by the requirement's version specifier. **The index a candidate came from is not part of that decision.** There is no priority order, no "private first", no fallback semantics. Two indexes serving the same name are simply two sources of candidates for one project. ### The failure that follows Take a fraud-scoring service whose feature-extraction library is published only to the team's private index as `fraudscore-features`, version `1.8.0`, and installed with `--extra-index-url https://pypi.internal.example.com/simple`. If anyone registers that same project name on the public index and publishes `9.0.0`, the next install resolves to the public `9.0.0` — pip did exactly what it was told. The nightly six-hour scoring run then executes third-party code with the service's credentials and data access, and the install log looks entirely normal because the requirement was satisfied. This class of attack is *dependency confusion*, and it needs no compromise of the private index at all: publishing a higher version under an unclaimed name is the whole exploit. ### Shapes that hold up **One authoritative index.** Point `--index-url` at a private repository configured to proxy the public index, and configure no extras at all. Every request goes to one place; the proxy decides what public content is visible and can be told never to shadow an internal name. This is the recommendation to give in an interview, because it removes the ambiguity rather than mitigating it. **Claim the names.** Register your internal project names on the public index as placeholder projects so nobody else can, and prefer a distinctive prefix for internal distributions. Cheap, and it survives a misconfigured machine. **Pin what you install.** A fully pinned requirements set with hashes makes an unexpected candidate fail loudly instead of installing, because a substituted file cannot match a recorded hash. Pinning is a separate discipline with its own tradeoffs, but it is the backstop when index configuration is not fully under your control. **Cut the network.** `--no-index` with `--find-links` against a vetted directory of wheels is the strongest form for a locked-down build: there is no index to confuse. ### Operational details worth saying out loud Credentials for a private index are frequently embedded in the URL, which then leaks into shell history, CI logs and the requirements file in version control; prefer the credential mechanisms your index and CI provide, and remember pip's own output can echo the URL on failure. `--trusted-host` exists to silence TLS verification against a host and is almost always the wrong fix for a certificate problem — it turns an authenticated channel into an unauthenticated one for every package fetched from it. Finally, an extra index is invisible in a lockfile that records only names and versions: two developers with different pip configuration can install different files for identical requirement lines, which is exactly the kind of "works on my machine" that takes a day to find. ### Answering the question crisply The compact answer is three sentences: `--index-url` replaces, `--extra-index-url` adds; pip merges candidates from all indexes and picks by version rather than by source; therefore an extra index is a name-shadowing risk and a single proxying index is the configuration to prefer. Everything above is the elaboration an interviewer will pull you toward once you have said that.

  • Your team must keep an extra index for now. What reduces the risk without changing the configuration?
    Register the internal project names on the public index so they cannot be claimed, and give internal distributions a distinctive name prefix. Then install from a fully pinned requirements set with recorded hashes, so a substituted file fails the hash check instead of installing. Add a CI check that the resolved distribution for each internal name came from the internal host, and treat any change in resolved origin as a build failure rather than a warning.
  • Where else can an index URL come from besides the command line?
    From pip's configuration files at global, user and site level, from environment variables, and from an option line inside a requirements file. That spread is the practical problem: a machine can carry an extra index nobody put in the command, so two engineers running an identical command install different files. Auditing index configuration means reading all of those sources, not just the invocation.
  • Is `--trusted-host` a reasonable fix when a private index has a certificate error?
    Almost never. It disables TLS verification for that host, so every distribution fetched from it is unauthenticated and modifiable in transit — you have swapped a certificate problem for a code-execution one. Install the internal certificate authority into the trust store, or point pip at a CA bundle. Reserve `--trusted-host` for a throwaway local experiment, never for CI or a production image build.

saying these in an interview costs you the question

  • Says pip prefers the private index over the public one
  • Thinks an extra index is only a fallback when the primary misses
  • Treats the two flags as synonyms
  • Believes an internal name cannot be published publicly
  • Reaches for --trusted-host to fix a TLS failure

context