After three years in the provisionally accepted state, the PEP has been rejected, because the required conditions for acceptance were never met.
This PEP has been provisionally accepted, with the following required conditions before the PEP is made Final:
Dependency confusion attacks, in which a malicious package is installed instead of the one the user expected, are an increasingly common supply chain threat. Most such attacks against Python dependencies, including the recent PyTorch incident, occur with multiple package repositories, where a dependency expected to come from one repository (e.g. a custom index) is installed from another (e.g. PyPI).
To help address this problem, this PEP proposes extending the Simple Repository API to allow repository operators to indicate that a project found on their repository “tracks” a project on different repositories, and allows projects to extend their namespaces across multiple repositories.
These features will allow installers to determine when a project being made available from a particular mix of repositories is expected and should be allowed, and when it is not and should halt the install with an error to protect the user.
There is a long-standing class of attacks that are called “dependency confusion” attacks, which roughly boil down to an individual user expected to get package A, but instead they got B. In Python, this almost always happens due to the configuration of multiple repositories (possibly including the default of PyPI), where they expected package A to come from repository X, but someone is able to publish package B to repository Y under the same name.
Dependency Confusion attacks have long been possible, but they’ve recently gained press with public examples of cases where these attacks were successfully executed.
A specific example of this is the recent case where the PyTorch project had an internal package named torchtriton which was only ever intended to be installed from their repositories located at https://download.pytorch.org/, but that repository was designed to be used in conjunction with PyPI, and the name of torchtriton was not claimed on PyPI, which allowed the attacker to use that name and publish a malicious version.
There are a number of ways to mitigate against these attacks today, but they all require that the end user go out of their way to protect themselves, rather than being protected by default. This means that for the vast bulk of users, they are likely to remain vulnerable, even if they are ultimately aware of these types of attacks.
Ultimately the underlying cause of these attacks come from the fact that there is no globally unique namespace that all Python package names come from. Instead, each repository is its own distinct namespace, and when given an “abstract” name such as spam to install, an installer has to implicitly turn that into a “concrete” name such as pypi.org:spam or example.com:spam. Currently the standard behavior in Python installation tools is to implicitly flatten these multiple namespaces into one that contains the files from all namespaces.
This assumption that collapsing the namespaces is what was expected means that when packages with the same name in different repositories are authored by different parties (such as in the torchtriton case) dependency confusion attacks become possible.
This is made particularly tricky in that there is no “right” answer; there are valid use cases both for wanting two repositories merged into one namespace and for wanting two repositories to be treated as distinct namespaces. This means that an installer needs some mechanism by which to determine when it should merge the namespaces of multiple repositories and when it should not, rather than a blanket always merge or never merge rule.
This functionality could be pushed directly to the end user, since ultimately the end user is the person whose expectations of what gets installed from what repository actually matters. However, by extending the repository specification to allow a repository to indicate when it is safe, we can enable individual projects and repositories to “work by default”, even when their project naturally spans multiple distinct namespaces, while maintaining the ability for an installer to be secure by default.
On its own, this PEP does not solve dependency confusion attacks, but what it does do is provide enough information so that installers can prevent them without causing too much collateral damage to otherwise valid and safe use cases.
There are two broad use cases for merging names across repositories that this PEP seeks to enable.
The first use case is when one repository is not defining its own names, but rather is extending names defined in other repositories. This commonly happens in cases where a project is being mirrored from one repository to another (see Bandersnatch) or when a repository is providing supplementary artifacts for a specific platform (see Piwheels).
In this case neither the repositories nor the projects that are being extended may have any knowledge that they are being extended or by whom, so this cannot rely on any information that isn’t present in the “extending” repository itself.
The second use case is when the project wants to publish to one “main” repository, but then have additional repositories that provide binaries for additional platforms, GPUs, CPUs, etc. Currently wheel tags are not sufficiently able to express these types of binary compatibility, so projects that wish to rely on them are forced to set up multiple repositories and have their users manually configure them to get the correct binaries for their platform, GPU, CPU, etc.
This use case is similar to the first, but the important difference that makes it a distinct use case on its own is who is providing the information and what their level of trust is.
When a user configures a specific repository (or relies on the default) there is no ambiguity as to what repository they mean. A repository is identified by an URL, and through the domain system, URLs are globally unique identifiers. This lack of ambiguity means that an installer can assume that the repository operator is trustworthy and can trust metadata that they provide without needing to validate it.
On the flip side, given an installer finds a name in multiple repositories it is ambiguous which of them the installer should trust. This ambiguity means that an installer cannot assume that the project owner on either repository is trustworthy and needs to validate that they are indeed the same project and that one isn’t a dependency confusion attack.
Without some way for the installer to validate the metadata between multiple repositories, projects would be forced into becoming repository operators to safely support this use case. That wouldn’t be a particularly wrong choice to make; however, there is a danger that if we don’t provide a way for repositories to let project owners express this relationship safely, they will be incentivized to let them use the repository operator’s metadata instead which would reintroduce the original insecurity.
This specification defines the changes in version 1.2 of the simple repository API, adding new two new metadata items: Repository “Tracks” and “Alternate Locations”.
To enable one repository to host a project that is intended to “extend” a project that is hosted at other repositories, this PEP allows the extending repository to declare that a particular project “tracks” a project at another repository or repositories by adding the URLs of the project and repositories that it is extending.
This is exposed in JSON as the key meta.tracks and in HTML as a meta element named pypi:tracks on the project specific URLs, ($root/$project/).
There are a few key properties that MUST be preserved when using this metadata:
It is NOT required that every name in a repository tracks the same repository, or that they all track a repository at all. Mixed use repositories where some names track a repository and some names do not are explicitly allowed.
To enable a project to extend its namespace across multiple repositories, this PEP allows a project owner to declare a list of “alternate locations” for their project. This is exposed in JSON as the key alternate-locations and in HTML as a meta element named pypi-alternate-locations, which may be used multiple times.
There are a few key properties that MUST be observed when using this metadata:
When an installer encounters a project that is using the alternate locations metadata it SHOULD consider that all repositories named are extending the same namespace across multiple repositories.
Note
This alternate locations metadata is project level metadata, not artifact level metadata, which means it doesn’t get included as part of the core metadata spec, but rather it is something that each repository will have to provide a configuration option for (if they choose to support it).
This section is non-normative; it provides recommendations to installers in how to interpret this metadata that this PEP feels provides the best tradeoff between protecting users by default and minimizing breakages to existing workflows. These recommendations are not binding, and installers are free to ignore them, or apply them selectively as they make sense in their specific situations.
Note
This algorithm is written based on how pip currently discovers files; other installers may adapt this based on their own discovery procedures.
Currently the “standard” file discovery algorithm looks something like this:
It is recommended that installers change their file discovery algorithm to take into account the new metadata, and instead do:
This is somewhat subtle, but the key things in the recommendation are:
This algorithm ensures that an installer never assumes that two disparate namespaces can be flattened into one, which for all practical purposes eliminates the possibility of any kind of dependency confusion attack, while still giving power throughout the stack in a safe way to allow people to explicitly declare when those disparate namespaces are actually one logical namespace that can be safely merged.
The above algorithm is mostly a conceptual model. In reality the algorithm may end up being slightly different in order to be more privacy preserving and faster, or even just adapted to fit a specific installer better.
This PEP avoids dictating or recommending a specific mechanism by which an installer allows an end user to configure exactly what repositories they want a specific package to be installed from. However, it does recommend that installers do provide some mechanism for end users to provide that configuration, as without it users can end up in a DoS situation in cases like torchtriton where they’re just completely broken unless they resolve the namespace collision externally (get the name taken down on one repository, stand up a personal repository that handles the merging, etc).
This configuration also allows end users to pre-emptively secure themselves during what is likely to be a long transition until the default behavior is safe.
Note
This example is pip specific and assumes specifics about how pip will choose to implement this PEP; it’s included as an example of how we can communicate this change, and not intended to constrain pip or any other installer in how they implement this. This may ultimately be the actual basis for communication, and if so will need be edited for accuracy and clarity.
This section should be read as if it were an entire “post” to communicate this change that could be used for a blog post, email, or discourse post.
There’s a long-standing class of attacks that are called “dependency confusion” attacks, which roughly boil down to an individual expected to get package A, but instead they got B. In Python, this almost always happens due to the end user having configured multiple repositories, where they expect package A to come from repository X, but someone is able to publish package B with the same name as package A in repository Y.
There are a number of ways to mitigate against these attacks today, but they all require that the end user explicitly go out of their way to protect themselves, rather than it being inherently safe.
In an effort to secure pip’s users and protect them from these types of attacks, we will be changing how pip discovers packages to install.
When pip discovers that the same project is available from multiple remote repositories, by default it will generate an error and refuse to proceed rather than make a guess about which repository was the correct one to install from.
Projects that natively publish to multiple repositories will be given the ability to safely “link” their repositories together so that pip does not error when those repositories are used together.
End users of pip will be given the ability to explicitly define one or more repositories that are valid for a specific project, causing pip to only consider those repositories for that project, and avoiding generating an error altogether.
See TBD for more information.
Users who are installing from multiple remote (e.g. not present on the local filesystem) repositories may be affected by having pip error instead of successfully install if:
Users who are not using multiple remote repositories will not be affected at all, which includes users who are only using a single remote repository, plus a local filesystem “wheel house”.
If you’re using only a single remote repository you do not have to do anything.
If you’re using multiple remote repositories, you can opt into the new behavior by adding --use-feature=TBD to your pip invocation to see if any of your dependencies are being served from multiple remote repositories. If they are, you should audit them to determine why they are, and what the best remediation step will be for you.
Once this behavior becomes the default, you can opt out of it temporarily by adding --use-deprecated=TBD to your pip invocation.
If you’re using projects that are not hosted on a public repository, but you still have the public repository as a fallback, consider configuring pip with a repository file to be explicit where that dependency is meant to come from to prevent registration of that name in a public repository to cause pip to error for you.
If you only publish your project to a single repository, then you do not have to do anything.
If you publish your project to multiple repositories that are intended to be used together at the same time, configure all repositories to serve the alternate repository metadata to prevent breakages for your end users.
If you publish your project to a single repository, but it is commonly used in conjunction with other repositories, consider preemptively registering your names with those repositories to prevent a third party from being able to cause your users pip install invocations to start failing. This may not be available if your project name is too generic or if the repositories have policies that prevent defensive name squatting.
You’ll need to decide how you intend for your repository to be used by your end users and how you want them to use it.
For private repositories that host private projects, it is recommended that you mirror the public projects that your users depend on into your own repository, taking care not to let a public project merge with a private project, and tell your users to use the --index-url option to use only your repository.
For public repositories that host public projects, you should implement the alternate repository mechanism and enable the owners of those projects to configure the list of repositories that their project is available from if they make it available from more than one repository.
For public repositories that “track” another repository, but provide supplemental artifacts such as wheels built for a specific platform, you should implement the “tracks” metadata for your repository. However, this information MUST NOT be settable by end users who are publishing projects to your repository. See TBD for more information.
Note: Some of these are somewhat specific to pip, but any solution that doesn’t work for pip isn’t a particularly useful solution.
If every repository returns the exact same list of files, then it is safe to consider those repositories to be the same namespace and implicitly merge them. This would possibly mean that mirrors would be automatically allowed without any work on any user or repository operator’s part.
Unfortunately, this has two failings that make it undesirable:
Providing some mechanism to give the repositories an order, and then short circuiting the discovery algorithm when it finds the first repository that provides files for that project is another workable solution that is safe if the order is specified correctly.
However, this has been rejected for a number of reasons:
There is a variant of this idea which effectively says that it’s really just PyPI’s nature of open registration that causes the real problems, so if we treat all repositories but the “default” one as equal priority, and then treat the default one as a lower priority then we’ll fix things.
That is true in that it does improve things, but it has many of the same problems as the general ordering idea (though not all of them).
It also assumes that PyPI, or whatever repository is configured as the “default”, is the only repository with open registration of names. However, projects like Piwheels exist which users are expected to use in addition to PyPI, which also effectively have open registration of names since it tracks whatever names are registered on PyPI.
One possible solution is to instead of having the installer have to solve this, to instead depend on repository proxies that can intelligently merge multiple repositories safely. This could provide a better experience for people with complex needs because they can have configuration and features that are dedicated to the problem space.
However, that has been rejected because:
Another possible solution is to rely on hash checking, since with hash checking enabled users cannot get an artifact that they didn’t expect; it doesn’t matter if the namespaces are incorrectly merged or not.
This is certainly a solution; unfortunately it also suffers from problems that make it unworkable:
Another idea is that we can narrow the scope of --extra-index-url such that its only supported use is to refer to supplemental repositories to the default repository, effectively saying that the default repository defines the namespace, and every additional repository just extends it with extra packages.
The implementation of this would roughly be to require that the project MUST be registered with the default repository in order for any additional repositories to work.
This sort of works if you successfully narrow the scope in that way, but ultimately it has been rejected because:
The main reason this problem exists is that we don’t have globally unique names, we have locally unique names that exist under multiple namespaces that we are attempting to merge into a single flat namespace. If we could instead come up with a way to have globally unique names, we could sidestep the entire issue.
This idea has been rejected because:
One idea that has come up is to essentially just implement the explicit configuration and don’t make any other changes to anything else. The specific proposal for a mapping policy is what actually inspired the explicit configuration option, and created a file that looked something like:
The recommendation to have explicit configuration pushes the decision on how to implement that onto each installer, allowing them to choose what works best for their users.
Ultimately only implementing some kind of explicit configuration was rejected because by its nature it’s opt in, so it doesn’t protect average users who are least capable to solve the problem with the existing tools; by adding additional protections alongside the explicit configuration, we are able to protect all users by default.
Additionally, relying on only explicit configuration also means that every end user has to resolve the same problem over and over again, even in cases like mirrors of PyPI, Piwheels, PyTorch, etc. In each and every case they have to sit there and make decisions (or find some example to cargo cult) in order to be secure. Adding extra features into the mix allows us to centralize those protections where we can, while still giving advanced end users the ability to completely control their own destiny.
There’s been some suggestion that scopes similar to how npm has implemented them may ultimately solve this. Ultimately scopes do not change anything about this problem. As far as I know scopes in npm are not globally unique, they’re tied to a specific registry just like unscoped names are. However what scopes do enable is an obvious mechanism for grouping related projects and the ability for a user or organization on npm.org to claim an entire scope, which makes explicit configuration significantly easier to handle because you can be assured that there’s a whole little slice of the namespace that wholly belongs to you, and you can easily write a rule that assigns an entire scope to a specific non public registry.
Unfortunately, it basically ends up being an easier version of the idea to only use explicit configuration, which works ok in npm because its not particularly common for people to use their own registries, but in Python we encourage you to do just that.
This PEP recommends installers to have a mechanism for explicit configuration of which repository a particular project comes from, but it does not define what that mechanism is. We are purposefully leave that undefined, as it is closely tied to the UX of each individual installer and we want to allow each individual installer the ability to expose that configuration in whatever way that they see fit for their particular use cases.
Further, when the idea of defining that mechanism came up, none of the other installers seemed particularly interested in having that mechanism defined for them, suggesting that they were happy to treat that as part of their UX.
Finally, that mechanism, if we did choose to define it, deserves it’s own PEP rather than baking it as part of the changes to the repository API in this PEP and it can be a future PEP if we ultimately decide we do want to go down the path of standardization for it.
Thanks to Trishank Kuppusamy for kick starting the discussion that lead to this PEP with his proposal.
Thanks to Paul Moore, Pradyun Gedam, Steve Dower, and Trishank Kuppusamy for providing early feedback and discussion on the ideas in this PEP.
Thanks to Jelle Zijlstra, C.A.M. Gerlach, Hugo van Kemenade, and Stefano Rivera for copy editing and improving the structure and quality of this PEP.
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-0708.rst
Last modified: 2026-04-17 09:54:03 UTC