chore: keep the publish credential away from the code it publishes - #26
Merged
Conversation
The publish job held an OIDC id-token while it installed dependencies and ran tests, so any postinstall in either tree could mint the credential npm exchanges for a publish token. The job now splits: a build job runs the release gates, the install, the tests, and packs the tarball holding no credential, and the publish job holds the id-token alone beside that tarball. A tag-on-main gate is what the approval now rests on, spelled refs/remotes/origin/main because git prefers a tag of that name over the remote-tracking ref. The release-notes composer also stops printing the origin URL when it refuses a non-GitHub remote, which can carry a credential.
There was a problem hiding this comment.
Pull request overview
This PR hardens the npm publish workflow by separating build/test/pack from the credentialed publish step, reducing the risk that dependency lifecycle scripts can access an OIDC token during installation.
Changes:
- Split
publish.ymlinto a non-credentialedbuildjob (gates, install, test, pack) and a credentialedpublishjob (OIDC only + tarball publish with--ignore-scripts). - Add a “tag must be on main” ancestry gate and broaden the “publishability” gate to reject more git-resolved dependency spec forms.
- Reduce potential credential leakage in
scripts/release-notes.shand document the trusted publishing/OIDC model inRELEASING.md.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
scripts/release-notes.sh |
Stops echoing the origin URL in the non-GitHub-origin failure path to avoid leaking credentials embedded in remotes. |
RELEASING.md |
Updates release process guidance to reflect the split build/publish jobs and adds documentation for npm trusted publishing auth. |
.github/workflows/publish.yml |
Implements the split build vs. publish jobs, adds tag-on-main gating and expanded dependency spec checks, and publishes a prebuilt tarball with OIDC provenance. |
Suppressed comments (1)
.github/workflows/publish.yml:133
- The publish job sets
permissionsto onlyid-token: write, which likely preventsactions/download-artifactfrom reading artifacts via the Actions API. Addactions: readso the tarball download can succeed while still keeping the token tightly scoped.
permissions:
id-token: write
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Why
The publish job held an OIDC id-token while it installed dependencies and ran the package lifecycle scripts, so a postinstall anywhere in either tree could mint the credential npm exchanges for a publish token. The workflow now splits: the build job runs the release gates, the install, the tests, and packs the tarball while holding no credential, and the publish job holds the id-token alone beside that prebuilt tarball, with no checkout and no dependency install.
The approval now rests on something mechanical. A build gate fails any tag whose commit is not an ancestor of main, so a green run is what the approver confirms. The gate names refs/remotes/origin/main in full, because a full-history checkout also fetches tags and git resolves a bare origin/main as refs/tags/origin/main first, which a pushed tag of that name could exploit. It runs directly after checkout, before the toolchain steps that execute configuration from the tagged commit. Every action is pinned by commit sha, and the npm CLI in the credentialed job is pinned exact and installed with --ignore-scripts.
scripts/release-notes.sh also stops printing the origin remote when it refuses a non-GitHub URL, which can carry credentials.
Validation
The workflows parse, and the mechanical checks hold: the build job carries no id-token and no environment; the publish job carries needs, the environment, id-token: write alone, and a tarball publish with --provenance --ignore-scripts; every uses line is a 40-hex sha with a version comment. The release-notes suite passes with its count unchanged from main. The split path itself runs at the next tag; the rendered workflow is the evidence until then.
The publishability gate also widens: it reads optionalDependencies and peerDependencies beside dependencies, and flags bare owner/repo shorthand and the gitlab, bitbucket and gist prefixes, which the old pattern let through.