Skip to content

chore: keep the publish credential away from the code it publishes - #21

Merged
robrigo merged 1 commit into
mainfrom
chore/publish-hardening
Aug 19, 2026
Merged

chore: keep the publish credential away from the code it publishes#21
robrigo merged 1 commit into
mainfrom
chore/publish-hardening

Conversation

@robrigo

@robrigo robrigo commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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 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.

Copilot AI left a comment

Copy link
Copy Markdown

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 hardens the tag-triggered npm publish workflow by separating build/test/pack from the credentialed publish step, reducing exposure of OIDC credentials to any package lifecycle scripts. It also updates release documentation and avoids leaking potentially credential-bearing origin URLs in release tooling errors.

Changes:

  • Split .github/workflows/publish.yml into an uncredentialed build job (gates, install, test, pack) and a credentialed publish job (artifact publish only).
  • Add a git ancestry gate to ensure the pushed tag’s commit is on main before approval/publish proceeds.
  • Update release docs and suppress printing the origin remote URL in scripts/release-notes.sh error output.

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 full origin URL when rejecting non-GitHub remotes to avoid credential leakage.
RELEASING.md Documents the new build+publish split, approval expectations, and OIDC trusted publishing behavior.
.github/workflows/publish.yml Implements the split workflow, tag-on-main gate, tarball artifact handoff, and credential-minimized publishing.
Suppressed comments (1)

.github/workflows/publish.yml:86

  • actions/download-artifact uses the GitHub Actions API and typically requires actions: read. With job-level permissions set to only id-token: write, artifact download can fail due to missing actions permission. Consider granting the minimum actions: read here (and keep contents unset if not needed).
    permissions:
      id-token: write

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/publish.yml
@robrigo
robrigo merged commit c95d13f into main Aug 19, 2026
4 checks passed
@robrigo
robrigo deleted the chore/publish-hardening branch August 19, 2026 01:36
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.

2 participants