Skip to main content
Dublin Library

The Publishing Project

What is a supply chain attack?

 

Supply chain attacks are a growing threat in the software development world, particularly with the rise of open-source package managers like NPM. These attacks exploit the trust developers place in third-party libraries and dependencies, leading to potentially devastating consequences.

This post will break down the lifecycle of a software supply chain attack, explain how it works, and provide strategies to mitigate the risks.

What is a Supply Chain Attack? #

A software supply chain attack occurs when a malicious actor infiltrates the software development process to inject malicious code into a legitimate application. With package managers like NPM, this often involves publishing a compromised package that developers then unknowingly install, triggering a chain reaction that can lead to severe security breaches.

Understanding the lifecycle of this attack is key to building an effective defense.

The Attack Process #

The following diagram illustrates the four common phases of a software supply chain attack, from the attacker's initial move to the final impact.

graph TD
    subgraph "Infiltration Vector"
        A[Attacker] -->|"Creates a new malicious package<br>[Typosquatting]"| B("e.g., 'react-scrips' instead of 'react-scripts'")
        A -->|"Compromises maintainer account<br>[Account Takeover]"| C{Legitimate Package}
        C -->|"Injects malicious code into new version"| D["Compromised Update <br>(e.g., popular-library v2.1.1)"]
    end

    subgraph "Publication & Distribution"
        B --> E(("Public Registry"))
        D --> E
    end

    subgraph "Consumption"
         E -->|"npm install or pip install<br>during development or build"| F[Developer / CI-CD Pipeline]
         F -->|"Malicious dependency is added to"| G[/Project / Application/]
    end

    subgraph "Execution & Impact"
        G --o|"Application is run"| H{Payload Activated}
        H -->|"Steals secrets, keys, env variables"| I[Data Breach]
        H -->|"Installs a backdoor or ransomware"| J[System Compromise]
        H -->|"Uses resources for crypto-mining"| K[Resource Hijacking]
    end

    %% Styling
    style A fill:#ff4d4d,stroke:#333,stroke-width:2px,color:#fff
    style H fill:#ff4d4d,stroke:#333,stroke-width:2px,color:#fff
    style I fill:#ffe5e5,stroke:#333,stroke-width:1px
    style J fill:#ffe5e5,stroke:#333,stroke-width:1px
    style K fill:#ffe5e5,stroke:#333,stroke-width:1px

Breakdown of the Phases & Mitigation Strategies #

Phase 1: Infiltration Vector #

This is the entry point. Attackers use several techniques to get their malicious code into the ecosystem. Each vector requires a specific set of defenses.

  • Typosquatting: The attacker publishes a package with a name that is a common misspelling of a popular one (e.g., expresss instead of express, or react-scrips vs. react-scripts). Developers can easily make a typo during npm install and inadvertently install the malicious version.
    • ➡️ Mitigation:
      • Code & Peer Review: Carefully check package names in package.json during code reviews.
      • Use Scoped Packages: When possible, use and create scoped packages (e.g., @babel/core). It is much harder to typosquat a package that includes an organization's name.
      • Private Registry Allowlisting: A private registry can be configured to only allow installation of explicitly approved packages, making it impossible to download a typosquatted package that isn't on the list.
  • Account Takeover: An attacker gains access to a legitimate package maintainer's NPM or GitHub account, often through phishing, credential stuffing, or leaked credentials. They then have the authority to publish a new, malicious version of that trusted package, which can then be automatically pulled into thousands of projects.
    • ➡️ Mitigation:
      • Enforce MFA: This is the single most effective defense. All maintainers must enable multi-factor authentication (MFA) on their NPM and GitHub accounts.
      • Use Granular Access Tokens: CI/CD pipelines should use publish tokens with limited permissions and short expiration dates, reducing the window of opportunity if a token is compromised.
  • Dependency Confusion: This attack targets organizations that use a mix of private and public packages. An attacker discovers the name of a private, internal package (e.g., internal-company-logger) and publishes a malicious package with the exact same name to the public NPM registry, often with a higher version number. When a developer runs npm install, their build tool may "confuse" the two and pull the higher-versioned public package instead of the intended private one.
    • ➡️ Mitigation:
      • Use Scopes for Internal Packages: The best defense is to use a private NPM scope for all internal packages (@my-company/internal-logger). The public NPM registry prevents others from publishing packages under your registered scope.
      • Configure Registry Precedence: Explicitly configure your package manager (.npmrc file) to always resolve your private scope from your private registry, leaving no ambiguity for the build tool.

Phase 2: Publication & Distribution #

The malicious package is uploaded to a public registry like NPM, where it sits, waiting to be downloaded. It looks like any other package, making it difficult to spot without careful inspection.

➡️ Mitigation: This is where a private registry (using tools like Verdaccio or JFrog Artifactory) is most effective. By creating a curated, allowlisted repository of approved packages, you create a "walled garden." Developers pull from your trusted registry, which prevents them from downloading unvetted packages directly from the public ecosystem.

Phase 3: Consumption #

A developer or a CI/CD pipeline runs npm install. If the project's package.json has a loose version range (^1.2.3), it might pull in a newly published malicious patch version. This is where the malicious code is downloaded and installed into the project's node_modules directory.

➡️ Mitigation:

  • Use and commit lockfiles: Always commit your package-lock.json or yarn.lock file. This ensures that everyone on the team and in the build pipeline installs the exact same version of every dependency, preventing unexpected updates.
  • Vet new packages: Before adding a dependency, check its download stats, GitHub activity, and author reputation.
  • Audit and Patch Dependencies:
    • Audit: Regularly run npm audit or use tools like Snyk or Dependabot to scan for known vulnerabilities in your project.
    • Use Overrides: If an audit reveals a vulnerability in a transitive dependency (a dependency of your dependency), you can't fix it directly. Use the overrides feature in your package.json to force a specific, patched version of the nested dependency. This is a powerful tool to fix vulnerabilities without waiting for the direct maintainer to update their package.

Phase 4: Execution & Impact #

The attack is dormant until the application is executed. Once it runs, the malicious code (the payload) activates. The impact can range from:

  • Data Breach: Stealing API keys, environment variables, or user data.
  • System Compromise: Installing a backdoor for future access or deploying ransomware.
  • Resource Hijacking: Using the server's CPU for cryptocurrency mining.

Are Other Registries Susceptible? #

Yes, absolutely. This is not an NPM-specific problem. Any open-source package management system is a target.

  • PyPI (Python): Has seen widespread typosquatting and dependency confusion attacks.
  • RubyGems (Ruby): Has dealt with hijacked gems and malicious code injections.
  • Docker Hub (Containers): Malicious container images containing malware are a known threat.

The fundamental challenge is the same across all ecosystems: modern software is built on a foundation of third-party code, and every dependency is a potential vector for an attack. A multi-layered defense is the only effective strategy.

Edit on Github