ci: replace secrets inherit with explicit secrets in release pipeline - #1196
ci: replace secrets inherit with explicit secrets in release pipeline#1196ital0 wants to merge 3 commits into
Conversation
Limit reusable workflows to the release secrets they need. Keep declarations optional to preserve existing behavior when repository secrets are missing.
- make each least-privilege release secret contract self-describing - keep every reusable workflow declaration aligned with its exact needs
Semgrep Security ScanNo security issues found. |
|
Preview environment deployed 🚀
Stack: Auto-destroys on PR close/merge. Login via the bundled Keycloak realm — |
PR Metrics
Updated Fri, 07 Aug 2026 00:37:55 GMT · run #2621 |
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: f03dc595a132 · mode: single · deferred 0 item(s) already reported by other bots (best-effort dedup)
| uses: ./.github/workflows/desktop-release.yml | ||
| secrets: inherit | ||
| secrets: | ||
| APPLE_DEVELOPER_CERTIFICATE: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE }} |
There was a problem hiding this comment.
🚫 Blocking — Dropping secrets: inherit could break environment-scoped release secrets
Heads up — I want to flag a subtle behavior change here before it bites a release. Every job that actually consumes these signing secrets declares environment: thunderbolt_release (desktop build, ios build_and_deploy_ios, android build_and_deploy_android, and version-bump). A job that calls a reusable workflow can't declare an environment, so when release.yml resolves ${{ secrets.APPLE_DEVELOPER_CERTIFICATE }} (and the RELEASE_APP_, ANDROID_, TAURI_* keys) it evaluates them in the caller's context, which sees only repository/organization secrets — not the thunderbolt_release environment. With the old secrets: inherit, the reusable workflow resolved those secrets inside its own environment-gated jobs, so environment secrets worked. If any of these are stored as environment secrets, they'll now be forwarded as empty strings and the if [ -z "$..." ] guards will abort every platform build. Interestingly, CRABNEBULA_CLOUD_API_KEY is used in desktop's non-environment draft/publish jobs, which tells me it's repo-level — but that doesn't guarantee the signing keys are. Could we confirm where the Apple/Android/Tauri/RELEASE_APP secrets actually live? If they're environment-scoped, the safest move is to keep secrets: inherit (it's still fully compatible with the new documented workflow_call.secrets declarations) rather than switching to explicit passing.
There was a problem hiding this comment.
🔭 thunder-deep-review (advisory)
Complements the other bots — surfaces only what they did not flag. Never approves, never requests changes, never gates merge.
head: 84c40cb2924d · mode: single · deferred 0 item(s) already reported by other bots (best-effort dedup)
| uses: ./.github/workflows/version-bump.yml | ||
| secrets: inherit | ||
| secrets: | ||
| RELEASE_APP_CLIENT_ID: ${{ secrets.RELEASE_APP_CLIENT_ID }} |
There was a problem hiding this comment.
🚫 Blocking — Explicit ${{ secrets.* }} in release.yml can't read the thunderbolt_release environment secrets
Heads up — I think this switch from secrets: inherit to explicit passing may silently break the release. The CHANGELOG notes these credentials were moved to environment secrets (#752: "Use environment secrets and rename RELEASE_APP_ID to RELEASE_APP_CLIENT_ID"), and every job in the called workflows declares environment: thunderbolt_release. But the jobs here in release.yml (version_bump, desktop, ios, android) do NOT declare that environment, and ${{ secrets.X }} only resolves environment secrets inside a job that targets that environment. So these expressions likely evaluate to empty strings and get passed down as empty.
Under the old secrets: inherit, this worked because each reusable job pulled the environment secrets itself via its own environment: thunderbolt_release declaration. With explicit passing, version_bump feeds empty RELEASE_APP_CLIENT_ID/RELEASE_APP_PRIVATE_KEY into actions/create-github-app-token (which has no [ -z ] guard and will error), failing the very first job — and since every other job is gated on needs.version_bump.result == 'success', the whole release becomes a no-op. The platform jobs would hit the same wall via their if [ -z "$APPLE_..." ]; exit 1 guards.
Could we either add environment: thunderbolt_release to these caller jobs so ${{ secrets.* }} can see the env secrets, or confirm with a nightly dry-run that the values still arrive non-empty before merging? Direct workflow_dispatch of the reusable workflows would still work (they target the environment themselves), which can mask this during testing.
Fixes code-scanning alerts #82, #83, #84, #85 (
yaml.github-actions.security.secrets-inherit).Changes
release.ymlpassed all repository secrets to its four called workflows viasecrets: inherit. Each reusable workflow now declares exactly the secrets it references inon.workflow_call.secrets, andrelease.ymlpasses exactly those:version-bump.ymlRELEASE_APP_CLIENT_ID,RELEASE_APP_PRIVATE_KEYdesktop-release.ymlCRABNEBULA_CLOUD_API_KEY, Tauri updater key pair (2),APPLE_TEAM_IDios-release.ymlAPPLE_TEAM_IDandroid-release.ymlGOOGLE_PLAY_SERVICE_ACCOUNT_JSONThe
clijob already passed no secrets and is untouched.GITHUB_TOKENis automatic and is neither declared nor passed.Behavior preservation
All declarations use
required: false, which reproduces theinheritsemantics exactly: a secret that doesn't exist in the repo resolves to an empty string instead of failing the workflow. The nightly release should behave identically — the only change is that a compromised called workflow can no longer read unrelated repository secrets.Checked:
release.ymlis the only caller of these four workflows, and none of them calls another reusable workflow withsecrets: inherit.Validation
actionlinton all five files: zero new findings vsmain(39 pre-existing shellcheck info-level findings, unchanged). Secret cross-check: referenced secrets == declared secrets == passed secrets, per workflow.