Skip to content

feat(release): gate the publish transition on OIDC, so a registry token cannot be the way in - #356

Merged
wenzowski merged 1 commit into
mainfrom
wenzowski/cloud-109-switch-release-plz-to-oidc-trusted-publishing
Aug 12, 2026
Merged

wenzowski merged 1 commit into
mainfrom
wenzowski/cloud-109-switch-release-plz-to-oidc-trusted-publishing

Conversation

@wenzowski

Copy link
Copy Markdown
Contributor

CLOUD-109 asks for two things. Measured against the repository, neither was a
change that did anything
:

Acceptance clause State on main
release-plz workflow uses crates.io OIDC trusted publishing (permissions: id-token: write) release-plz.toml sets [workspace] publish = false. mise run release never contacts a registry, so there is no publish for a trusted publisher to authenticate.
The stored CARGO_REGISTRY_TOKEN secret is removed once trusted publishing is confirmed working That secret does not exist. gh api repos/button-inc/batten/actions/secrets returns exactly one name, RELEASE_PLZ_TOKEN, and a tree-wide grep finds CARGO_REGISTRY_TOKEN nowhere.

Adding id-token: write today would grant a capability no step uses — dead
config that zizmor's excessive-permissions audit reads as a finding, and the
syntax of trusted publishing without its semantics.

And the decision behind it is not stale: CLOUD-205 (Done, founder-confirmed)
keeps the crate unpublished. So the deliverable is the transition, which
needs no credential and is buildable now.

What lands

mise run publish-credential-check, wired into the hk gate. One question — can
this repository publish with a long-lived credential?
— and the load-bearing
rule is an implication rather than a literal:

  • A registry credential is refused wherever it appears in a workflow:
    CARGO_REGISTRY_TOKEN, the CARGO_REGISTRIES_*_TOKEN alternate-registry form,
    and a hand-rolled cargo login. Green today by construction — it is the
    ratchet that keeps it green.
  • publish = true requires id-token: write in the same commit. While
    publishing is off the permission is not required, because requiring it would
    require the dead config above. Turning publishing on is therefore a change that
    cannot skip OIDC.
  • An absent publish key reads as true, because that is release-plz's own
    default. Reading a missing key as false would make the gate silent in exactly
    the case it exists for; a test pins it.

Output is pointer-only (rule 4) and the matched line is deliberately never
printed — the class of thing this gate looks for is the class that must not reach
a log. A test asserts the finding names path:line and a rule id and nothing
else. Exit 0/1/2 per house-style §7.

The release-plz.yml header separated the two credentials it had been
conflating, since that conflation is what put this issue and CLOUD-94 on the same
thread.

What a human must still do — none of it possible from a session

  • Register a crates.io trusted publisher (crate settings → Trusted Publishing
    → GitHub, owner button-inc, repo batten, workflow release-plz.yml).
    Impossible before the crate is published at all.
  • Flip publish = true. CLOUD-205's decision to revisit, not this ticket's to
    pre-empt. The gate is what makes that flip safe whenever it comes.
  • Retire RELEASE_PLZ_TOKEN — a GitHub App on button-inc with
    contents: write + pull_requests: write, and APP_ID / APP_PRIVATE_KEY
    secrets. crates.io OIDC does not reach this credential, so CLOUD-94 must not
    be closed on the strength of this PR. That is CLOUD-94's scope and it already
    specifies the fix.

Verification

  • mise run publish-credential-check green on the tree (publish=false, 16
    workflows clean).
  • tests/publish-credential-check.bats (13) covers both directions: each of the
    three credential spellings, publish = true without the permission (fails) and
    with it (passes), the permission named only in a comment (still fails), the
    absent-key default, and exit 2 for an unreadable config, an empty workflow
    directory, and publishing on with no release workflow.
  • mise run verify green.

Refs: CLOUD-109

@linear-code

linear-code Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
CLOUD-109 Switch release-plz to OIDC trusted publishing

Avoid a long-lived registry token.

Scope / acceptance

  • release-plz workflow uses crates.io OIDC trusted publishing (permissions: id-token: write).
  • The stored CARGO_REGISTRY_TOKEN secret is removed once trusted publishing is confirmed working.

Review in Linear

@wenzowski
wenzowski marked this pull request as ready for review August 12, 2026 05:17
…en cannot be the way in

CLOUD-109 asked to switch release-plz to crates.io OIDC trusted publishing and
to delete CARGO_REGISTRY_TOKEN. Measured: `release-plz.toml` sets
`publish = false`, so release-plz never contacts a registry, and that secret
does not exist — the repository carries exactly one Actions secret,
RELEASE_PLZ_TOKEN, and a tree-wide grep finds the registry name nowhere. Adding
`id-token: write` today would grant a capability no step uses, which zizmor's
excessive-permissions audit reads as a finding.

So the deliverable is the transition, not an edit to today's workflow.
publish-credential-check refuses a registry token or a hand-rolled cargo login
in any workflow, and refuses `publish = true` unless the release job carries
`id-token: write` in the same commit. An absent `publish` key reads as true,
because that is release-plz's default and a gate silent in exactly its own case
is no gate.

The workflow header now separates the two credentials it kept conflating:
crates.io OIDC retires a registry token and does nothing for RELEASE_PLZ_TOKEN,
whose replacement is an org-owned GitHub App (CLOUD-94).

Refs: CLOUD-109
@wenzowski
wenzowski force-pushed the wenzowski/cloud-109-switch-release-plz-to-oidc-trusted-publishing branch from 4338d05 to ac10c3a Compare August 12, 2026 05:19
@sonarqubecloud

Copy link
Copy Markdown

@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

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