Skip to content

feat(ci): add supply chain security defences#2644

Draft
mroderick wants to merge 1 commit into
masterfrom
feature/supply-chain-security
Draft

feat(ci): add supply chain security defences#2644
mroderick wants to merge 1 commit into
masterfrom
feature/supply-chain-security

Conversation

@mroderick

Copy link
Copy Markdown
Collaborator

What

Adds three defensive layers against supply chain attacks targeting Ruby dependencies.

Changes

  1. Dependabot cooldowns (.github/dependabot.yml)

    • 7-day default delay for version update PRs
    • Per-semver granularity: major 14d, minor 7d, patch 3d
    • Security updates bypass automatically — no delay for CVE patches
  2. Bundler checksum verification (Gemfile.lock)

    • Adds CHECKSUMS section with SHA-256 hashes for every gem
    • Bundler verifies downloaded gems match expected hashes on install
    • Detects tag hijacking (attacker replacing contents of an existing version)
    • Also fixes a high-severity vulnerability: oauth2 2.0.20 -> 2.0.22 (GHSA-pp92-crg2-gfv9)
  3. bundler-audit in CI (Gemfile + .github/workflows/ruby.yml)

    • New security-audit job runs on every push/PR
    • Checks against the ruby-advisory-db for known vulnerable gem versions
    • Already caught and fixed the oauth2 vulnerability above

Why

Recent incidents (Axios, LiteLLM, March 2026) show that malicious packages are typically detected and removed within hours. A simple delay between publication and installation blocks the most dangerous window.

These three layers complement each other:

  • Cooldowns block newly published malicious versions
  • Checksums detect tampering with existing versions
  • bundler-audit catches known vulnerabilities that slipped through

This is a low-risk, high-value change. No runtime code is affected. All three mechanisms are well-established patterns in the Ruby ecosystem.

Add three defensive layers against dependency supply chain attacks:

- Dependabot cooldowns: 7-day default for version update PRs, with
  per-semver granularity (major: 14d, minor: 7d, patch: 3d). Security
  updates bypass automatically.

- Bundler checksum verification: enable lockfile_checksums and add
  CHECKSUMS section to Gemfile.lock. Verifies downloaded gems match
  expected hashes on every install.

- bundler-audit: add to Gemfile and run in CI. Checks against the
  ruby-advisory-db for known vulnerable gem versions.

Also updates oauth2 (2.0.20 -> 2.0.22) to fix GHSA-pp92-crg2-gfv9,
a high-severity bearer token leakage vulnerability flagged by the new
bundler-audit check.

Refs NextLink Labs article on dependency cooldowns (Apr 2026).
@mroderick

Copy link
Copy Markdown
Collaborator Author

Detailed analysis: why these three defences, and what we evaluated

Background

The Ruby gem ecosystem has the same supply chain attack surface as npm and PyPI: a single compromised maintainer account can push malicious code that reaches thousands of production applications before anyone notices. Recent incidents:

  • Axios (March 2026): North Korean state actor hijacked npm account, published backdoored versions. Malicious versions live for ~3 hours. 100M+ weekly downloads.
  • LiteLLM (March 2026): CI/CD compromised via poisoned Trivy scanner. Malicious versions live for 40 minutes. 3.4M daily downloads.
  • rest-client (2019): Account hijack → backdoored gem.
  • strong_password (2020): RCE payload undetected for 6 months.

The common thread is time. Most malicious packages are detected and removed within hours or days. A deliberate delay — a cooldown — between publication and installation blocks the most dangerous window.

What we evaluated

1. Dependabot cooldowns (chosen — implemented)

GitHub shipped this feature in early 2026. It delays the creation of automated version-update PRs until a gem has been published for a minimum number of days.

Why we chose it:

  • Zero risk — fully reversible, no runtime impact
  • GitHub-native — enterprise SLA, automatic security bypass
  • Granular — per-semver configuration (major/minor/patch)
  • Already documented in the official Dependabot options reference

Why it's not enough on its own:

  • Only blocks the automated PR pipeline
  • Does not block manual bundle install or CI runs
  • Needs to be paired with registry-level or client-level cooldowns for full coverage

2. Bundler checksum verification (chosen — implemented)

Bundler 2.6+ introduced checksum verification. It calculates SHA-256 hashes for each gem and stores them in Gemfile.lock. On subsequent installs, it verifies downloaded gems match the expected hash.

Why we chose it:

  • Detects tag hijacking (attacker replacing contents of an existing version)
  • Complements cooldowns — they cover different attack vectors
  • One command to enable: bundle lock --add-checksums
  • No Bundler version changes needed — works with existing 2.7.2

What it does NOT protect against:

  • Newly published malicious versions (use cooldowns for this)
  • Compromised CI/CD pipelines
  • Social engineering attacks

What we also fixed: The new bundler-audit check immediately flagged oauth2 2.0.20 with GHSA-pp92-crg2-gfv9 (high severity — bearer token leakage to attacker hosts). Updated to 2.0.22.

3. bundler-audit (chosen — implemented)

bundler-audit checks Gemfile.lock against the ruby-advisory-db, a community-maintained database of known vulnerable gem versions.

Why we chose it:

  • Table stakes for Ruby projects
  • Catches known vulnerabilities that cooldowns and checksums miss
  • Fast CI job — runs in seconds
  • Already caught a real vulnerability in our lockfile

Limitations:

  • Database-dependent — only catches known vulnerabilities
  • Does not prevent installation — only flags after the fact
  • Must be run regularly (hence CI integration)

What we evaluated and deferred

gem.coop registry-level cooldowns (deferred)

gem.coop is a community-run gem server by the former RubyGems.org infrastructure team (Andre Arko, David Rodriguez, Martin Emde, etc.). Their beta cooldown endpoint (https://beta.gem.coop/cooldown) enforces a 48-hour delay at the registry level — no Bundler changes needed.

Why we deferred it:

  • Beta maturity: Released January 2026 (~5 months old). Only 1 known repo uses the cooldown endpoint.
  • Political entanglement: The project was created after a governance crisis where Ruby Central removed the maintainers from the RubyGems GitHub organisation. Legal action has been mentioned by multiple sources.
  • Trust question: While the team is legitimate (they ran RubyGems.org for a decade), the service is a new third-party registry with no SLA and no public status page.
  • Adoption: The regular mirror has real adoption (Sidekiq, RSpec, 235 repos), but the cooldown feature specifically is not yet production-ready.

What we will do instead:

  • Monitor gem.coop cooldown adoption. If Sidekiq/RSpec adopt it, that's a signal.
  • Re-evaluate in 3-6 months when the beta label is removed.
  • Consider gem.coop as a mirror (for speed/caching) separately from the cooldown feature.

Sources:


What these defences do NOT protect against

  • Typosquatting: A malicious gem named devize instead of devise passes a cooldown after 48 hours.
  • Long-lived compromises: If an attacker maintains malicious code for weeks, cooldowns won't catch it.
  • Compromised CI/CD: If your own build pipeline is compromised, these defences don't help.
  • Social engineering: Attacker becomes a legitimate maintainer over time.

We recommend pairing these defences with:

  • Regular review of Dependabot security alerts (already enabled)
  • GitHub secret scanning (already enabled)
  • Keeping Bundler updated (4.x roadmap includes lockfile improvements)

Summary

Layer What it blocks What it misses Effort
Dependabot cooldowns Automated PRs for fresh gems Manual installs, CI 5 min config
Bundler checksums Tampered existing versions New malicious versions 1 command
bundler-audit Known vulnerabilities Zero-days, unknown issues 1 gem + CI job

Together they cover the three most common attack vectors with minimal risk and no runtime impact.

@mroderick mroderick requested a review from olleolleolle June 13, 2026 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant