Supply Chain Security: Protecting Your Application From Compromised Dependencies

Philip Rehberger Apr 6, 2026 7 min read

Modern applications rely on hundreds of third-party packages. Learn how to audit, lock, verify, and monitor your dependency supply chain before an attacker exploits it.

Supply Chain Security: Protecting Your Application From Compromised Dependencies

The SolarWinds breach. The XZ Utils backdoor. The event-stream npm incident. Supply chain attacks are no longer theoretical — they are actively targeting the open-source ecosystem your application depends on every day. Understanding how these attacks happen and building defenses into your development workflow is now table stakes for production systems.

How Supply Chain Attacks Work

Attackers compromise software supply chains through several vectors:

Typosquatting: Publishing a package named lodahs to catch developers who mistype lodash. These packages run arbitrary code during install.

Account takeover: Stealing credentials of a legitimate package maintainer, then publishing a malicious update to a trusted package. This is what happened with event-stream in 2018.

Dependency confusion: Exploiting how package managers resolve private versus public packages. An attacker publishes a public package with the same name as your internal private package — at a higher version number — and the package manager picks it up.

Build system compromise: Injecting malicious code into the CI/CD pipeline itself, so even clean source code produces a compromised artifact.

Maintainer abandonment: Acquiring unmaintained popular packages by reaching out to the original author. Many developers transfer packages they no longer use.

Lock Your Dependency Graph

The first line of defense is deterministic builds. Always commit your lock file.

For npm/Node.js:

# Use npm ci (clean install) in CI — it respects package-lock.json exactly
npm ci

# Audit your installed packages
npm audit

# Generate a detailed audit report
npm audit --json > audit-report.json

For PHP/Composer:

# Always commit composer.lock
git add composer.lock

# Use --no-dev in production
composer install --no-dev --optimize-autoloader

# Audit for known vulnerabilities
composer audit

For Python:

# Pin exact versions
pip freeze > requirements.txt

# Use pip-audit for vulnerability scanning
pip install pip-audit
pip-audit -r requirements.txt

The lock file records exact versions and — crucially — integrity hashes for every package. When a package manager verifies a lock file, it checks that the downloaded package matches the expected hash. If an attacker swaps a package on the registry, the hash check fails.

Verify Package Integrity

Go further than lock files by verifying signatures where available.

For npm packages, check if a package is signed:

npm audit signatures

For Docker base images, use digest pinning instead of tags:

# Bad: mutable tag, could change to anything
FROM node:20-alpine

# Good: immutable digest, always the exact same image
FROM node:20-alpine@sha256:a1b2c3d4e5f6...

Generate the digest for your current base image:

docker pull node:20-alpine
docker inspect node:20-alpine --format='{{index .RepoDigests 0}}'

For Go modules, the module proxy and checksum database provide automatic verification:

# GONOSUMCHECK can bypass this — don't use it in production builds
export GONOSUMCHECK=""

# Verify module checksums against the sum database
go mod verify

Automate Vulnerability Scanning

Manual auditing doesn't scale. Integrate vulnerability scanning into your pipeline.

GitHub Dependabot is the lowest-friction option for GitHub-hosted repositories. Add a configuration file:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    groups:
      production-dependencies:
        dependency-type: "production"

  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"

  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"

For more control, integrate Trivy into your CI pipeline:

# .github/workflows/security-scan.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:

jobs:
  trivy-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Scan filesystem for vulnerabilities
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

      - name: Upload results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-results.sarif'

This fails the build on critical or high severity vulnerabilities and uploads results to GitHub's Security tab for review.

Prevent Dependency Confusion Attacks

If your organization uses private packages, configure your package manager to scope them correctly.

For npm with a private registry:

# .npmrc — committed to the repository
@mycompany:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}

# Prevent falling back to public registry for scoped packages
@mycompany:always-auth=true

For Composer with a private Packagist mirror:

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://repo.packagist.com/mycompany/"
        },
        {
            "packagist.org": false
        }
    ]
}

Disabling the public Packagist registry for internal packages ensures your package manager never reaches out to find a public substitute.

Implement a Software Bill of Materials (SBOM)

An SBOM is a machine-readable inventory of every component in your software. It enables rapid response when a new vulnerability is disclosed — you can query your SBOM to know within minutes whether you are affected.

Generate an SBOM with Syft:

# Install Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

# Generate SBOM for a directory
synft dir:. -o spdx-json=sbom.spdx.json

# Generate SBOM for a Docker image
synft image nginx:latest -o cyclonedx-json=sbom.cyclonedx.json

Store your SBOMs as CI artifacts and attach them to releases:

- name: Generate SBOM
  run: syft dir:. -o spdx-json=sbom.spdx.json

- name: Attach SBOM to release
  uses: actions/upload-release-asset@v1
  with:
    asset_path: sbom.spdx.json
    asset_name: sbom.spdx.json
    asset_content_type: application/json

Vet New Dependencies Before Adding Them

The most dangerous moment in supply chain security is when you add a new dependency. Establish a review process:

  1. Check download counts and age: A package with 50 downloads and created last week is riskier than one with 50 million weekly downloads and 8 years of history.
  2. Inspect the source code: Look at the package's repository. Is there active maintenance? Are issues being responded to?
  3. Check maintainer count: Single-maintainer packages have higher risk of abandonment or account takeover.
  4. Review what the package does at install time: Check for preinstall, postinstall, and prepare scripts in package.json. These run during npm install and can execute arbitrary code.
  5. Use socket.dev or similar services: Socket analyzes npm packages for suspicious patterns like obfuscated code, network requests, and filesystem access.
# Check for install scripts before installing
npm info lodash scripts

# Install without running scripts (audit first manually)
npm install --ignore-scripts some-new-package

Monitor for New Vulnerabilities Continuously

Vulnerability scanning at build time is not enough. A package you installed six months ago might have a CVE disclosed today.

Use a software composition analysis (SCA) tool that monitors continuously:

  • Snyk: Integrates with GitHub, monitors repositories 24/7, and opens PRs with fixes automatically.
  • OWASP Dependency-Check: Open-source, runs in CI, integrates with Jenkins and GitHub Actions.
  • Socket.dev: Focuses specifically on npm and PyPI, with real-time monitoring.

For critical applications, subscribe to security advisories for your major dependencies directly. Many frameworks publish security advisories through GitHub's advisory database, which you can watch via the repository notification settings.

Build Provenance Into Your Pipeline

Slsa (Supply-chain Levels for Software Artifacts) defines a framework for proving where your software came from. At SLSA level 2, your CI system generates a signed provenance document attesting that it built the artifact from specific source code.

# Use the SLSA GitHub generator action
jobs:
  build:
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
      - id: build
        run: |
          docker build -t myapp .
          echo "digest=$(docker inspect myapp --format='{{index .RepoDigests 0}}')"

  provenance:
    needs: build
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
    with:
      image: myapp
      digest: ${{ needs.build.outputs.digest }}

This creates a verifiable chain of custody from source code to deployed artifact.

Summary

Supply chain security is not a single tool — it is a set of overlapping practices:

  • Lock files for deterministic, hash-verified installs
  • Digest pinning for Docker base images
  • Automated vulnerability scanning in every CI run
  • Scoped private registries to prevent dependency confusion
  • SBOMs for rapid vulnerability impact assessment
  • Dependency vetting before adding new packages
  • Continuous monitoring for post-release vulnerability disclosures

Building these practices into your development workflow — rather than treating them as one-off audits — is what turns supply chain security from a concern into a solved problem.

Building secure, reliable systems? We help teams deliver software they can trust. scopeforged.com

Share this article

Related Articles

Need help with your project?

Let's discuss how we can help you build reliable software.