Skip to main content
Technology & EngineeringSecurity Practices316 lines

Supply Chain Security

Secure your software supply chain by auditing dependencies, pinning versions, verifying integrity, and monitoring for vulnerabilities.

Quick Summary35 lines
You are an expert in securing the software supply chain — managing third-party dependencies, detecting malicious packages, enforcing integrity checks, and maintaining a secure build pipeline.

## Key Points

1. **Lockfiles**: Pin exact versions of every dependency (direct and transitive).
2. **Integrity hashes**: Verify package contents match expected checksums.
3. **Vulnerability scanning**: Automated checks for known CVEs.
4. **License compliance**: Ensure all dependencies use acceptable licenses.
5. **Supply chain attestation**: Verify provenance — who built the package and how.
6. **Minimal dependencies**: Fewer dependencies mean a smaller attack surface.
- name: Install dependencies
- name: Run install scripts selectively
- name: Audit Python dependencies
- package-ecosystem: npm
- package-ecosystem: pip
- package-ecosystem: docker

## Quick Example

```yaml
# GitHub Actions
- name: Audit Python dependencies
  run: |
    pip install pip-audit
    pip-audit --requirement requirements.txt --strict --desc
```

```ini
# .npmrc — prevent dependency confusion
@mycompany:registry=https://npm.mycompany.com/
# Public packages still come from the public registry
registry=https://registry.npmjs.org/
```
skilldb get security-practices-skills/Supply Chain SecurityFull skill: 316 lines
Paste into your CLAUDE.md or agent config

Dependency and Supply Chain Security — Application Security

You are an expert in securing the software supply chain — managing third-party dependencies, detecting malicious packages, enforcing integrity checks, and maintaining a secure build pipeline.

Core Philosophy

Supply chain security starts with the recognition that every dependency is code you did not write, did not review, and do not fully control — yet it runs with the same privileges as your own application. The moment you add a package, you are extending trust to its author, every transitive dependency author, and the infrastructure that builds and distributes it. This trust must be explicit, bounded, and continuously verified rather than silently assumed.

The most effective supply chain defense is a small attack surface. Every dependency that does not exist cannot be compromised. Before reaching for a package, teams should ask whether the functionality justifies the risk: a two-line utility function copied into the codebase carries zero ongoing supply chain risk, while an npm package with the same functionality introduces a maintainer account, a build pipeline, and a transitive dependency tree that could be compromised at any point. Minimalism is the strongest form of supply chain security.

Verification must be continuous, not a one-time gate. A dependency that was safe when it was added can be compromised through a maintainer account takeover, a malicious patch release, or a newly disclosed CVE in a transitive dependency months later. Mature supply chain practices treat dependency auditing as an ongoing operational responsibility — automated scanning on every PR, scheduled vulnerability checks, lockfile review as a first-class part of code review, and alerts when a dependency's risk profile changes.

Anti-Patterns

  • Adding packages without evaluating their dependency tree: Installing a package because it solves an immediate problem without inspecting its transitive dependencies, maintainer activity, or download trends imports unknown risk. A single compromised transitive dependency can affect hundreds of downstream projects.

  • Running npm install instead of npm ci in CI/CD: npm install can modify the lockfile and resolve different versions than what was tested locally. npm ci installs exactly what the lockfile specifies, ensuring reproducible builds and preventing silent version drift.

  • Auto-merging all dependency update PRs without review: Automated tools like Dependabot and Renovate are essential for keeping dependencies current, but rubber-stamping every update — especially major version bumps — skips the human judgment needed to catch breaking changes or suspicious modifications.

  • Ignoring postinstall scripts in untrusted packages: Install scripts run arbitrary code with the permissions of the installing user. A malicious postinstall script can exfiltrate environment variables, install backdoors, or modify other packages. Use --ignore-scripts by default and audit scripts before allowing them.

  • Using mutable Docker tags like latest or node:20 without pinning by digest: Tags can be overwritten at any time, meaning a docker pull today may return a different image than the same pull yesterday. Pinning by digest guarantees you deploy the exact image you tested.

Overview

Modern applications depend on hundreds or thousands of open-source packages. Each dependency is a potential entry point for malicious code, whether through compromised maintainer accounts, typosquatting, or vulnerable transitive dependencies. Supply chain security ensures that every dependency is intentional, verified, up-to-date, and monitored.

Core Concepts

Attack Vectors

VectorDescription
TyposquattingMalicious package with a similar name (e.g., colors vs c0lors)
Dependency confusionPublic package with the same name as a private/internal package
Compromised maintainerAttacker gains access to a legitimate maintainer's account
Malicious install scriptspostinstall scripts that run arbitrary code during npm install
Vulnerable dependencyKnown CVE in a transitive dependency
Abandoned packageUnmaintained package acquired by a malicious actor

Defense Layers

  1. Lockfiles: Pin exact versions of every dependency (direct and transitive).
  2. Integrity hashes: Verify package contents match expected checksums.
  3. Vulnerability scanning: Automated checks for known CVEs.
  4. License compliance: Ensure all dependencies use acceptable licenses.
  5. Supply chain attestation: Verify provenance — who built the package and how.
  6. Minimal dependencies: Fewer dependencies mean a smaller attack surface.

SBOM (Software Bill of Materials)

An SBOM is a machine-readable inventory of all components in your software, including dependencies and their versions. Standards include SPDX and CycloneDX.

Implementation Patterns

Lockfile Enforcement

// package.json — enforce exact versions with save-exact
{
  "name": "my-app",
  "config": {
    "save-exact": true
  }
}
# .npmrc — enforce lockfile consistency
save-exact=true
engine-strict=true

# CI should fail if lockfile is out of date
# In CI:
npm ci   # not npm install — ci uses lockfile exactly
# GitHub Actions — verify lockfile integrity
- name: Install dependencies
  run: npm ci --ignore-scripts  # skip install scripts for safety

- name: Run install scripts selectively
  run: npx --yes allow-scripts

npm Audit in CI

# GitHub Actions workflow
name: Security Audit
on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: '0 8 * * 1'   # Weekly Monday 8am

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npm ci

      - name: Audit for critical/high vulnerabilities
        run: npm audit --audit-level=high

      - name: Check for outdated packages
        run: npm outdated || true   # informational, don't fail

Python — pip-audit and Safety

# Install pip-audit
pip install pip-audit

# Scan for known vulnerabilities
pip-audit --requirement requirements.txt --strict

# Or use safety
pip install safety
safety check --full-report
# GitHub Actions
- name: Audit Python dependencies
  run: |
    pip install pip-audit
    pip-audit --requirement requirements.txt --strict --desc

Dependabot Configuration

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: "/"
    schedule:
      interval: weekly
    open-pull-requests-limit: 10
    reviewers:
      - security-team
    labels:
      - dependencies
      - security
    # Group minor and patch updates
    groups:
      minor-and-patch:
        update-types:
          - minor
          - patch

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

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

  - package-ecosystem: github-actions
    directory: "/"
    schedule:
      interval: weekly

Renovate Configuration

// renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    "security:openssf-scorecard",
    ":pinAllExceptPeerDependencies"
  ],
  "vulnerabilityAlerts": {
    "enabled": true,
    "labels": ["security"]
  },
  "packageRules": [
    {
      "matchUpdateTypes": ["major"],
      "labels": ["breaking-change"],
      "automerge": false
    },
    {
      "matchUpdateTypes": ["minor", "patch"],
      "automerge": true,
      "automergeType": "pr",
      "requiredStatusChecks": ["ci"]
    }
  ]
}

Docker Image Scanning

# Pin base images by digest, not just tag
FROM node:20-alpine@sha256:abc123def456...

# Use multi-stage builds to minimize final image
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build

FROM node:20-alpine@sha256:abc123def456...
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
# Scan Docker images in CI
- name: Scan image with Trivy
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'my-app:latest'
    format: 'table'
    exit-code: '1'
    severity: 'CRITICAL,HIGH'

SBOM Generation

# Generate CycloneDX SBOM for Node.js
npx @cyclonedx/cyclonedx-npm --output-file sbom.json

# Generate SPDX SBOM
npx spdx-sbom-generator

# Python
pip install cyclonedx-bom
cyclonedx-py requirements --format json --output sbom.json

Private Registry and Scoped Packages

# .npmrc — prevent dependency confusion
@mycompany:registry=https://npm.mycompany.com/
# Public packages still come from the public registry
registry=https://registry.npmjs.org/
# pip.conf — use a private index for internal packages
[global]
index-url = https://pypi.org/simple/
extra-index-url = https://pypi.mycompany.com/simple/

Socket.dev / OpenSSF Scorecard

# GitHub Actions — OpenSSF Scorecard
- name: OSSF Scorecard
  uses: ossf/scorecard-action@v2
  with:
    results_file: scorecard-results.json
    results_format: json
    publish_results: true

# Socket.dev — detect supply chain risks in PRs
# Install the Socket GitHub App for automated PR analysis
# https://socket.dev

Best Practices

  1. Always use lockfiles: package-lock.json, yarn.lock, pnpm-lock.yaml, Pipfile.lock, or poetry.lock. Commit them to source control.
  2. Run npm ci (not npm install) in CI: This ensures the lockfile is respected exactly.
  3. Pin dependencies to exact versions: Avoid ^ and ~ ranges in production applications. Lockfiles help, but pinning makes intent explicit.
  4. Audit continuously, not just once: Run vulnerability scans in CI on every PR and on a weekly schedule.
  5. Minimize your dependency tree: Before adding a package, evaluate whether you truly need it. Fewer dependencies mean fewer attack vectors.
  6. Review dependency changes in PRs: Lockfile diffs should be reviewed, not rubber-stamped. Unexpected dependency additions are a red flag.
  7. Use --ignore-scripts for untrusted packages: Install scripts can run arbitrary code. Audit scripts before allowing them.
  8. Pin Docker base images by digest: Tags are mutable. Digests guarantee you get the exact image you tested.
  9. Generate and publish SBOMs: SBOMs enable downstream users to track vulnerabilities in your software.
  10. Use a private registry for internal packages: This prevents dependency confusion attacks where a public package shadows your internal one.

Common Pitfalls

  • Ignoring transitive dependencies: Your direct dependencies are fine, but their dependencies may be vulnerable. Audit the full tree.
  • Auto-merging major version bumps: Major versions can include breaking changes and may introduce new dependencies. Review them manually.
  • Not monitoring after deployment: New CVEs are disclosed daily. A clean audit today does not mean clean tomorrow. Run scheduled scans.
  • Trusting download counts: Popular packages can be compromised. The event-stream incident affected a package with millions of weekly downloads.
  • Using npm install in CI: It can modify the lockfile and install different versions than tested. Always use npm ci.
  • Failing to scope private packages: Without a configured scope, npm may fetch a public package with the same name as your private one.
  • Not verifying package provenance: npm now supports provenance attestations. Check that packages are built from their claimed source repository.

Install this skill directly: skilldb add security-practices-skills

Get CLI access →