Skip to content

publish.yml can push a prerelease to latest with a long-lived token — superseded by release.yml but never removed #51

Description

@yakimoto

Summary

This repo has two npm publish workflows on different tag patterns. One is a well-built, gated, provenance-enforcing release. The other is an ungated legacy path that can publish a prerelease to latest with a long-lived token — and NPM_TOKEN is currently set on the repo, so it is armed.

release.yml already knows this. Its own header says so:

publish.yml (tags: v*) publishes with NO gate; this workflow is the guarded [one]

The guarded one landed in #44 (2026-07-02, fix/npm-oidc-trusted-publishing). The ungated one was never removed.

The two paths

release.yml — guarded publish.yml — ungated
Trigger tags: ['sdk-v*'] tags: ['v*']
Auth OIDC trusted publishing, no token NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Provenance --provenance, enforced --no-provenance, explicitly stripped
Version check tag must match package.json or it refuses none
dist-tag routed — *-next.*next none → latest

Why this is live, not theoretical

package.json on main is at 2.1.0-next.0. npm currently serves 2.0.14 as latest.

npm publish with no --tag publishes to latest regardless of whether the version is a semver prerelease. So a v*-shaped tag pushed to this repo today would put 2.1.0-next.0 on the latest dist-tag, and every npm install @wave-av/sdk would start resolving to a prerelease.

release.yml handles exactly this correctly:

case "$PKG_VERSION" in
  *-next.*) DIST_TAG=next ;;   # explicit prerelease channel

publish.yml has no equivalent.

Two smaller problems in the same file:

  1. --no-provenance overrides the package's own stated intent. package.json declares publishConfig: { "provenance": true }. This workflow passes a flag that cancels it, so a release down this path is unattested even though the package asked to be attested — and this is a public repo, where provenance actually works.
  2. Unpinned actions. actions/checkout@v4 and actions/setup-node@v4 are mutable tags in a workflow that holds publish rights. release.yml is pinned; this one isn't.

Suggested fix

Delete .github/workflows/publish.yml. It is fully superseded — release.yml does the same job with a version gate, correct dist-tag routing, provenance, and no long-lived credential.

Then revoke NPM_TOKEN on this repo. Once the ungated path is gone, nothing reads it, and an unused publish token on a public repo is a standing liability rather than a fallback. Trusted publishing is already registered for @wave-av/sdk against release.yml, so there is no path that still needs it.

If a v* trigger is wanted for ergonomics, add v* to release.yml's tag list rather than keeping a second workflow — one publisher, one gate.

Why I'm filing rather than fixing

Deleting a publish workflow and revoking a publish credential on a public package is a release-infrastructure change with a blast radius beyond my lane, and CI can't currently verify anything (org Actions are billing-locked, so every check on every repo fails at 0 jobs). Happy to open the PR once someone confirms the intent — the change itself is two deletions.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageOn the board but missing Type/Area/Priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions