Skip to content

fix: wrap bare secret-name tokens in setup-cli step - #189

Merged
joshua-temple merged 1 commit into
mainfrom
fix/setup-cli-token
Jun 16, 2026
Merged

joshua-temple merged 1 commit into
mainfrom
fix/setup-cli-token

Conversation

@joshua-temple

Copy link
Copy Markdown
Collaborator

Problem

A manifest that sets release_token (or state_token, or a notify token) to a bare secret name like CASCADE_STATE_TOKEN made the generator emit that name verbatim into the Setup CLI step:

- name: Setup CLI
  uses: stablekernel/cascade/.github/actions/setup-cli@v1...
  with:
    token: CASCADE_STATE_TOKEN   # bare literal, not an expression

The setup-cli action then runs gh release download with GH_TOKEN set to the literal string CASCADE_STATE_TOKEN, which GitHub rejects with 401 Bad credentials. Repos with a bare release_token (e.g. cascade-example-primary) fail; repos that omit it fall back to ${{ secrets.GITHUB_TOKEN }} and work.

Root cause

internal/config/types.go: GetReleaseToken, GetStateToken, and NotifyConfig.GetToken returned a configured value verbatim, assuming it was always a full ${{ ... }} expression. The release_token field is documented as a 'GitHub secret name', so an operator-written bare name was passed straight through. No wrapper was ever stripped; the bare name was simply never wrapped.

Fix

Add normalizeTokenExpression: a full ${{ ... }} expression passes through, an unwrapped context form (secrets.X, vars.X) is wrapped, and a bare name is treated as a secret (${{ secrets.NAME }}). The Setup CLI download targets a public release, so the wrapped expression resolves. Wired into all three token getters.

Verification

  • Failing-first unit tests for normalizeTokenExpression and the three getters.
  • Generator regression test: a primary-shaped manifest (bare release_token) now emits token: ${{ secrets.CASCADE_STATE_TOKEN }}, and a guard asserts no emitted token:/GH_TOKEN: line is a bare identifier. Default (no release_token) still emits ${{ secrets.GITHUB_TOKEN }}.
  • e2e scenario orchestrate/22-release-token-bare-secret-name.yaml.
  • go build ./... && go test ./... (1375 pass) and golangci-lint run ./... green; e2e module builds and vets clean.
  • Dogfood regen produces no drift (own manifest uses an already-wrapped state_token, no release_token).

A manifest that sets release_token (or state_token, or a notify token) to a
bare secret name such as CASCADE_STATE_TOKEN caused the generator to emit that
name verbatim into the Setup CLI step (token: CASCADE_STATE_TOKEN). The setup-cli
action then ran gh release download with GH_TOKEN set to the literal string,
which GitHub rejected with 401 Bad credentials.

Normalize token values in GetReleaseToken, GetStateToken, and NotifyConfig.GetToken:
a full ${{ ... }} expression passes through, an unwrapped context form (secrets.X,
vars.X) is wrapped, and a bare name is treated as a secret. The Setup CLI download
targets a public release, so the wrapped expression resolves correctly.

Adds unit coverage for the normalizer and the token getters, a generator
regression test asserting no emitted token is a bare identifier, and an e2e
scenario exercising a bare release_token.

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
@joshua-temple
joshua-temple merged commit 3be73d1 into main Jun 16, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant