1 of 15

Protecting Yourself

(And Your Poor CI Runners) Against Supply Chain Attacks

Connor Shea, Rocky Mountain Ruby

2 of 15

Step 1: Rewrite everything in Rust.

* This joke was much funnier before last week

3 of 15

Your dependencies

4 of 15

Set a cooldown

Malicious package releases usually get caught within a few hours!

Tool

Setting

Where

Available Since

Bundler

cooldown

bundle config / --cooldown

4.0.13+ (June 2026)

npm

min-release-age

.npmrc

11.10+ (February 2026)

pnpm

minimumReleaseAge

pnpm-workspace.yaml

10.16+ (September 2025)

yarn

npmMinimalAgeGate

.yarnrc.yml

4.10+ (September 2025)

* Note that different package managers use different units of time, make sure to check the docs.

​

pnpm has had a default cooldown since v11 (April 2026), yarn since 4.15 (May 2026). npm and Bundler must opt-in.

Dependencies

5 of 15

Set a cooldown

Setting a cooldown in your Gemfile is easy!

Dependencies

# this will prevent installing gems newer than 3 days

source 'https://rubygems.org', cooldown: 3

​

ruby '>= 4.0.6'

​

gem 'rails', '~> 8.2.0'

6 of 15

“But the tragedy of the commons!”

Worry not: Security companies love finding these things

And you can skip the cooldown if a gem gets a critical security fix:

bundle update rails --cooldown 0

Dependencies

7 of 15

Add checksums to your Gemfile.lock

$ bundle lock --add-checksums

​

CHECKSUMS

  rack (2.1.3) sha256=9ae0d2...

Bundler will confirm the SHA hashes of every gem before installing, so if RubyGems.org or your network get compromised, manipulated gems won’t effect you.

Bundler 4 adds this to new lockfiles by default, but any project from >1 year ago needs to opt-in.

(Requires Bundler 2.6+)

Dependencies

8 of 15

Turn off post-install scripts (or upgrade)

Package manager

Dependency scripts off by default

npm

v12 (July 2026)

pnpm

v10 (January 2025)

yarn

v4.14 (April 2026)

Post-install scripts run by default and can compromise you very easily if you upgrade a compromised package, disable them! (or upgrade to a version that disables them by default!)

You can enable install scripts per-package via config files/CLI commands, some packages do genuinely need them.

Unfortunately, Bundler has no equivalent setting currently.

Dependencies

9 of 15

CI / CD

Protect your GitHub Actions/GitLab CI setup

10 of 15

Pin GitHub Actions to commit SHAs

# a tag will install the latest version in the range

- uses: connorshea/super-cool-workflow@v3

​

# a commit SHA will always install the same version forever

- uses: connorshea/super-cool-workflow@<SHA> # v3.1.0

Enforce SHA hashes in settings

In the Actions settings, require workflows to be pinned to a full-length commit SHA.

Dependabot/Renovate will update for you

Dependabot and Renovate update pinned SHAs. You can use pinact to pin your existing actions files (or just ask Claude)

CI / CD

​

11 of 15

Don't install packages ad-hoc in CI

Bad!

gem install foo

npm install foo

npx is-true

What version does this install? The latest one! And the latest versions of its 30 dependencies!

​

If you run CI every 5 minutes, even short compromises on npm/RubyGems will ruin your security team’s day

Good :)

BUNDLE_FROZEN="true" bundle install

npm ci

pnpm install --frozen-lockfile

yarn install --immutable

CI / CD

With most of these, you can set these flags in the config file as well.

12 of 15

Lint your CI config with Zizmor

Run zizmor

$ zizmor .github/workflows/

Static analysis for GitHub Actions.

You can automatically enforce a lot of the things I’ve mentioned here!

  • adhoc-packages: disallow gem install, npm install, etc. (I contributed this one!)
  • unpinned-uses: enforce that all workflows are pinned to a SHA hash.
  • excessive-permissions: force setting the permissions field on every workflow to limit the impact if a CI workflow is compromised.

CI / CD

​

13 of 15

Check your default GITHUB_TOKEN permissions

Feb 2023

Older repo or org? Every workflow may still get write by default!

In the Actions settings, make sure workflow permissions are set to read by default.

And set permissions per-job to limit what it can do:

permissions:

  contents: read

CI / CD

By default with read/write:

  • Read/write issues
  • R/W PRs
  • R/W packages
  • R/W GitHub Pages
  • R/W deployments
  • R/W commits & tags

​

14 of 15

Verify binaries you download

Pulling a CLI from a GitHub or GitLab release in a Dockerfile for CI/prod? Make sure it pins to a SHA hash.

ARG TOOL_SHA256=<sha256, committed in your repo>

RUN curl -fsSLo tool.tar.gz "$TOOL_URL" \

  && echo "$TOOL_SHA256  tool.tar.gz" | sha256sum -c -

Otherwise a compromised GitHub release can get you!

CI / CD

15 of 15

Thanks!

Keep your servers safe!

These slides are linked on the schedule if you want to reference them later.