ci(npm-publish): rename to npm-publish.yml + env npm-publish, OIDC trusted publishing + dry-run gate + auto GitHub release (v1.x) - #1428
Conversation
…usted publishing + dry-run gate + auto GitHub release (v1.x) collapses the publish workflow rename, OIDC trusted publishing, dry-run gate, and automatic GitHub release cutting for the v1.x line into one commit. bugbot's high-severity finding was confirmed a false positive (steps.<id>.outcome is the pre-continue-on-error result).
1bfae12 to
76065c7
Compare
…lish fail Two ways this workflow could ship a bad release. A dispatch from any ref could overwrite socket@latest. npm trusted publishing authorizes on repository + workflow filename + GitHub environment — it does not pin a branch. The npm-publish environment has no deployment-branch policy either (checked 2026-07-28: deployment_ branch_policy is null, zero protection rules), and dist-tag defaults to latest. So a workflow_dispatch from an old release branch, or from any branch an attacker can push, mints a valid token and publishes over latest. The guard now refuses a latest publish from anything but the default branch; other refs must name a non-latest tag, which is how the v1.x line should publish anyway. An environment deployment-branch policy is still worth adding, but this guard does not depend on one. A 1-of-3 release could look green. All three publishes carried continue-on-error: true while the tag and GitHub Release were gated only on the socket publish. If socket landed and either sibling failed, the job stayed green and cut a v<version> tag plus an IMMUTABLE Release describing artifacts that were never published — recovery is a version burn. A failed publish now fails the job, so no tag and no release. Verified by parsing the workflow rather than reading it: the guard is step 0 of the build job and no step carries continue-on-error. The first attempt at this edit inserted a second `steps:` key, which YAML resolves by keeping the last one — the guard was silently dropped and the file still looked correct by eye. Parse-checking caught it.
|
Pushed A dispatch from any ref could overwrite The guard now refuses a A 1-of-3 release could look green. All three publishes carried How I verified, and the mistake worth knowing aboutI checked by parsing the workflow, not by reading it: the guard is step 0 of the That mattered. My first attempt inserted a second |
Important
Update (adds commit
1bfae126c): the workflow is renamedprovenance.yml→npm-publish.ymland its job now bindsenvironment: npm-publish.npm trusted publishing is per-package, not per-branch. main now publishes
socket/@socketsecurity/cli/@socketsecurity/cli-with-sentryfromnpm-publish.yml+ thenpm-publishenvironment, and their TP entries pin that filename + environment. v1.x must publish those same packages from the same filename + environment or the OIDC token exchange 404s — so this PR conforms NAME + ENV (previouslyEnvironment: none). v1.x's own 3-variant build/publish flow is otherwise unchanged; OIDC (id-token: write, noNPM_TOKEN) is unchanged.What this does
The v1.x publish workflow (
.github/workflows/npm-publish.yml) is a standalone, inlined workflow that builds and publishes the three CLI packages to npm. This PR finishes the SURF-726 goal of making tag and release creation automatic in the release process, and removes the last long-lived npm secret from the release path.Three changes:
NPM_TOKEN. All threenpm publishsteps dropNODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}. The job already hasid-token: write, so npm mints a short-lived registry token from the OIDC token at publish time and attaches provenance. No npm secret lives in the repo anymore.dry-runinput that defaults to the safe mode. It defaults totrue, so a stray dispatch builds everything but does not touch the registry. A real release requires a deliberate dispatch withdry-run: false.v<version>tag creation, the workflow cuts the immutable GitHub Release for that tag, idempotently.Links: SURF-726. Modeled on socket-sdk-js #639 (inlined standalone publish + idempotent auto-tag with a
dry-runinput), adapted to socket-cli v1.x's real build (Socket Firewall shims, the Maven manifest jar, and the three package variants), which was already inlined by #1322.How the publish path works
The workflow runs on manual dispatch only. In order, the single
buildjob:persist-credentials: false(no git token in.git/config).pnpm installis scanned.socket,@socketsecurity/cli(legacy), and@socketsecurity/cli-with-sentry.Each publish uses
npm publish --provenance --access public --tag <dist-tag>. The three variants differ only in the build env vars (INLINED_SOCKET_CLI_LEGACY_BUILD,INLINED_SOCKET_CLI_SENTRY_BUILD) that rename the package and adjust its bins before publish. None of that changed here; only the auth mechanism, the dry-run gate, and the release step are new.OIDC / trusted-publisher setup (what needs configuring on npm)
npm trusted publishing means npm trusts a specific GitHub workflow to publish a package, with no shared secret. Each package must have a trusted-publisher entry on npm pointing at:
SocketDev/socket-cli.github/workflows/npm-publish.ymlv1.xnpm-publishPackages that need the entry (this workflow publishes all three):
socket@socketsecurity/cli@socketsecurity/cli-with-sentryUntil each entry exists on npm, that package's publish step will fail at the OIDC token exchange. This is why the token was removed here and configured on npm separately.
Dry-run gate
The
dry-runinput is a boolean, defaulttrue. Every publish step carriesif: ${{ inputs.dry-run == false }}, so on a dry run they are skipped and only the build runs. Because the tag and release steps are gated onsteps.publish_socket.outcome == 'success', a skipped publish also skips the tag and the release. A real release is a deliberate dispatch withdry-run: false.A
concurrencygroup keyed on the dist-tag serializes concurrent dispatches so two runs can't race on the samenpm publish(one would 409); an in-flight publish is never cancelled.Token scoping and the order rule
The tag and release steps talk to GitHub through
gh(gh api/gh release), withGH_TOKENset only in each step'senvblock. The token is never written to.git/config, so it can't leak into an earlierpnpm install's postinstall scripts. Checkout keepspersist-credentials: falsefor the same reason.Order rule: the
v<version>tag and the immutable GitHub Release are the final markers of a release. They are created only after thesocketpublish is confirmed live (publish_socket.outcome == 'success'), and the release step runs after the tag step. Tag creation is idempotent — an existing tag at the same commit is a no-op, and an existing tag at a different commit hard-fails with operator-recovery instructions (release immutability makes moving a tag unsafe). The release step is likewise idempotent: it no-ops if a release for the tag already exists.Validation
actionlinton.github/workflows/npm-publish.yml: zero findings (exit 0). This PR also fixes a pre-existing actionlint error on v1.x — thedebuginput had anoptions:list that is only valid forchoice-typed inputs.zizmor(v1.25.2): no findings (the two suppressed items are the pre-existing, intentionalgithub-envignores on the pnpm/Socket Firewall download scripts)..git/config(checkout usespersist-credentials: false) or exposed topnpm install(theghtoken lives only in the tag/release step env).Note
High Risk
Changes the release path to OIDC publishing and automatic immutable GitHub releases; misconfiguration or a dispatch with dry-run false can block or incorrectly gate production npm/GitHub artifacts.
Overview
Hardens the v1.x manual npm publish workflow so accidental runs build only, publishes use OIDC trusted publishing instead of a repo secret, and a successful
socketpublish can auto-tag and cut a GitHub Release.A new
dry-runinput defaults totrue, so dispatch still runs the full build but skips all threenpm publishsteps unlessdry-run: false. Tag and release steps stay gated onpublish_socketsucceeding, so dry runs never touch the registry or GitHub release markers.concurrencyserializes runs perdist-tagwithout cancelling in-flight publishes.NODE_AUTH_TOKEN/NPM_TOKENis removed from every publish step; publishing relies on existingid-token: writeand npm trusted-publisher config forsocket,@socketsecurity/cli, and@socketsecurity/cli-with-sentry. Legacy and Sentry publishes get explicit step names; behavior is unchanged aside from auth and the dry-run gate.After a successful primary publish, the tag step now exports
tagto step outputs (including same-SHA no-op) for a new idempotentgh release createstep (--verify-tag,--generate-notes). Minor workflow fixes: invaliddebuginputoptions, and shellcheck notes on checksumtrfor Windows paths.Reviewed by Cursor Bugbot for commit 651ad81. Configure here.