[CI] Harden the release-on-npm workflow#3605
Merged
Merged
Conversation
Kocal
commented
May 30, 2026
The release-on-npm workflow used to check out `2.x` HEAD when triggered by a tag, meaning the published code could diverge from the tagged commit if `2.x` advanced between tag creation and job execution (TOCTOU). Check out the tag itself (`github.ref`) and verify it is an ancestor of the matching release branch, derived from the tag's major version, so the same workflow works for `v2.*.*` and `v3.*.*` without hardcoding the branch name.
697674d to
d23d276
Compare
kbond
reviewed
Jun 2, 2026
kbond
left a comment
Member
There was a problem hiding this comment.
Does/should this address the packages.json version update?
The release-on-npm workflow used to run `pnpm version`, commit and push the result back to `2.x` after publishing. That commit desynchronized the branch from the real Symfony UX version (the Git tag) and required `contents: write` on the workflow token, which can bypass branch protection. Version bumps are now done manually by the maintainer before tagging, so the workflow just checks out the tag and publishes whatever versions are already in `package.json`. The `Configure Git`, `Extract version from tag`, `Set version of JS packages`, `Commit changes` and `Push changes` steps are dropped, and the workflow token is downgraded to `contents: read`. A new `MAINTAINERS.md` documents the bump procedure so the convention is discoverable.
The release-on-npm workflow installed `npm@latest` before running `pnpm publish`. A compromised npm release would have landed on the runner with access to the OIDC token, with no signal to anyone before the publish step ran. Pin npm to `11.16.0` (current latest, above the 11.5.1 minimum required for OIDC). Bumps now require an explicit PR, so the version used in CI is reviewable and auditable.
`pnpm publish` ran without `--provenance`, so the tarballs landing on the npm registry carried no cryptographic link to the source repository. Users could not verify that a given `@symfony/ux-*` release was actually built from this repo by this workflow. Add `--provenance` to the publish step. Each tarball now ships a signed SLSA attestation pointing to the repo, the commit and the workflow run, visible on the package page and verifiable via `npm audit signatures`.
Two release-on-npm runs on the same tag could previously execute in parallel — typically after a force re-push of a tag — leading to races between concurrent `pnpm publish` calls. Add a concurrency group keyed on `github.ref_name` so runs on the same tag queue instead of overlapping, while runs on distinct tags (e.g. `v2.x.y` and `v3.0.0`) still execute in parallel. `cancel-in-progress: false` lets a publish already in flight finish cleanly.
The release workflow used to publish whatever was committed at the tag, with no on-the-spot integrity check. A compromised PR that modified source code without rebuilding `dist/` — or that modified `dist/` without touching the source — would ship straight to npm. Run `pnpm build` on the tagged commit and fail the workflow if `git diff --quiet` reports anything. This mirrors the existing `dist-files-unbuilt.yaml` PR check, brought into the release path so a tampered tag cannot publish even if the branch CI was bypassed. Fast (~2-5 min) and deterministic — no flaky PHPUnit/E2E in the release-critical path.
d23d276 to
6607915
Compare
Member
Author
Yes, the With this potential security vulnerability, and the fact that |
Member
Author
|
As discussed, I'm will add a new dispatchable workflow (in another PR) for opening a PR that automatically modifies the |
Kocal
added a commit
that referenced
this pull request
Jun 5, 2026
This PR was merged into the 3.x branch. Discussion ---------- [CI] Add workflow to prepare NPM release | Q | A | -------------- | --- | Bug fix? | no | New feature? | no <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- if yes, also update UPGRADE-*.md and src/**/CHANGELOG.md --> | Documentation? | no <!-- required for new features, or documentation updates --> | Issues | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead --> | License | MIT Following #3605 Commits ------- d125385 [CI] Add workflow to prepare NPM release
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Following #3604
Summary
Following an audit of
.github/workflows/release-on-npm.yamlwith my super friend Claude,this PR tightens several aspects of the npm release pipeline.
OIDC trusted publisher protects authentication on the npm side, but says nothing about the integrity of the code being published.
The changes below close that gap.
Each commit addresses one finding from the audit, tagged
(F<n>)in its title.Commits
[CI] Publish from the tagged commit, not the branch HEAD (F1) — checkout
${{ github.ref }}(the tag) insteadof the hardcoded
2.xbranch, and verify the tagged commit is an ancestor of the matching release branch(
2.xforv2.*.*,3.xforv3.*.*). Closes a TOCTOU where2.xHEAD could drift between tag creation and job execution.[CI] Stop touching package.json versions during release (F8) — drop the
pnpm version+ commit + push back to2.xblock. The Git tag is the single source of truth; version bumps are now done manually before tagging. The workflow token is downgraded fromcontents: writetocontents: read, and the newMAINTAINERS.mddocuments the bump procedure.Code can not be commited/pushed to the repo anymore ✨
[CI] Pin npm version used by the release workflow (F6) — replace
npm@latestwithnpm@11.16.0so a compromised npm release cannot ship through the release path without an explicit, reviewable bump PR.[CI] Generate npm provenance attestations on publish (F7) — add
--provenancetopnpm publish. Each tarball now carries a signed SLSA attestation pointing to the repo, commit and workflow run, verifiable vianpm audit signatures.[CI] Serialize release runs per tag (F9) — add a concurrency group keyed on
github.ref_name, so two runs on the same tag queue instead of overlapping. Distinct tags (v2.x.yvsv3.0.0) still run in parallel.[CI] Build assets and refuse to publish if dist files drift (F2) — run
pnpm buildon the tagged commit and fail ifgit diff --quietreports anything. Mirrors the existingdist-files-unbuilt.yamlPR check, brought into the release path so tampered dist/source pairs cannot publish even if the branch CI was bypassed.