Skip to content

ci: enforce yarn integrity#18018

Merged
JLHwung merged 7 commits into
babel:mainfrom
JLHwung:ensure-yarn-binary-integrity
Jun 15, 2026
Merged

ci: enforce yarn integrity#18018
JLHwung merged 7 commits into
babel:mainfrom
JLHwung:ensure-yarn-binary-integrity

Conversation

@JLHwung

@JLHwung JLHwung commented May 25, 2026

Copy link
Copy Markdown
Contributor
Q                       A
Fixed Issues? Fixes #1, Fixes #2
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

In this PR we enforce the integrity check for the yarn binary.

Motivation

The yarn binary is a minified .cjs which is difficult to review. However, an attacker could modify the yarn binary and easily perform a supply chain attack, e.g. inject payloads to every published package or try to steal the publish credential. Because yarn is minified, the attacker could open a seemingly safe PR, e.g. upgrade Yarn to 5.6.7 for CVE-1234-56789 and trick maintainers to merge it. In the past Babel has temporarily modified the yarn binary as well, though it is for a good reason at that time.

The corepack use command will download yarn binary and add a sha512 signature to the packageManager field. However, it does not enforce that the local yarn binary matches the hash. Since an integral global yarn binary will still invoke an affected yarn binary in the local repo, corepack does not provide the integrity assurance of the package manager.

Approach

In this PR, we read the yarn version from the packageManager field, run corepack enable and then manually compare the sha512sum of the local binary to the result set by corepack use yarn@<version>, which will download yarn from the official release channel and update the hash.

To minimize the CI overhead, the check will only run if it is a PR and there are changes in .yarn/releases.

Context

There is an upstream issue proposing to add hash in yarn version set: yarnpkg/berry#6657.

Demo

Here are three example PRs and the workflow fails the first two and passes the last one as expected:

Modified yarn binary without updating hash: JLHwung#15

Modified yarn binary as well as the hash: JLHwung#16

Normal yarn update: JLHwung#17

/cc @arcanis Is it possible to let a global yarn binary (installed by corepack) to copy itself to the local repo without first invoking the local yarn binary?

@JLHwung

JLHwung commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

I will open a few example malicious PRs to test if the updated workflow can detect modified yarn binary.

@babel-bot

babel-bot commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/61773

@JLHwung JLHwung force-pushed the ensure-yarn-binary-integrity branch 3 times, most recently from 1931e2a to 80f5b68 Compare May 25, 2026 18:41
@arcanis

arcanis commented May 25, 2026

Copy link
Copy Markdown
Contributor

Wouldn't it be easier to just use packageManager and skip the checked-in binary?

@pkg-pr-new

pkg-pr-new Bot commented May 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

commit: 834b8e9

@JLHwung

JLHwung commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

Wouldn't it be easier to just use packageManager and skip the checked-in binary?

Good idea. I will explore a bit.

Previously I was following the recommendation from https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored, but https://yarnpkg.com/configuration/yarnrc suggested that corepack should be used in most cases.

I guess we can probably remove the yarnPath and the checked-in binary.

@arcanis I did some research and before actions/setup-node#531 is resolved, removing the checked-in binary will bring new hassles because we may have to manually install corepack on some platforms (actions/setup-node#480 (comment)) when we use yarn. And this caveat will unnecessarily complicate many other workflows.

For the time being I think we will settle down on the integrity check, which only runs on ubuntu-latest with node.js latest, before corepack becomes generally available in every GH action runners we use.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds a CI safeguard to ensure the Yarn binary committed in the repo hasn’t been tampered with, by comparing the local .yarn/releases artifact hash against the integrity hash recorded in package.json’s packageManager field.

Changes:

  • Add scripts/ensure-yarn-version-integrity.ts to compute and compare the Yarn binary’s sha512.
  • Update package.json packageManager to include the +sha512.<hash> integrity suffix.
  • Add a new GitHub Actions workflow that runs the integrity check on relevant PR changes.

Reviewed changes

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

File Description
scripts/ensure-yarn-version-integrity.ts Implements the sha512 comparison between the committed Yarn binary and the expected integrity hash.
package.json Records Yarn version + integrity hash in the packageManager field.
.github/workflows/package-manager.yml Adds a PR-scoped workflow to execute the integrity verification script.

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

Comment thread .github/workflows/package-manager.yml Outdated
Comment on lines +3 to +7
pull_request:
paths:
# Ensure packageManager field contains the correct yarn version and integrity hash.
- "package.json"
- ".yarn/releases/*"
Comment thread .github/workflows/package-manager.yml
Comment on lines +29 to +33
const yarnPath = `./.yarn/releases/yarn-${yarnVersion}.cjs`;

const actualHash = createHash("sha512")
.update(fs.readFileSync(yarnPath))
.digest("hex");

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

const match = packageManagerSpecRegex.exec(readPackageManagerSpec());
if (!match) {
throw new Error(
"Invalid packageManager format in package.json, expected 'yarn@<version>+sha512.<hash>'. Use 'corepack prepare yarn@<version>' to get the correct hash."
cp.execSync(`corepack use yarn@${yarnVersion}`);

// Read the package manager spec again to get the expected hash, since corepack might have updated it
const match2 = packageManagerSpecRegex.exec(readPackageManagerSpec())!;
Comment thread .github/workflows/package-manager.yml
Comment thread .github/workflows/package-manager.yml
@JLHwung JLHwung force-pushed the ensure-yarn-binary-integrity branch 2 times, most recently from 78521ba to 57aea21 Compare May 26, 2026 15:06
@nicolo-ribaudo nicolo-ribaudo added the PR: Internal 🏠 A type of pull request used for our changelog categories label Jun 14, 2026
@JLHwung JLHwung force-pushed the ensure-yarn-binary-integrity branch from 57aea21 to 834b8e9 Compare June 15, 2026 15:18
@JLHwung JLHwung merged commit c7142d4 into babel:main Jun 15, 2026
58 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: Internal 🏠 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants