How can a dependency's import path still resolve correctly after an attacker takes over its expired domain?
answer
- a path is a coordinate, not content
- control of the name changed hands
- the diff is empty either way
- pinning a label is not pinning bytes
- recorded checksum turns it into a failure
basics
~20 sBecause an import path is a name, not an identity. Resolution asks whoever controls that domain or namespace today for the code, so a lapsed domain bought by a stranger serves their content under the original, unchanged path.
solid answer
~50 sName-based resolution binds a build to a coordinate - a domain, an organisation namespace, a hosting path - and re-asks that coordinate on every fetch. Control of the coordinate can change hands without the coordinate changing: a vanity import domain lapses and is bought at auction, or an abandoned organisation namespace on a code host is re-registered by a stranger while old paths still redirect to it. The build then succeeds exactly as before, the diff is empty, and the code that arrives is someone else's. What breaks the attack is having recorded the identity of what you resolved last time - a checksum recorded alongside the version - so that a change of content under an unchanged name is a hard failure rather than a silent one. As a publisher you owe the mirror image of that: keep the domain renewed and the namespace claimed for as long as anyone can still import you.
go deeper
Know that a dependency path is a name that gets looked up again on every build, so whoever controls that name today controls the code you get. Recording a checksum is what makes a swap visible.
Be able to walk the mechanics: resolution consults the domain or namespace, ownership can change without the path changing, and only a recorded content hash distinguishes the two owners.
Show judgment about detection and about the publisher side - inventorying the coordinates a build depends on, failing builds on hash mismatch, and keeping your own domains and namespaces claimed after a project is archived.
Frame it as a naming-authority risk across the estate: decide which ecosystems you accept dependencies from based on whether they bind content identity, and fund an internal copy of what you have already accepted.
## Names versus identities Every dependency system has to answer two different questions. *Which thing do I want?* is answered by a **name**: a package coordinate, an organisation and repository, or - in ecosystems that support vanity import paths - a domain that the toolchain visits to learn where the code really lives. *Is this the thing I got last time?* is answered by an **identity**: a cryptographic hash of the bytes. The attacks in this family all exploit systems where the name is checked on every build and the identity is checked on none. Nothing is spoofed, nothing is broken into, and no version number changes. Control of the name simply moved. ## Three shapes of the same move **An expired vanity domain.** A library is imported under a path rooted in a domain the author owned. Resolution starts by asking that domain where the source is. Years later the author stops paying for it, the registration lapses, and it is bought at auction - domain drop-catching is an industry, and names with live traffic are exactly what it looks for. The new owner points resolution wherever they like. Every consumer whose build still fetches by path now compiles the buyer's code, and the import line in their source is byte-identical to what it always was. **An abandoned organisation namespace.** A project moves, the organisation on the code host is deleted, and the name becomes free. A stranger re-registers it. Old download URLs and old import paths still resolve, and on some hosts a rename even leaves a redirect that now lands in the new owner's repository. Consumers who pinned to a tag or a branch under that path keep fetching. **A hosting path that outlives its owner.** The same reasoning applies to a release-artifact URL, a documentation-hosted install target, or any address that other people's automation still calls long after the team behind it dissolved. The attacker position is unglamorous and cheap: an anonymous buyer with a credit card and a script watching for expiry. The asset is **source integrity across every consumer that never pinned an identity** - and, downstream of that, whatever those consumers' builds can reach. ## Why the usual defences miss it - **Code review sees nothing.** The import line did not change; no pull request exists to review. - **Version pinning is not enough on its own.** Pinning `v1.4.2` fixes which *label* you ask for. If the new owner can publish or serve content under that label - or if the label is a git tag in a repository they now control - the label resolves to their bytes. - **A dependency inventory does not help by itself.** An inventory that lists names and versions describes the *coordinate*, and the coordinate is precisely what stayed the same. An inventory carrying a hash for each component is a different matter, because the hash is the identity. - **The attack does not need a new release.** It only needs the next fetch that misses a cache. ## Detection and response The reliable detector is a recorded hash: ecosystems that write a checksum next to every resolved dependency turn a content change under a stable name into a build failure, which is exactly the outcome you want. Absent that, the weaker signals are external - a change in the registration or hosting of a domain your build depends on, a new maintainer appearing on a long-quiet project, a certificate issued by an authority the project never used before. These are monitoring signals, not guarantees. Operationally, the practical controls are: 1. **Record identities, not just names.** Verify a checksum for every resolved dependency and treat a mismatch as a stop. 2. **Keep a copy of what you already resolved**, so that the network is not re-consulted for code you have already accepted, and so a swap upstream does not silently enter tomorrow's build. 3. **Inventory the coordinates you depend on**, including the domains behind them, and know which of them are load-bearing for a build. 4. **Watch for abandonment.** A dependency whose maintainer has gone quiet is a candidate for a namespace or domain change of hands, not only for unpatched bugs. ## The publisher's obligation If you publish under a domain or namespace that others import, that name is part of your product's security surface for as long as anyone can still fetch it. Renew the registration past the point where you care about it, keep the organisation claimed even if the project is archived, and do not delete a namespace that old download URLs still reach. Handing a live name to whoever bids for it is a decision, even when it is made by forgetting to pay an invoice.
- Does pinning an exact version protect a consumer against this?Only partly. An exact version pins the label you request, not the bytes you get. If the new owner controls the repository or hosting behind that path, they can serve content under the same label. What protects you is a recorded hash of the resolved artifact, checked on every fetch, so identical coordinates with different content fail the build.
- You publish a library under a vanity domain and are archiving the project. What do you owe consumers?Keep the name. Renew the domain and keep the namespace claimed for as long as the path can still be fetched, because releasing it hands live traffic to a stranger. Announce the archive, point the path at a final immutable location, and avoid deleting namespaces that old download URLs and old import paths still resolve to.
- What early signal would make you look at a dependency for this?A change in who controls the coordinate: fresh registration data on a domain your build resolves through, a long-dormant project suddenly acquiring a new maintainer or a new release cadence, or hosting that moves to an unfamiliar provider. None is proof, but each is a reason to compare the resolved content against the hash you recorded.
The street address in your address book is unchanged; the house was sold. Mail keeps arriving, and someone else opens it.
saying these in an interview costs you the question
- Says pinning the version makes it impossible
- Believes the import path itself must change
- Assumes registries block namespace re-registration everywhere
- Treats a name-and-version inventory as an integrity control
- Thinks code review would surface the swap