Skip to content

ci(provenance): OIDC trusted publishing + dry-run gate + auto GitHub release (v1.x)#1428

Open
John-David Dalton (jdalton) wants to merge 1 commit into
v1.xfrom
jdalton/surf-726-v1x-publish-workflows
Open

ci(provenance): OIDC trusted publishing + dry-run gate + auto GitHub release (v1.x)#1428
John-David Dalton (jdalton) wants to merge 1 commit into
v1.xfrom
jdalton/surf-726-v1x-publish-workflows

Conversation

@jdalton

@jdalton John-David Dalton (jdalton) commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

What this does

The v1.x publish workflow (.github/workflows/provenance.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:

  1. npm trusted publishing (OIDC) instead of a stored NPM_TOKEN. All three npm publish steps drop NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}. The job already has id-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.
  2. A dry-run input that defaults to the safe mode. It defaults to true, so a stray dispatch builds everything but does not touch the registry. A real release requires a deliberate dispatch with dry-run: false.
  3. An automatic GitHub Release. After a successful publish and the existing 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-run input), 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 build job:

  1. Checks out with persist-credentials: false (no git token in .git/config).
  2. Installs a pinned, checksum-verified pnpm and Node 25.9.0.
  3. Downloads Socket Firewall and creates shims so pnpm install is scanned.
  4. Installs dependencies and builds the Maven manifest extension jar.
  5. Builds and publishes each of the three packages in turn: 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:

  • Repository: SocketDev/socket-cli
  • Workflow file: .github/workflows/provenance.yml
  • Branch/ref: v1.x
  • Environment: none

Packages that need the entry (this workflow publishes all three):

  • socket
  • @socketsecurity/cli
  • @socketsecurity/cli-with-sentry

Until 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-run input is a boolean, default true. Every publish step carries if: ${{ 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 on steps.publish_socket.outcome == 'success', a skipped publish also skips the tag and the release. A real release is a deliberate dispatch with dry-run: false.

A concurrency group keyed on the dist-tag serializes concurrent dispatches so two runs can't race on the same npm 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), with GH_TOKEN set only in each step's env block. The token is never written to .git/config, so it can't leak into an earlier pnpm install's postinstall scripts. Checkout keeps persist-credentials: false for 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 the socket publish 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

  • actionlint on .github/workflows/provenance.yml: zero findings (exit 0). This PR also fixes a pre-existing actionlint error on v1.x — the debug input had an options: list that is only valid for choice-typed inputs.
  • zizmor (v1.25.2): no findings (the two suppressed items are the pre-existing, intentional github-env ignores on the pnpm/Socket Firewall download scripts).
  • No secret is written to .git/config (checkout uses persist-credentials: false) or exposed to pnpm install (the gh token 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 socket publish can auto-tag and cut a GitHub Release.

A new dry-run input defaults to true, so dispatch still runs the full build but skips all three npm publish steps unless dry-run: false. Tag and release steps stay gated on publish_socket succeeding, so dry runs never touch the registry or GitHub release markers. concurrency serializes runs per dist-tag without cancelling in-flight publishes.

NODE_AUTH_TOKEN / NPM_TOKEN is removed from every publish step; publishing relies on existing id-token: write and npm trusted-publisher config for socket, @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 tag to step outputs (including same-SHA no-op) for a new idempotent gh release create step (--verify-tag, --generate-notes). Minor workflow fixes: invalid debug input options, and shellcheck notes on checksum tr for Windows paths.

Reviewed by Cursor Bugbot for commit 651ad81. Configure here.

…release

Switch the v1.x standalone publish workflow off the long-lived NPM_TOKEN to
npm trusted publishing (OIDC via id-token: write), so no npm secret is
stored in the repo. Add a dry-run input that defaults to true, gating all
three package publishes so an accidental dispatch builds but never reaches
the registry. After a successful publish, cut the immutable GitHub Release
for the auto-created v<version> tag, idempotently.

SURF-726

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue. You can view the agent here.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 651ad81. Configure here.

Comment thread .github/workflows/provenance.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant