Skip to content
Fast-turnaround security assessments available — 10+ years development & security experienceGet started
methodologyTypical severity: Critical

Software Supply Chain Security: Dependencies as Attack Vectors

·10 min read

Software Supply Chain Security: Dependencies as Attack Vectors

A typical production application does not run code written exclusively by the team that built it. It runs code from dozens of open-source packages, each of which depends on more packages, which depend on more still. Add build tools, test frameworks, CI/CD runners, container base images, and infrastructure-as-code modules, and the set of third-party code executing in a production environment is often an order of magnitude larger than the first-party codebase.

Every component in that dependency chain is a potential attack vector. Software supply chain attacks exploit the implicit trust that application builders place in the packages, registries, and tooling they depend on. The attacker does not need to breach the target organization directly — they need to compromise something the organization already trusts.

The Structure of the Attack Surface

Understanding software supply chain risks requires mapping the chain itself. For a typical web application, that chain includes:

Direct dependencies. The packages explicitly listed in the project's manifest file. These are the ones developers think about and intentionally add. A project might have twenty or thirty direct dependencies.

Transitive dependencies. The dependencies of the direct dependencies, and their dependencies, recursively. For most applications, the transitive tree is three to ten times larger than the direct dependency list. Many of these packages are small, single-purpose utilities that were not evaluated for trustworthiness when they were pulled in — they arrived as a consequence of adding something else.

Build and development tools. Package managers, compilers, linters, test frameworks, code generators, and bundlers. These tools run locally and in CI/CD pipelines with access to source code and frequently to credentials and secrets. A compromised build tool is as dangerous as a compromised runtime library.

CI/CD infrastructure. The systems that fetch dependencies, run builds, execute tests, sign artifacts, and deploy to production. These systems often have the broadest access of any component in the development workflow — credentials, cloud access, artifact storage, deployment keys.

Container base images. The operating system layers and runtime environments that application containers are built on top of. A malicious or outdated base image introduces vulnerabilities before a single line of application code runs.

Each category has distinct attack patterns.

Dependency Confusion

Dependency confusion is an attack that exploits the behavior of package managers when they are configured to resolve packages from multiple registries — typically a public registry and a private internal one.

Organizations frequently use private package registries to host internal libraries that are not published publicly. These internal packages are referenced by name in dependency manifests, just like public packages. If the package manager checks the public registry first, or checks both and selects the higher version number, an attacker can exploit this by publishing to the public registry under the same package name.

The attack sequence:

  1. The attacker discovers internal package names by finding them in publicly accessible dependency manifests — package.json files, requirements.txt, Gemfile — committed to public repositories, exposed in job listings, or visible in error messages and documentation.
  2. The attacker publishes a package to the public registry with the discovered internal name, setting the version number higher than any plausible internal version.
  3. When a developer or CI/CD system runs a dependency install, the package manager resolves the public version as the highest available and fetches it.
  4. The malicious package executes as part of the normal build or install process.

The attack was definitively demonstrated by a researcher who published packages using names harvested from internal manifest files of dozens of large technology organizations. In each case, the package was benign — it only reported back that the resolution had succeeded — but the point was made: every organization whose internal package names reached the public internet was potentially vulnerable.

Mitigation requires explicit scoping: configuring the package manager to only resolve internal package names from the internal registry, never from public sources. For npm, this means scoping all internal packages under a namespace and configuring the registry mapping accordingly. For Python, it means using --index-url rather than --extra-index-url for internal packages. The subtlety matters: the configuration setting that adds a second registry as a fallback is different from the one that makes it authoritative.

Typosquatting on Package Registries

Package registries are large enough that most plausible names are unregistered. An attacker who registers a package with a name one character different from a popular library occupies a real namespace on the registry with effectively zero barrier.

Common typosquatting patterns include:

  • Transposition. reqeusts instead of requests, lodash with one character swapped.
  • Hyphen or underscore substitution. python-dateutil versus python_dateutil, cross-env versus crossenv.
  • Common misspellings. Packages with names that match frequent autocomplete errors or regional spelling variations.
  • Homoglyph substitution. Characters that are visually identical or nearly so in most fonts, such as l and 1, O and 0.
  • Extra or missing words. color versus colors, lodash versus lodash-utils.

Malicious typosquatting packages are typically designed to function identically to the package they impersonate — exporting the same interface, producing the same results — while also executing a payload. The payload might run at install time (using package lifecycle scripts like postinstall) rather than at import time, meaning the malicious code runs during npm install even if the package is never actually imported in the application code.

Several malicious packages have remained on major registries for months or years before removal, and the payload variants are increasingly evasive — running only in CI/CD environments (by detecting environment variables), only on specific operating systems, or after a time delay.

Compromised Maintainer Accounts

An attacker who gains control of a legitimate maintainer's account can publish a new version of a trusted, established package. This version reaches every project that accepts automatic updates and every build system that pulls the latest version on each run.

Maintainer account compromise vectors include:

Credential stuffing. Maintainers who reuse passwords from breached services are vulnerable to automated credential stuffing. If the registry account uses the same password as an account exposed in a data breach, that password is likely in circulation.

Phishing. Targeted phishing of maintainers of high-value packages is increasingly documented. The attack does not require breaching registry infrastructure — it only requires the maintainer to authenticate to a fake registry login page.

Account takeover through email compromise. Password reset flows for registry accounts typically rely on email. Compromising the maintainer's email account enables password resets on their registry account.

Purchasing abandoned accounts. Packages with large download counts sometimes become unmaintained when their original authors move on. Attackers have acquired ownership of such packages through legitimate mechanisms — offering to take over maintenance — and then published malicious versions.

The defense at the individual level is strong authentication (hardware security keys rather than SMS or authenticator app codes) on both the registry account and the associated email account, combined with publishing using automated signing that makes any unsigned version detectable. At the organizational level, the defense is pinning dependencies to specific versions with hash verification rather than accepting automatic updates.

Build and CI/CD Pipeline Compromise

The build pipeline is a privileged environment. It has access to source code, build secrets, signing certificates, artifact storage, deployment credentials, and often production infrastructure. Compromise of the pipeline means compromise of every artifact it produces.

Attack paths into CI/CD pipelines include:

Malicious dependencies with install-time scripts. Package lifecycle hooks (postinstall, prepare) run during dependency installation in the build environment. A malicious package with an install-time script can exfiltrate environment variables — including cloud credentials, API keys, and signing material — directly from the CI/CD runner.

Poisoned pipeline execution (PPE). If a CI/CD system builds pull requests from external contributors without restricting which pipeline definition file is used, a malicious pull request can modify the pipeline definition to inject malicious steps. The build system executes the modified pipeline, which runs with full CI access.

Compromised build action or plugin. CI/CD systems typically use a marketplace of reusable actions or plugins. A malicious or compromised action in the pipeline configuration runs with whatever access the pipeline grants. Actions with broad access to secrets are high-value targets for attackers who can compromise the action's source repository.

Build artifact tampering. An attacker with write access to artifact storage can replace built artifacts with modified versions after the legitimate build completes but before deployment. If the deployment process does not verify artifact integrity, the modified artifact reaches production.

Assessing build pipeline security requires reviewing what secrets are accessible in each pipeline stage, what external actions or plugins are used and whether they are pinned to specific versions, whether the pipeline configuration itself is protected from modification by untrusted contributors, and whether artifacts are signed and their signatures verified at deployment time.

Transitive Dependency Risk

The transitive dependency tree is where most supply chain risk lives. A project that directly depends on twenty packages may have two hundred transitive dependencies. The developers who chose those twenty packages evaluated them to varying degrees. Nobody evaluated the hundred and eighty they didn't choose — they arrived automatically.

The average npm package has more than fifty transitive dependencies. Many of those dependencies are small packages maintained by individuals with no organizational security program. A package that formats dates, pluralizes words, or pads strings may have fewer than ten lines of code and one maintainer who last published to it three years ago.

Reducing transitive dependency risk involves several approaches:

Software composition analysis. Automated tools that enumerate the full dependency tree and flag packages against known vulnerability databases, license conflicts, and package health signals (unmaintained, recently transferred ownership, new maintainers).

Dependency tree auditing. Understanding which transitive dependency provides which functionality and whether that functionality is actually used. Unused dependencies pulled in by a larger package can sometimes be excluded.

Minimal dependencies. The least-risk dependency is the one you don't have. For small, well-understood functionality (string padding, date formatting), implementing the functionality inline eliminates the dependency entirely.

Lockfile enforcement. Generating a lockfile that records specific versions and hashes for all resolved packages, and enforcing that only lockfile-recorded versions are installed in CI/CD and production build environments, prevents version substitution during builds.

Assessing Supply Chain Security

A supply chain security assessment covers the full chain from development environment to deployed artifact:

Dependency manifest review. Enumerate all direct and transitive dependencies. Flag packages that are unmaintained, recently transferred, have unusually broad install-time scripts, or have names that match typosquatting patterns against popular packages.

Registry configuration. Review how the package manager is configured when multiple registries are in use. Confirm that internal package names are explicitly scoped to internal registries and cannot be resolved from public sources.

Build pipeline review. Review CI/CD configuration for untrusted input to pipeline definitions, access to secrets in pull request builds, use of unpinned external actions, and artifact storage access controls.

Secrets in build environments. Confirm that secrets available to CI/CD runners are scoped to the minimum required and rotated if they have been accessible to any build step that fetched external dependencies.

Artifact integrity. Confirm that build artifacts are signed and that signatures are verified before deployment. Identify any gaps in the chain of custody between build completion and production deployment.

Dependency monitoring. Review whether the organization has an ongoing process for detecting newly published advisories, ownership changes, or anomalous releases for packages in the dependency tree.

The full picture of supply chain risk requires treating dependencies as untrusted third-party code — because that is what they are — rather than as inert libraries with a known-good status that persists indefinitely.

For the specific vulnerability patterns that affect web application interfaces to third-party services, see the API rate limiting bypass article and the OAuth vulnerabilities article.

Concerned about supply chain exposure in your build pipeline or dependency tree? Get in touch.

Need your application tested?

We find these vulnerabilities in real applications every day. Get a comprehensive security assessment with detailed remediation.

Request an Assessment

Summary

Modern applications depend on hundreds of third-party packages, build tools, and external services. Each one is a potential entry point. Software supply chain attacks exploit this dependency chain — targeting package registries, open-source maintainers, build pipelines, and update mechanisms — to distribute malicious code inside software that organizations trust by default. Understanding the attack surface is the first step to assessing it.

Key Takeaways

  • 1Dependency confusion attacks exploit the way package managers resolve names — by publishing a public package with the same name as a private internal package but a higher version number, an attacker can cause builds to fetch the malicious public version instead
  • 2Typosquatting targets developers who mistype package names; malicious packages with names one character off from popular libraries are regularly discovered on major registries
  • 3Compromising a single popular open-source maintainer account can distribute malicious code to millions of downstream applications through legitimate update mechanisms
  • 4Build and CI/CD pipeline compromise is increasingly targeted — injecting malicious steps into a build process can exfiltrate secrets, modify build artifacts, or plant backdoors before code reaches production
  • 5Transitive dependencies are the largest attack surface — most applications have more indirect dependencies than direct ones, and few organizations have full visibility into their complete dependency tree

Frequently Asked Questions

A dependency confusion attack, also called a namespace confusion attack, exploits how package managers resolve package names when both private and public registries are configured. If a build system is configured to check a public registry alongside a private one, and an attacker publishes a package to the public registry with the same name as an internal private package but a higher version number, some package managers will fetch the public (malicious) version instead of the intended private one. The attack was publicly documented through research that demonstrated it against dozens of major technology companies by publishing packages with names harvested from dependency manifests found in public code repositories.

Typosquatting on package registries involves publishing packages with names that closely resemble widely-used legitimate packages — one character transposed, a hyphen added or removed, a common misspelling. Developers who make a typing error when adding a new dependency install the malicious package instead. The malicious package may function normally (to avoid immediate detection) while also executing a payload: exfiltrating environment variables, writing a persistent backdoor, or making network connections to attacker infrastructure. Detection is difficult because the package appears to work and the install log shows only the version that was resolved.

Open-source packages are maintained by individuals who publish updates through registry accounts. If an attacker gains access to a maintainer's account — through credential stuffing, phishing, or purchasing abandoned accounts from former maintainers — they can publish a new version of a legitimate, trusted package containing malicious code. Downstream applications that accept automatic minor or patch updates will receive the malicious version during their next build. The attack leverages the trust that has been established by the legitimate package's history and reputation. Several high-profile incidents have involved accounts that were inactive for years being repurchased and used to push malicious versions.

Build pipeline compromise targets the infrastructure that assembles, tests, and packages software rather than individual dependencies. Attackers with access to a CI/CD system can modify build scripts to add malicious steps, inject code into compiled artifacts after compilation, exfiltrate signing keys or deployment credentials, or alter test results to mask compromised code. Because build systems typically have elevated access — to source code, secrets, registries, and deployment targets — compromise of the pipeline itself can be more impactful than compromise of any single dependency. The 2020 build system compromise that affected a network management software vendor illustrated how a single build infrastructure compromise can propagate to thousands of downstream organizations.

A lockfile records the exact resolved versions and cryptographic hashes of all direct and transitive dependencies at the time a dependency resolution was performed. Installing from a lockfile with hash verification prevents version substitution attacks — the package manager will reject any package that does not match the recorded hash. Lockfiles significantly reduce the risk of dependency confusion and version substitution, but they do not prevent attacks that occur before the lockfile is generated (when malicious code is already present in the version that was resolved), nor do they protect against build pipeline compromise that occurs after packages are fetched.