Supply Chain Security

Modern software is built on a foundation of open-source components. A typical web application depends on hundreds or even thousands of third-party packages, each of which depends on its own set of dependencies. This chain of trust — your software supply chain — is a significant attack surface. If any single package in the chain is compromised, every application that depends on it is affected. Supply chain attacks have moved from theoretical concern to front-page news, with high-profile incidents demonstrating just how devastating they can be.

The scope of the problem

Consider a fresh create-react-app project. Running npm install pulls in over 1,400 packages. Your application's package.json might list 20 direct dependencies, but each of those depends on more packages, which depend on yet more packages. These indirect packages are called transitive dependencies, and they make up the vast majority of your dependency tree.

You vet the packages you choose directly. But who vets the packages they depend on? A vulnerability or malicious code three or four levels deep in your dependency tree is just as dangerous as one in your own code — but far harder to notice.

npm audit

npm audit is built into the npm package manager and checks your installed packages against the npm advisory database. It reports known vulnerabilities with their severity (low, moderate, high, critical), affected versions, and available fixes.

Usage

# Check for known vulnerabilities
npm audit

# Automatically fix what can be fixed
npm audit fix

# View a detailed JSON report
npm audit --json

# Only report high and critical vulnerabilities
npm audit --audit-level=high

Limitations

  • Only covers packages in the npm registry. If you use packages from other sources, they are not covered.
  • Automated fixes (npm audit fix) may introduce breaking changes. Always test after running it.
  • Cannot detect malicious code that has not yet been reported as a vulnerability.
  • The --force flag can install major version updates, which frequently include breaking API changes.

OSV.dev

The Open Source Vulnerability database (OSV.dev) is a distributed, open, and multi-ecosystem vulnerability database. Unlike registry-specific tools, OSV aggregates vulnerability data from multiple sources including the National Vulnerability Database (NVD), GitHub Security Advisories, PyPI, RubyGems, Go, Rust, and many more.

Key features

  • Multi-ecosystem: Covers npm, PyPI, crates.io, Go modules, Maven, NuGet, Packagist, RubyGems, and more.
  • Open data format: Uses the OSV Schema, an open standard for describing vulnerabilities.
  • API access: Programmatic access for integration into CI/CD pipelines and scanning tools.
  • osv-scanner: A command-line tool that scans your project's lockfiles and SBOMs against the OSV database.

Usage

# Install osv-scanner
go install github.com/google/osv-scanner/cmd/osv-scanner@latest

# Scan a project directory (reads lockfiles automatically)
osv-scanner --lockfile=package-lock.json

# Scan an SBOM
osv-scanner --sbom=sbom.json
Tip: CodeFrog uses OSV to scan for supply chain vulnerabilities. When you run a mega report, CodeFrog analyzes your project's dependencies against the OSV database, reporting known vulnerabilities with their severity levels and affected versions. This gives you visibility into supply chain risks without needing to configure separate tooling.

Dependabot

GitHub's Dependabot automatically monitors your repository's dependencies and creates pull requests to update vulnerable packages. It integrates directly into the GitHub workflow, making it easy to review and merge security updates.

How it works

  • Scans your dependency files (package.json, Gemfile, requirements.txt, etc.) daily.
  • When a vulnerability is disclosed, Dependabot opens a pull request with the minimum version update needed to resolve it.
  • Each PR includes release notes, changelog entries, and commit information so you can assess the risk of updating.
  • Can be configured to auto-merge low-risk updates (patch versions) to reduce manual review burden.

Configuration

Add a .github/dependabot.yml file to your repository:

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 10

Snyk

Snyk is a commercial security platform that provides comprehensive supply chain security. It goes beyond simple vulnerability scanning by offering fix suggestions, prioritization based on exploitability, and continuous monitoring.

Key features

  • Fix pull requests: Automatically creates PRs with the minimum changes needed to resolve vulnerabilities.
  • Reachability analysis: Determines whether your code actually calls the vulnerable function, helping you prioritize what to fix.
  • License compliance: Scans your dependencies for license conflicts (GPL, AGPL, etc.).
  • Container scanning: Scans Docker images for OS-level vulnerabilities.

Lock files

Lock files (package-lock.json, yarn.lock, pnpm-lock.yaml) are a critical security mechanism. They record the exact version of every direct and transitive dependency installed in your project, ensuring that every developer and every CI/CD run uses identical versions.

Why lock files matter for security

  • Reproducible builds: Without a lock file, running npm install on two different machines at two different times could resolve to different package versions, potentially introducing a compromised version.
  • Pinned versions: The lock file pins exact versions of all transitive dependencies, preventing unexpected updates.
  • Integrity hashes: Lock files include integrity hashes (SHA-512) for every package, ensuring that the downloaded package matches what was originally resolved.
Warning: Always commit your lock file to version control. Never add it to .gitignore. Without a lock file, your CI/CD pipeline may resolve different dependency versions than your local environment, introducing hard-to-debug inconsistencies or security vulnerabilities.

SBOM (Software Bill of Materials)

An SBOM is a formal, machine-readable inventory of all software components and dependencies in a project. Think of it like a nutritional label for software — it tells you exactly what ingredients are in the product. SBOMs have become a regulatory requirement in many industries, particularly after the 2021 Executive Order on Improving the Nation's Cybersecurity.

What an SBOM contains

  • Component name, version, and supplier for every dependency.
  • Dependency relationships (which component depends on which).
  • License information for each component.
  • Unique identifiers (CPE, PURL) for precise vulnerability matching.

SBOM formats

  • CycloneDX: Lightweight, purpose-built for security use cases. Supported by OWASP.
  • SPDX: ISO standard (ISO/IEC 5962:2021) originally designed for license compliance but now widely used for vulnerability tracking.

Generating an SBOM

# Using CycloneDX for npm projects
npx @cyclonedx/cyclonedx-npm --output-file sbom.json

# Using syft for container images
syft your-image:latest -o cyclonedx-json > sbom.json

Transitive dependencies and the risks they introduce

Your direct dependencies are the packages you explicitly choose and install. Transitive dependencies are the packages those packages depend on, and so on down the chain. In a typical JavaScript project, transitive dependencies outnumber direct dependencies by a ratio of 10:1 or more.

The risks

  • Invisible attack surface: You may have no idea that your application ultimately depends on a package maintained by a single developer in their spare time.
  • Version drift: Without lock files, transitive dependencies can silently update to compromised versions.
  • Leftpad problem: A single package being removed or compromised can break or compromise thousands of projects.
  • Typosquatting: Attackers publish packages with names similar to popular packages (e.g., lodahs instead of lodash) to trick developers into installing malicious code.

Real supply chain attacks

The event-stream incident (2018)

The event-stream package was one of the most popular npm packages, downloaded over two million times per week. Its original maintainer, who had lost interest in the project, transferred ownership to a new contributor who had been submitting helpful pull requests. This new maintainer added a transitive dependency called flatmap-stream that contained obfuscated malicious code. The malicious code targeted the Copay Bitcoin wallet application specifically, stealing cryptocurrency wallet credentials. The attack was sophisticated — the malicious payload was encrypted and only activated when the Copay application's build process was detected.

Lessons learned: Maintainer trust is fragile. Packages with new maintainers should be scrutinized. Dependency auditing tools would have flagged the new dependency. Lockfiles would have prevented automatic adoption of the compromised version.

The ua-parser-js incident (2021)

The ua-parser-js package, used to parse browser user-agent strings, was compromised when an attacker gained access to the maintainer's npm account. Malicious versions were published that installed a cryptocurrency miner and a credential-stealing trojan on developer machines. The package had over 7 million weekly downloads and was used by major companies including Facebook, Amazon, Microsoft, Google, and Slack. The malicious versions (0.7.29, 0.8.0, 1.0.0) were live for approximately four hours before being detected and removed.

Lessons learned: Even a few hours of exposure can affect millions of installations. npm account security (2FA) is critical. Automated alerts for unexpected dependency changes are essential. Lock files would have prevented most installations of the compromised version.

The colors and faker incident (2022)

The maintainer of the popular colors and faker packages deliberately sabotaged them to protest the use of open-source software by large corporations without financial support. The colors package (over 20 million weekly downloads) was updated to print gibberish and an infinite loop. The faker package (over 2.5 million weekly downloads) was emptied entirely. This affected thousands of projects including AWS CDK.

Lessons learned: Dependency on individual maintainers is a systemic risk. Organizations should have processes to evaluate the sustainability and governance of their critical dependencies. Lock files prevented automatic adoption of the sabotaged versions for projects that had them in place.

Best practices

  • Always commit lock files to version control to ensure reproducible builds.
  • Run dependency audits in CI/CD: Add npm audit or osv-scanner to your pipeline and fail builds on high/critical vulnerabilities.
  • Enable Dependabot or similar automated update tools for your repositories.
  • Generate and maintain SBOMs for your projects, especially for production deployments.
  • Minimize dependencies: Evaluate whether you truly need each dependency. Can you write 10 lines of code instead of adding a package?
  • Review dependency changes: When a dependency updates, read the changelog and review the diff before merging.
  • Use Subresource Integrity (SRI) for any externally loaded scripts or stylesheets.
  • Monitor for new vulnerabilities continuously — not just at build time.

Resources