Skip to content

Comments

Harden against supply chain attacks#1558

Merged
bobular merged 23 commits intomainfrom
harden-against-supply-chain-attacks
Dec 8, 2025
Merged

Harden against supply chain attacks#1558
bobular merged 23 commits intomainfrom
harden-against-supply-chain-attacks

Conversation

@bobular
Copy link
Member

@bobular bobular commented Nov 27, 2025

What does this PR do?

This PR implements several security improvements and modernizes our development tooling to protect against supply chain attacks (when malicious code sneaks into our project through compromised npm packages).

Key Changes

1. Supply Chain Security: 7-Day Package Age Requirement 🛡️

What changed: Added npmMinimalAgeGate: 10080 to .yarnrc.yml

What this means: Yarn will now refuse to install any npm package that's less than 7 days old (10080 minutes = 7 days).

Why this helps: If a popular package gets compromised, it usually gets detected and fixed within a few days. By waiting 7 days before allowing new package versions, we avoid automatically pulling in malicious code that might have just been published.

Example: If someone publishes react@19.0.0-malware today (December 1), our project won't be able to install it until December 8, giving the security community time to catch and report it.


2. Upgraded to Yarn 4.12.0 📦

What changed: Updated from Yarn 3.3.1 to Yarn 4.12.0 using Corepack

Why:

  • Yarn 4 includes the npmMinimalAgeGate security feature (not available in Yarn 3)
  • Better performance and bug fixes
  • Corepack (built into Node.js) manages Yarn versions automatically

What you need to do:

  1. Make sure you have Node 24+ installed (via Volta: it should auto-switch)
  2. Run these commands once on your machine:
    npm install -g corepack
    corepack enable --install-directory ~/.volta/bin
  3. That's it! The correct Yarn version will be used automatically.

3. Package Manager Validation

What changed: Added tools/scripts/check-package-manager.mjs that runs before every yarn install

What this does:

  • Checks that you're using the correct Yarn version (4.12.0)
  • Gives you a helpful error if corepack isn't set up correctly
  • Prevents accidental use of wrong package manager versions

What you'll see:

  • If everything is set up correctly: ✓ Yarn version check passed
  • If corepack isn't enabled: Clear error message with instructions

4. Immutable Lockfile Protection 🔒

(See also VEuPathDB/install#42 which will be merged soon.)

What changed: Added enableImmutableInstalls: true to .yarnrc.yml

What this means: The yarn.lock file cannot be modified by running yarn install. It must be properly committed to git.

Why this helps:

  • Prevents accidental mass upgrades (e.g., if someone deletes and regenerates yarn.lock, many packages would upgrade to newer versions)
  • Ensures all dependency upgrades are intentional and reviewed, not accidental
  • Ensures everyone on the team has identical dependencies
  • Aligns with our production/CI practices (already enforced on build servers and Jenkins)

How it affects you:

  • yarn add date-fns - works normally, updates lockfile as expected
  • yarn remove lodash - works normally, updates lockfile as expected
  • yarn install - works if lockfile is up to date
  • yarn install - fails if lockfile is missing or out of sync (intentional protection!)

Error you might see:

YN0028: The lockfile would have been modified by this install, which is explicitly forbidden.

What to do: Make sure your yarn.lock is committed and up to date. Don't try to regenerate it from scratch - dependency upgrades should be intentional.


5. Blocked npm Usage 🚫

What changed:

  • Added .npmrc with engine-strict=true
  • Added "npm": "please-use-yarn" to package.json engines

Why: This project uses Yarn-specific features that npm doesn't support. These changes are intended to give developers a clear error if they accidentally try npm install instead of yarn install. However, this does not prevent npm install from doing initial dependency checks which will fail with a cryptic error message about dependency failures. This measure is just belt-and-braces.


6. Standardized on Node 24 ⬆️

What changed: All GitHub Actions workflows now consistently use Node 24.11.0, and local development environment updated to match

Why:

  • Node 24 includes the appropriate npm version needed for trusted publishing/OIDC support in our npm-publish workflow
  • Previously had a mix of Node versions across different GitHub Actions (Node 14, 24)
  • Now CI and local development use the same Node version for consistency

7. Improved CI Caching

What changed:

  • GitHub Actions workflows now explicitly cache .yarn/cache and .yarn/install-state.gz
  • Removed redundant build caching that wasn't providing benefits
  • Cache keys include OS prefix for better isolation

Impact: Faster CI builds (~300MB cache, much faster yarn install)


8. Simplified CI Build Process 🎯

What changed: Removed separate yarn nx affected --target=build-npm-modules step from PR checks

Why: The compile:check target automatically builds dependencies (configured via NX's dependsOn), so we were building things twice. Now we just run compile:check once.


9. Cleaned Up Configuration 🧹

What changed:

  • Removed outdated Yarn 3 plugins and release binary
  • Removed Volta yarn version pin (Corepack manages this now)
  • Removed volta configs from individual package.json files
  • Added prettier as explicit dependency (was only transitive before)

Why: Simplify configuration and remove obsolete/conflicting settings


What do I need to do?

First Time Setup (One-time per machine):

# 1. Make sure you have Volta installed (for Node version management)
curl https://get.volta.sh | bash

# 2. Install and enable corepack (for Yarn version management)
npm install -g corepack
corepack enable --install-directory ~/.volta/bin

# 3. Navigate to the repo and run yarn
cd /path/to/web-monorepo
yarn install

Regular Development:

Nothing changes! Just use yarn commands as normal:

  • yarn install - installs dependencies (with 7-day age check)
  • yarn nx start @veupathdb/genomics-site - start dev server
  • etc.

Security Benefits Summary

  1. Compromised packages are blocked: 7-day waiting period catches most supply chain attacks
  2. Prevents accidental upgrades: Immutable lockfile ensures all dependency changes are intentional
  3. Version consistency: Everyone uses the exact same Yarn version (4.12.0) and exact same dependencies
  4. Clear errors: Helpful messages if setup is incorrect
  5. Prevents accidents: Can't accidentally use npm or wrong Yarn version

Questions?

Q: What if I need a package that was just published?
A: You'll need to wait 7 days, or temporarily disable the check (not recommended). This is intentional - fresh packages are risky.

Q: Will this slow down my development?
A: No! The age check only affects new packages you're adding. Existing dependencies install normally.

Q: What if I see "Expected Yarn 4.12.0 but got 1.22.22"?
A: Run corepack enable --install-directory ~/.volta/bin (see setup instructions in README.adoc)

Q: Can I still use npm?
A: No, this project requires Yarn. npm install will fail.

Q: What if I see "The lockfile would have been modified by this install"?
A: This means your yarn.lock is out of sync with package.json. Make sure you've pulled the latest changes from git. If you're intentionally adding/removing packages, use yarn add or yarn remove (not plain yarn install).


Technical Details

For more information about the security features:


Testing

You can force the wrong version of yarn with something like this:

~/.volta/tools/image/yarn/1.22.22/bin/yarn install

An npm publish action has been tested on this branch and it succeeded:
https://github.com/VEuPathDB/web-monorepo/actions/runs/19822162339

@bobular bobular marked this pull request as ready for review December 1, 2025 12:06
@bobular bobular requested a review from Copilot December 1, 2025 12:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements comprehensive supply chain security improvements by upgrading to Yarn 4.12.0 and introducing package age gates, immutable lockfiles, and automated version validation. The changes modernize the development toolchain to protect against compromised npm packages while standardizing on Node 24 across all environments.

Key Changes:

  • Added 7-day package age requirement (npmMinimalAgeGate: 10080) to block newly published packages
  • Upgraded from Yarn 3.3.1 to 4.12.0 with Corepack-based version management
  • Enabled immutable lockfile protection to prevent accidental mass upgrades

Reviewed changes

Copilot reviewed 18 out of 20 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tools/scripts/check-package-manager.mjs New preinstall hook that validates correct Yarn version via user agent parsing
package.json Updated to Yarn 4.12.0, added preinstall/postinstall scripts, blocked npm usage, added prettier dependency
packages/*/package.json (9 files) Removed individual volta version pins (now centralized in root package.json)
.yarnrc.yml Configured npmMinimalAgeGate (7 days), enableImmutableInstalls, removed Yarn 3 plugins
.npmrc Added engine-strict enforcement to block npm with clear error messages
.github/workflows/*.yml (3 files) Standardized on Node 24.11.0, added corepack, improved caching, removed redundant build step
README.adoc Comprehensive documentation of new setup requirements and security features

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Read expected version from package.json
const packageJson = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf-8'));
const expectedVersion = packageJson.packageManager?.replace('yarn@', '');
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The replace() method only replaces the first occurrence. If packageManager contains multiple '@' characters (e.g., 'yarn@4.12.0@something'), this could produce incorrect results. Use packageJson.packageManager?.split('@')[1] or add a regex with proper anchoring: replace(/^yarn@/, '') to ensure only the prefix is removed.

Suggested change
const expectedVersion = packageJson.packageManager?.replace('yarn@', '');
const expectedVersion = packageJson.packageManager?.split('@')[1];

Copilot uses AI. Check for mistakes.
"packages/sites/*"
],
"scripts": {
"preinstall": "node tools/scripts/check-package-manager.mjs",
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The preinstall script runs for every yarn install, including in CI. Consider whether this adds unnecessary overhead in trusted environments like GitHub Actions where the Yarn version is already guaranteed by corepack. You could make the check conditional (e.g., skip if CI=true environment variable is set) to reduce execution time in automated workflows.

Suggested change
"preinstall": "node tools/scripts/check-package-manager.mjs",
"preinstall": "if [ \"$CI\" != \"true\" ]; then node tools/scripts/check-package-manager.mjs; fi",

Copilot uses AI. Check for mistakes.
bobular and others added 2 commits December 1, 2025 12:41
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@bobular bobular merged commit 76d3124 into main Dec 8, 2025
1 check passed
@bobular bobular deleted the harden-against-supply-chain-attacks branch December 8, 2025 14:48
@bobular
Copy link
Member Author

bobular commented Dec 9, 2025

Note: we are not cherry-picking this into the legacy deployment branch v1.3.6-patch

It's not really needed there and there are yarn.lock file merge complications.

aurreco-uga pushed a commit that referenced this pull request Jan 9, 2026
* node 14 to 24 upgrade in github actions

* remove broken condition in cache.yml workflow

* start setting up for yarn 4.10.3 with corepack

* more corepack/volta config

* better docs for volta users

* remove volta configs from sub-packages

* minutes not nice time strings

* indentation error fixed

* enable corepack in github actions

* rejig corepack and cache config

* upgrade to latest action plugin versions

* document sunsetting of cache.yml

* pin Node version exactly in actions

* try caching NX cache too

* move comment, mainly to trigger new action

* add built package lib dirs to cache

* add --verbose to understand nx behaviour

* abandon trying to cache nx between actions; only run one yarn nx affected command to avoid repeating lib builds

* upgrade to yarn 4.12.0 for no urgent reason

* more checks on yarn versions and attempts/documentation to prevent npm usage

* added lockfile checks for devs too, improved documentation

* Explain minutes value

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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