How to Harden Your Build Pipeline Against npm, PyPI, and RubyGems Supply Chain Attacks

Attackers are actively compromising maintainer accounts, publishing malicious versions, and using worms to spread credential theft, remote access tools, and CI pipeline backdoors across npm, Bun, RubyGems, PyPI, and other ecosystems. This is happening now.

The good news: you can dramatically cut your exposure with simple, low-effort changes. No new tools, no complex overhauls, no downtime. These steps focus on locking, pinning, maturity delays, and integrity checks.

Core Principles (Apply Everywhere)

  • Always commit lockfiles and enforce them in CI/CD.
  • Pin direct dependencies to exact versions.
  • Add a short maturity/cooldown period — this blocks fresh malicious releases.
  • Use cryptographic hashes and immutable references (commit SHAs).
  • Prefer strict install commands that fail fast on mismatch.

JavaScript / TypeScript

npm / pnpm / Yarn

  • Commit package-lock.json, pnpm-lock.yaml, or yarn.lock.
  • In CI: use npm ci, pnpm install –frozen-lockfile, or yarn install –frozen-lockfile.
  • Pin versions in package.json:

"axios": "1.7.2" //exact, not "^1.7.0"

  • Minimum release age (npm ≥ 11.10) — add to .npmrc:

min-release-age=7

  • For pnpm: minimumReleaseAge=10080 (minutes) in pnpm-workspace.yaml
  • For Yarn (4.10+): add to .yarnrc.yml:

npmMinimalAgeGate: 7d

Yarn accepts duration strings (7d, 1w, 168h are all equivalent). You can also set it via yarn config set npmMinimalAgeGate 7d.

Bun

  • Commit bun.lock.
  • In CI: bun install –frozen-lockfile.
  • Pin exact versions in package.json.
  • Git sources: use full commit SHA (#abc123def456…).
  • Minimum release age (highly recommended) — add to bunfig.toml:

[install]
minimumReleaseAge = 604800 # 7 days in seconds

Python

uv

  • Use uv lock → commit uv.lock (includes hashes).
  • In CI: uv sync or uv pip install -r requirements.txt.
  • Maturity filter — add to pyproject.toml or uv.toml:

[tool.uv]
exclude-newer = "7 days"

  • Or: uv lock –exclude-newer “7 days”.

Classic pip

Use pip-tools:

pip-compile --generate-hashes requirements.in -o requirements.txt

Install with:

pip install --require-hashes -r requirements.txt

Ruby (RubyGems + Bundler)

  • Commit Gemfile.lock.
  • In CI/production: bundle install –deployment.
  • Pin tightly in Gemfile:

gem 'rails', '7.1.3.4' # exact
gem 'devise', '~> 4.9.0' # patch-level only

  • Checksum verification — run once:

bundle lock --add-checksums

  • Git sources: use full commit SHA:

gem 'my-gem', git: 'https://github.com/user/repo.git', ref: 'abc123def456...'

Java

Maven

  • Use a lockfile plugin (e.g., maven-lockfile or similar) to generate and commit a lockfile with checksums.
  • Pin exact versions in pom.xml — avoid ranges.
  • Enable dependency verification with checksums.

Gradle

Enable dependency locking in build.gradle:

dependencyLocking {
lockAllConfigurations()
}

Generate locks:

./gradlew dependencies --write-locks

  • Commit the lockfiles.
  • Use verification-metadata.xml for checksum/signature checks.

Go

  • Always commit both go.mod and go.sum (go.sum contains cryptographic hashes for everything).
  • Pin exact or pseudo-versions in go.mod.
  • For git sources, use full commit SHA:

go get github.com/user/repo@abc123def456

  • In CI: run go mod tidy && go mod verify.

Quick Implementation Checklist

  • Commit your lockfile(s) and switch CI to strict/frozen install commands.
  • Tighten all direct dependency version ranges to exact versions.
  • Enable minimum package age / exclude-newer where supported (npm, pnpm, Bun, uv).
  • Add cryptographic hashes/checksums (Python, Go, Gradle, Ruby, Maven lockfiles).
  • Pin any Git/VCS dependencies to full commit SHAs.
  • (Optional but fast) Disable postinstall scripts in CI: ignore-scripts=true (npm/Bun) — test first.

Why These Steps Work

These mitigations map directly to the attack patterns being used:

  • New malicious versions are ignored — maturity delay.
  • Compromised packages can’t break your builds — lockfiles and pins.
  • Tampering is detected — hashes.

Builds become reproducible and faster as a bonus.

None of this requires a new vendor or a big project. It requires an afternoon and the discipline to keep it in place. The attacks hitting these ecosystems right now are opportunistic. They work because most pipelines weren’t built with this in mind. Closing these gaps puts you in a different category from most of the targets.

If you want help auditing your dependency posture or hardening your CI pipeline, the Rhythmic team is happy to take a look.

Scroll to Top