Skip to content

[CI] Harden the release-on-npm workflow#3605

Merged
Kocal merged 6 commits into
symfony:2.xfrom
Kocal:harden-auto-releases-on-npm
Jun 4, 2026
Merged

[CI] Harden the release-on-npm workflow#3605
Kocal merged 6 commits into
symfony:2.xfrom
Kocal:harden-auto-releases-on-npm

Conversation

@Kocal

@Kocal Kocal commented May 29, 2026

Copy link
Copy Markdown
Member
Q A
Bug fix? no
New feature? no
Deprecations? no
Documentation? no
Issues Fix #...
License MIT

Following #3604

Summary

Following an audit of .github/workflows/release-on-npm.yaml with 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) instead
    of the hardcoded 2.x branch, and verify the tagged commit is an ancestor of the matching release branch
    (2.x for v2.*.*, 3.x for v3.*.*). Closes a TOCTOU where 2.x HEAD could drift between tag creation and job execution.

  • [CI] Stop touching package.json versions during release (F8) — drop the pnpm version + commit + push back to
    2.x block. The Git tag is the single source of truth; version bumps are now done manually before tagging. The workflow token is downgraded from contents: write to contents: read, and the new MAINTAINERS.md documents 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@latest with npm@11.16.0 so 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 --provenance to pnpm publish. Each tarball now carries a signed SLSA attestation pointing to the repo, commit and workflow run, verifiable via npm 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.y vs v3.0.0) still run in parallel.

  • [CI] Build assets and refuse to publish if dist files drift (F2) — run pnpm build on the tagged commit and fail if git diff --quiet reports anything. Mirrors the existing dist-files-unbuilt.yaml PR check, brought into the release path so tampered dist/source pairs cannot publish even if the branch CI was bypassed.

@Kocal Kocal self-assigned this May 29, 2026
@carsonbot carsonbot added the Status: Needs Review Needs to be reviewed label May 29, 2026
@Kocal Kocal requested a review from kbond May 29, 2026 20:34
Comment thread MAINTAINERS.md Outdated
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.
@Kocal Kocal force-pushed the harden-auto-releases-on-npm branch from 697674d to d23d276 Compare June 2, 2026 10:02

@kbond kbond left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does/should this address the packages.json version update?

Kocal added 5 commits June 2, 2026 06:11
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.
@Kocal Kocal force-pushed the harden-auto-releases-on-npm branch from d23d276 to 6607915 Compare June 2, 2026 10:11
@Kocal

Kocal commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

Does/should this address the packages.json version update?

Yes, the release-on-npm.yaml workflow previously commited/push all new files, and had
write permissions on contents.

With this potential security vulnerability, and the fact that version field from package.json files were updated after the creation of the release tag (by you or Fabien), leading to inconsistency etc., ATM I think the best option is to manually bump npm versions before the release tag.

@Kocal Kocal requested a review from kbond June 3, 2026 13:08
@Kocal

Kocal commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

As discussed, I'm will add a new dispatchable workflow (in another PR) for opening a PR that automatically modifies the version from package.json files

@Kocal Kocal merged commit 44687c5 into symfony:2.x Jun 4, 2026
26 of 29 checks passed
@Kocal Kocal deleted the harden-auto-releases-on-npm branch June 4, 2026 20:36
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Needs Review Needs to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants