What Is Dependency Confusion?
Dependency confusion is often mistaken for a general malicious-package problem, but the defining issue is more specific. The failure occurs when a build system asks for a software dependency by name and retrieves an attacker-controlled package from a public repository instead of the organization’s intended internal package. That distinction changes the defensive question. The problem is not only whether a package contains harmful code, but also whether the organization can prove which repository is authorized to supply that package name. A build may appear normal because the dependency tool is doing exactly what it was configured to do. By the end of this episode, you should be able to explain how package names, repository selection, and automated dependency resolution interact, why common controls sometimes provide false confidence, and what design decisions can prevent an internal package from being silently replaced during a build. A dependency is software that another application, library, or build process relies on. A package is a distributable unit of code and metadata that a dependency tool can retrieve and install. An internal package is intended for use inside an organization and is normally stored in a private repository, while a public repository is available to a much wider community. Dependency confusion happens when the same package name can be found in more than one place and the resolver chooses the public version instead of the private one. The resolver may be a package manager, a build tool, or another automated component that decides where to search and which version to accept. The main distinction to remember is that the build is confused about package identity and source authority. The name matches, but the selected package does not come from the repository the organization intended to trust. To understand the failure, start with normal dependency resolution. A project declares that it needs a package, often by recording a name and an acceptable version range in a manifest or configuration file. The dependency tool then searches one or more configured repositories, compares the available versions, and retrieves a package that satisfies its rules. Those rules vary by ecosystem and configuration. Some tools search sources in a defined order, while others may compare candidates from several sources or prefer a higher compatible version. Automation makes this process fast and repeatable, but it also means the tool can make a dangerous choice without a person reviewing each download. If private and public sources are both available, the resolver needs an unambiguous rule that says which source is authoritative for each package. Without that rule, a familiar name can create the appearance of trust even when the source has changed. Package names sit at the center of dependency confusion because a name is often treated as an identity even when it is not globally unique. An organization may create an internal package with a simple name that happens to be unused in a public repository. Developers know what that name means inside the organization, but a public package service does not automatically know that the name is reserved for private use. If someone later publishes a different package under the same public name, the build environment may see two candidates that appear to satisfy the request. Namespaces and scoped package names can reduce this ambiguity by associating packages with an owner or organizational scope, but they only help when the package ecosystem supports them and the build is configured to enforce them. A private naming convention is useful for people, yet it is not a security boundary unless the resolver can verify and restrict the authorized source. Public repositories add reach and convenience, while private repositories support organization-specific code, controlled distribution, and internal development. Dependency confusion appears when a build can consult both without a reliable boundary between them. The public service is not necessarily compromised, and the private repository may be operating normally. The weakness lies in the decision path that allows a public result to compete with an internal one. Automated tools can intensify that weakness because they may run in developer workstations, build servers, containers, and deployment pipelines with network access and installation privileges. A single configuration may also be copied across many projects, causing the same resolution behavior to repeat at scale. The combined problem is therefore larger than one package. It is a trust-design issue involving repository configuration, package identity, network access, and the assumptions built into automated software delivery. Version selection can make the confusion harder to recognize. A project may request a range of acceptable versions instead of one exact release, and a resolver may prefer the highest version that fits its rules. If the private repository contains an internal package with a modest version number while a public package with the same name advertises a higher compatible number, some configurations may select the public candidate. This behavior is not universal, so defenders should verify the actual rules used by each package manager and repository client rather than relying on a general assumption. Source order can create a similar problem when a tool checks the public repository before the private one or falls back to public sources after a private lookup fails. The practical lesson is that version rules and source rules must work together. A precise version does not prove that the package came from the correct place, and a trusted repository does not help if the resolver is free to choose another source. The consequences can extend beyond the initial build because dependencies often run with meaningful access. Installation scripts may execute during package retrieval, libraries may become part of the finished application, and build agents may hold credentials, source code, signing material, or access to internal services. A malicious package therefore does not need to behave like a visible standalone program to create harm. It can affect the build process, alter produced artifacts, expose secrets, or introduce code that is carried into later environments. Transitive dependencies deepen the issue because a project may depend on one package that depends on several others. The development team may never have selected every package directly, yet the build still trusts the resolver to obtain them. Dependency confusion is consequently a software supply chain problem, but its immediate cause remains the mistaken selection of a package source for a name the organization believed was internal. Before we continue, this is a brief promotional message. This episode is brought to you by the Bare Metal Cyber Academy. The Academy is a place for people who want to continue developing practical cybersecurity knowledge through clear, structured education. Topics such as software supply chain risk become easier to manage when teams understand both the technical mechanism and the decisions that allow it to occur. You can visit Bare Metal Cyber dot com to explore the Academy and see the learning opportunities currently available. The focus is practical understanding that can support better questions, clearer communication, and more deliberate security choices. Now, let’s return to dependency confusion and the controls that keep package resolution tied to an authorized source. Dependency confusion is related to several other package attacks, but it should not be used as a label for all of them. Typosquatting relies on a misleading name that resembles the intended package, hoping someone requests the wrong one. Dependency confusion normally uses the exact name expected by the build, while exploiting uncertainty about where that name should be resolved. A compromised public repository involves unauthorized control over the repository or an existing package account, which is different from publishing a separate package under an available name. A malicious update to a legitimate dependency also differs because the package identity and source may be correct even though a new version is harmful. These distinctions affect the response. Spelling reviews may help with typosquatting, account protection may reduce package takeover, and update review may catch suspicious releases, but dependency confusion requires controls that bind an internal package name to an authorized private source. Detection begins by examining what the build actually retrieved rather than assuming that the declared name tells the whole story. Useful evidence can include repository access logs, package-manager output, build records, resolved source locations, package hashes, and changes in dependency lock files. Defenders should look for internal package names being requested from public services, unexpected first-time downloads, unfamiliar versions, and source changes that do not match an approved configuration change. Network monitoring can also reveal build agents contacting public package services when the project should rely only on an internal repository or approved proxy. A Software Bill of Materials (S B O M) can support investigation by showing which components were included in an artifact, although it is only as reliable as the process that generated and verified it. Detection is strongest when package identity, version, source, and integrity are recorded together, allowing the team to distinguish an authorized update from an unexpected substitution. Prevention should begin with repository architecture, not with user vigilance. Build systems should have a clear source of truth for internal packages, and private names should not be allowed to resolve freely from public repositories. An internal repository manager or controlled proxy can centralize approved package retrieval, enforce source policy, and provide consistent logging. Where the ecosystem supports namespaces or organizational scopes, teams should use them and protect ownership of those scopes. Public network access from build environments should also be limited to what the build genuinely requires. Egress filtering can prevent a private-only build from contacting an unapproved public package service, while allowlists can restrict retrieval to approved repositories. These measures work best together because no single control answers every question. The objective is to make the authorized source explicit, technically enforced, and observable whenever a dependency is resolved. Version pinning, lock files, hashes, and signatures are valuable, but each control has limits. Pinning narrows which version the tool may accept, yet it does not automatically prove that the chosen package came from the private repository. A lock file can preserve a previously resolved dependency graph and sometimes record integrity information, but it must be created in a trusted process, reviewed when it changes, and honored by the build. Cryptographic hashes can show that a retrieved package matches an expected artifact, provided the expected hash was obtained securely. Package signatures or provenance information can provide stronger evidence about origin when the ecosystem and organization support verification. None of these controls should be treated as decorative metadata. They reduce risk only when the build fails closed after a mismatch, an unknown signer, an unexpected source, or an unapproved change instead of quietly continuing with whatever package remains available. Governance matters because dependency behavior is shaped by many small decisions across development and operations. Teams need an inventory of internal package names, their owners, approved repositories, and the projects that depend on them. Package creation should include a deliberate naming and publication policy rather than leaving every project to invent its own convention. Build templates should carry secure repository settings so that new pipelines do not recreate the same ambiguity. Changes to dependency sources, lock files, and package versions should receive review appropriate to the sensitivity of the application and the privileges of the build environment. Secrets used by build agents should be limited in scope and duration because a compromised dependency can misuse whatever access the process already has. Clear ownership also improves response. When an unexpected package appears, the organization should know which development, platform, and security teams are responsible for validating the source and containing the affected builds. A practical way to test for dependency confusion is to follow one internal package name through the complete resolution path. Confirm where the name is declared, which repositories the tool can search, what order or preference rules it applies, which versions it will accept, and what evidence proves the final source. Then check whether the same name exists or could be claimed in a public repository and whether the build would contact that service. Review the lock file, expected hash, signature or provenance information, and build logs to see whether a source change would be visible and whether the build would stop. Repeat the test for direct and transitive dependencies, especially in privileged build environments. The purpose is not to ask whether the private repository exists. The useful question is whether every automated path is forced to retrieve the intended package from the authorized location and reject all competing candidates. Dependency confusion is a package-resolution failure in which an automated build retrieves an attacker-controlled public package instead of the internal package the organization intended. It becomes possible when package names are ambiguous across repositories, resolver rules allow public and private candidates to compete, and the build treats a matching name or version as sufficient proof of identity. The strongest response is to bind internal names to controlled sources, restrict repository access, verify version and integrity information, and record enough evidence to detect any change in origin. Namespaces, lock files, hashes, signatures, controlled proxies, and egress restrictions can all contribute, but they must be enforced as parts of one trust decision. A secure build should not merely locate a package that satisfies the request. It should be able to prove that the package came from the repository authorized to supply that dependency and stop when that proof is missing.
