Skip to content

ci: keep the publish credential inside the release environment - #6170

Merged
max-sixty merged 2 commits into
mainfrom
publish-static-environments
Aug 9, 2026
Merged

ci: keep the publish credential inside the release environment#6170
max-sixty merged 2 commits into
mainfrom
publish-static-environments

Conversation

@max-sixty

@max-sixty max-sixty commented Aug 8, 2026

Copy link
Copy Markdown
Member

publish-js and publish-to-cargo each ran on every event, holding id-token: write throughout, and named the release environment only when github.event_name == 'release'. On every other event they minted an OIDC token from a run nothing gates.

That token is accepted by a registry whose trusted-publisher config doesn't pin an environment — the claims it checks are the repository and the workflow filename, both of which an ungated run satisfies. release.yaml accepts workflow_dispatch, so an account with write access can dispatch a branch carrying its own copy of this file and publish with it. The environment is the only part of the run it can't fake, which is why the credential has to live inside one.

So each pair is now two jobs:

  • publish-js / publish-to-cargoif: github.event_name == 'release', environment: release, and the OIDC permission.
  • test-publish-js / test-publish-to-cargo — the smoke tests nightly.yaml runs on PRs, with no id-token: write at all.

The js build steps move into .github/actions/build-js rather than being duplicated across the two jobs, following build-prqlc and build-python. The nightly path filter in tests.yaml gains .github/actions/build-*/** to go with that: it watched release.yaml but nothing under .github/actions/, so a change to the extracted steps would no longer have triggered the smoke test that runs them. build-prqlc and build-python had the same gap.

Two consequences worth naming:

  • The npm smoke test drops --provenance, which needs an OIDC token. It still checks that the package builds and that npm publish accepts it.
  • A static environment: is also readable by tend, whose credential-environments check declines to judge a job whose environment is an expression. This takes prql from SKIP to PASS on it.

This only closes the hole if the registries pin the environment too, which is configured on their side: npm, crates.io and PyPI each have an optional environment field in the trusted publisher config, and a blank one accepts any run of this workflow.

This was written by Claude Code on behalf of max-sixty

publish-js and publish-to-cargo held `id-token: write` on every event while naming the `release` environment only on releases, so an ungated run could mint a token that a registry with no environment pinned would accept — reachable by dispatching this workflow from a branch.

Each is now release-only with a static `environment: release`, and the off-release smoke tests are separate jobs with no OIDC permission. The js build moves into a composite action rather than being duplicated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@prql-bot prql-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split reads correctly to me — the pairs match the shape publish-python already uses (if: github.event_name == 'release' + static environment: release), and nothing else in the repo references the old job names, so the rename is contained. env.version and hashFiles('./Cargo.lock') inside a composite action are both already proven by build-prqlc and time-compilation, and every run step in the new action carries shell: bash.

One consequence of the extraction worth naming: the nightly path filter in tests.yaml lists .github/workflows/release.yaml but nothing under .github/actions/, so a PR that only edits .github/actions/build-js/action.yaml won't set steps.changes.outputs.nightly, won't run the nightly job, and therefore won't run test-publish-js. Those same steps live in release.yaml today, which the filter does watch — so the smoke-test coverage on them is lost by the move. The concrete case is a Dependabot bump of actions/setup-node or Swatinem/rust-cache inside the new action landing without the npm publish path having been exercised. Adding .github/actions/build-*/** to the nightly filter closes it — all of .github/actions/** would too, but that also drags in tend-setup changes, which don't need a nightly run. build-prqlc and build-python have the same gap already, so the one filter entry covers them as well. Happy to push it here if you'd rather not carry a separate PR.

The `nightly` path filter watched `release.yaml` but nothing under `.github/actions/`, so moving the js build steps into a composite action would have taken them out of the smoke test. `build-prqlc` and `build-python` had the same gap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@max-sixty
max-sixty merged commit d295ae8 into main Aug 9, 2026
85 of 86 checks passed
@max-sixty
max-sixty deleted the publish-static-environments branch August 9, 2026 00:08
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