Repository navigation
ci: stop passing NPM_TOKEN to Speakeasy workflows for npm trusted publishing - #1267
Conversation
|
I'll fix CI failures and address comments from users with write access that start with 'Devin'.
Original prompt from Alex
|
There was a problem hiding this comment.
Perry's Review
Verdict: 💬 Comments / questions
APPROVE was downgraded to COMMENT: Perry's maintainer app is not installed on
OpenRouterTeam, so it cannot cast an approving review here. The findings below are clean — this is ready for a human to approve.
Correct removal, and I was able to verify the one thing the description flags as unverified. One thing to document so this doesn't break later.
Verification of the premise
I checked each claim in the description against the pinned action source and the registry rather than taking it on trust:
- npm OIDC is live for this package. Versions 1.5.9, 1.5.10 and 1.5.11 of the package all carry
_npmUser: GitHub Actions <npm-oidc-no-reply@github.com>withtrustedPublisher.id: githubanddist.attestations, so the last several releases already published over OIDC, not the token. The token was genuinely unused. npm_tokenis optional in both reusable workflows. The pinned revisions of sdk-publish.yaml and workflow-executor.yaml in sdk-generation-action declare itrequired: false, so dropping thesecrets:entry does not fail workflow validation.- The
releasestep never readsnpm_token. Its Go implementation only callsaddPublishOutputs→AddTargetPublishOutputs, which decides whether to publish fromtarget.IsPublished(). Nothing in the release path touches the npm token, so the "not verified until the first release" caveat in the description does not apply — there is no code path that could reject an empty$npm_token. - Empty
NODE_AUTH_TOKENstill leaves OIDC reachable. In the npm CLI's publish command,oidc()runs before the code treats a missing credential asENEEDAUTH; the exchanged token is installed viaconfig.set(authTokenKey, token, 'cli'), which outranks the_authTokenlinesetup-nodewrites into the npmrc. An empty env-var-derived value is simply overridden. Publishing keeps working. id-token: writereaches the callee. sdk_publish.yaml grants it at workflow level and the pinned reusable workflow declares nopermissions:block, so it cannot reduce it. The 1.5.11 provenance attestation is the empirical proof.
The one thing worth documenting
The Speakeasy workflow file keeps token: $npm_token, and that line is now load-bearing even though the variable it names no longer exists.
workflow.Load in the sdk-gen-config library never expands environment references — it leaves Token as the literal string "$npm_token". Because that string is non-empty, Publishing.IsPublished() returns true, AddTargetPublishOutputs emits publish_typescript=true, and the publish-npm job runs. That is the only reason npm publishing still happens.
So if someone later cleans up that workflow file and deletes the token: $npm_token line — a very natural follow-up, since the secret is being retired — IsPublished() flips to false, the publish-npm job is skipped on the next release, and the Publish workflow still reports success. The only signal would be the agent-bump dispatch failing ~20 minutes later because the new version is not on npm. Nothing in the file says that line must stay.
I left an inline comment asking for a short comment there. Not a blocker, and not something this PR has to solve before merging — but it is the kind of silent failure that costs an afternoon later.
Risk
Risk: 🟡 Medium
Estimated impact: If this change is wrong, the next Publish run fails to publish to npm and blocks the SDK release chain (and the agent bump that follows it). Fully recoverable by re-adding the NPM_TOKEN secret and reverting this diff; detected within one release cycle.
| Dimension | Severity | Risk | Reasoning |
|---|---|---|---|
| Implementation risk | 🟨🟨 | Medium | The diff is three deletions and each is verified against the pinned action source; the residual risk is the undocumented dependency on the orphaned token: line. |
| Premise risk | 🟩 | Low | Every claim was independently confirmed: OIDC user on three published versions, required: false on both reusable workflows, and no npm_token read anywhere in the release path. |
| Estimated impact | 🟨🟨 | Medium | Worst case is a blocked npm release, not a bad publish — nothing reaches consumers, and a revert plus secret restore recovers it. |
| Risk Factor | Severity | Risk | Reasoning |
|---|---|---|---|
| Reversibility | 🟩 | Low | Reverting the diff and restoring the secret returns the previous state completely. |
| Detectability | 🟨🟨 | Medium | The Publish workflow can succeed while skipping npm publish, so the failure surfaces only when the downstream version check times out. |
| Blast radius | 🟨🟨 | Medium | All consumers of the SDK are affected if publishing stops, but no published artifact can be corrupted. |
| Data integrity | None | No persisted state is touched; a failed publish writes nothing to the registry. | |
| Financial exposure | None | No billing or revenue path is involved. | |
| Security and privacy exposure | 🟩 | Low | This removes a long-lived credential from CI and narrows to short-lived OIDC — a net reduction in exposure. |
| Propagation | 🟨🟨 | Medium | A missed publish propagates into the dependent agent repo and the monorepo bump via the dispatch workflow. |
| Availability | 🟨🟨 | Medium | Release availability depends on the publish path staying enabled. |
| Recovery cost | 🟩 | Low | One revert commit plus re-adding a secret; no reconciliation or backfill. |
| Time to correct | 🟩 | Low | Correctable on the next workflow run once the revert lands. |
…der (#1268) ## Summary Follow-up to #1267, from Perry's review there. Adds a comment only. Nothing changes at runtime. `targets.openrouter.publish.npm.token: $npm_token` now looks dead, because `NPM_TOKEN` is no longer passed. But it is what turns publishing on. sdk-gen-config does not expand `$npm_token`, and `Publishing.IsPublished("typescript")` returns true only when `NPM.Token != ""`. That makes the Speakeasy release step emit `publish_typescript=true`, which is what runs `publish-npm`. If the line is deleted, npm publishing is skipped silently and the `Publish` workflow still reports success. npm itself authenticates through trusted publishing (OIDC). Unverified: whether a later Speakeasy CLI rewrite of `workflow.yaml` (for example a `speakeasyVersion` bump) keeps YAML comments. Searched existing PRs; none matched. Link to Devin session: https://openrouter.devinenterprise.com/sessions/ab9f8df32f544a1eae4412b551a5f4b4 Open in Devin Desktop: https://openrouter.devinenterprise.com/desktop/session/ab9f8df32f544a1eae4412b551a5f4b4?variant=devin Requested by: @alexb-openrouter Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Summary
Removes
npm_token: ${{ secrets.NPM_TOKEN }}fromsdk_publish.yaml,sdk_generation.yamlandsdk_generation_for_spec_change.yaml, so that the long-livedNPM_TOKENsecret can be deleted. Publishing relies on npm trusted publishing (OIDC) only.@openrouter/sdk@1.5.11shows_npmUser: GitHub Actions <npm-oidc-no-reply@github.com>and has SLSA provenance. The token is passed today but OIDC is what gets used, so nothing needs to change on npmjs.com.sdk-publish.yaml@68f3bfepublish-npmjob runs plainnpm publishon a GitHub-hosted runner with Node24.x, and it inherits the caller'sid-token: write.npm_tokenisrequired: falsein both reusable workflows. WithNODE_AUTH_TOKENempty, npm uses OIDC.mode: prand never publish. They only passed the token through..speakeasy/workflow.yamlkeepstoken: $npm_token. In sdk-gen-config theNPMschema markstokenas required, and unlike PyPI there is nouseTrustedPublishingoption. It is only an env-var reference, and an unset variable resolves to empty.Not verified until the first release after merge: that Speakeasy's
releasestep accepts an empty$npm_token. If it fails, revert this PR.After a successful release: on npmjs.com, turn on "Require two-factor authentication and disallow tokens" for
@openrouter/sdk, revoke the npm token and delete theNPM_TOKENrepo secret.Link to Devin session: https://openrouter.devinenterprise.com/sessions/ab9f8df32f544a1eae4412b551a5f4b4
Open in Devin Desktop: https://openrouter.devinenterprise.com/desktop/session/ab9f8df32f544a1eae4412b551a5f4b4?variant=devin
Requested by: @alexb-openrouter