Skip to content

ci: add release preflight to verify deploy credentials before the build#641

Merged
bundolee merged 1 commit into
mainfrom
ci/release-preflight
Jul 14, 2026
Merged

ci: add release preflight to verify deploy credentials before the build#641
bundolee merged 1 commit into
mainfrom
ci/release-preflight

Conversation

@bundolee

@bundolee bundolee commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Objective

A release only touches its deploy credentials in the very last steps, after a ~30-40 min build. So an expired npm token, a revoked GitHub PAT, or a mis-scoped Maven login isn't discovered until the build has already finished — wasting the wait and risking a half-published release where some registries got the artifact and others didn't.

Approach

Add a preflight that actually authenticates each credential up front and gates the release on it.

  • One real authenticated call per target: npm whoami, Maven Central Portal, a GPG sign+verify, a GitHub repo push-permission check, and a PyPI OIDC token mint.
  • Collect-then-fail: one run reports every broken credential, not just the first.
  • Lives in its own reusable workflow (preflight.yml, workflow_call + workflow_dispatch), so it both gates release.yml via needs and can be run on its own from the Actions tab to check credentials in ~1 min without triggering a build.

Secrets are handled defensively — read only from the environment; fed to curl via a stdin config (never argv, so nothing leaks through /proc/<pid>/cmdline); derived values are ::add-mask::'d; the GPG private key is imported into an ephemeral keyring killed and wiped on exit; and credentials are passed to the reusable workflow explicitly (not secrets: inherit), so preflight only ever sees the six secrets it uses.

Note on the Maven check: credentials are base64-encoded in the shell and sent as an Authorization: Basic header, not via curl's user = config. curl's config quoting mangles values containing ", \, or whitespace — which would send the wrong credential and misreport a valid login as a 401. The base64 blob is [A-Za-z0-9+/=] only, so it's safe.

Evidence

Ran the script locally against the live endpoints with deliberately wrong credentials, and scanned all output for secret material.

Before: credential failures surfaced only after the full build, at deploy time.
After: preflight fails in ~1 min and names every broken credential; release does not start.

Scenario Expected Actual
No secrets set all FAIL, clean exit 1 5/5 FAIL, exit 1, no crash
Wrong Maven creds (live call) HTTP 401, labeled bad creds HTTP 401 — bad credentials
Maven pass with " \ : and spaces credential sent intact decoded credential byte-identical
Wrong GitHub PAT (live call) no push access no push access to …/opendataloader.org
Bad GPG key import fails, not a false pass GPG signing (import) FAIL
CI + RUNNER_TEMP unset refuse (don't write key to /tmp) hard-fail before import
Leak scan (stdout+stderr+summary) zero secret material no raw or base64 credential found

shellcheck and actionlint both report clean on all three files.

Known limitations (by design)

  • Auth-only, no publish dry-run — proves credentials authenticate, not that publish scope is complete.
  • PyPI check verifies OIDC token issuance (id-token: write), not trusted-publisher registration (not checkable without a publish).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added preflight validation for release credentials before lengthy build and publishing steps.
    • Added authentication checks for npm, Maven Central, GPG signing, GitHub, and PyPI.
    • Added a clear pass/fail summary of credential checks in the workflow results.
  • Bug Fixes

    • Releases now stop early when required deployment credentials are invalid, reducing failed release attempts.

Objective: A release only touches its deploy credentials in the very last
steps, after a ~30-40 min build. So an expired npm token, a revoked GitHub
PAT, or a mis-scoped Maven login is not discovered until the build has
already finished — wasting the wait and risking a half-published release
where some registries got the artifact and others did not.

Approach: Add a preflight that actually authenticates each credential up
front and gates the release on it. It makes a real authenticated call per
target (npm whoami, Maven Central Portal, a GPG sign+verify, a GitHub repo
push-permission check, and a PyPI OIDC token mint) and collects all results
before failing, so one run tells you every credential that is broken — not
just the first. It lives in its own reusable workflow (preflight.yml) so it
both gates release.yml via `needs` AND can be run on its own from the
Actions tab to check credentials in ~1 min without triggering a build.

Secrets are handled defensively: read only from the environment, fed to curl
via a stdin config (never argv, so nothing leaks through /proc/<pid>/cmdline),
derived values are ::add-mask::'d, and the GPG private key is imported into an
ephemeral keyring that is killed and wiped on exit. Credentials are passed to
the reusable workflow explicitly (not `secrets: inherit`) so preflight only
ever sees the six secrets it uses.

Evidence: Ran the script locally against the live endpoints with deliberately
wrong credentials, and scanned all output for secret material.

Before: credential failures surfaced only after the full build, at deploy time.
After: preflight fails in ~1 min and names every broken credential; release
does not start.

| Scenario | Expected | Actual |
|----------|----------|--------|
| No secrets set | all FAIL, clean exit 1 | 5/5 FAIL, exit 1, no crash |
| Wrong Maven creds (live call) | HTTP 401, labeled bad creds | "HTTP 401 - bad credentials" |
| Maven pass with " \ : and spaces | credential sent intact | decoded credential byte-identical |
| Wrong GitHub PAT (live call) | no push access | "no push access to .../opendataloader.org" |
| Bad GPG key | import fails, not a false pass | "GPG signing (import)" FAIL |
| CI + RUNNER_TEMP unset | refuse (do not write key to /tmp) | hard-fail before import |
| Leak scan (stdout+stderr+summary) | zero secret material | no raw or base64 credential found |

shellcheck and actionlint both report clean on all three files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bundolee
bundolee merged commit ff5891c into main Jul 14, 2026
6 checks passed
@bundolee
bundolee deleted the ci/release-preflight branch July 14, 2026 09:53
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d0f887a8-59ea-4493-9e9a-eb064c52921a

📥 Commits

Reviewing files that changed from the base of the PR and between 2bd7466 and 42377b9.

📒 Files selected for processing (3)
  • .github/workflows/preflight.yml
  • .github/workflows/release.yml
  • scripts/preflight.sh

Walkthrough

Adds a reusable release preflight workflow and script that authenticate deployment credentials before release execution. The release workflow now waits for preflight completion, while the script aggregates npm, Maven, GPG, GitHub, and PyPI authentication results.

Changes

Release preflight gate

Layer / File(s) Summary
Workflow entrypoints and release gate
.github/workflows/preflight.yml, .github/workflows/release.yml
Adds reusable and manually dispatched preflight execution, explicit secret passing, minimal permissions, and a release dependency on the preflight job.
Credential authentication checks
scripts/preflight.sh
Checks npm, Maven Central, GPG signing, GitHub push permissions, and PyPI OIDC token issuance without printing secret values.
Cleanup, summary, and exit status
scripts/preflight.sh
Runs all checks, writes the GitHub Actions summary, removes temporary GPG state, and exits with the aggregated failure status.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReleaseWorkflow
  participant PreflightWorkflow
  participant PreflightScript
  participant CredentialServices
  ReleaseWorkflow->>PreflightWorkflow: invoke with selected secrets
  PreflightWorkflow->>PreflightScript: run preflight checks
  PreflightScript->>CredentialServices: authenticate npm, Maven, GPG, GitHub, and PyPI
  CredentialServices-->>PreflightScript: return authentication results
  PreflightScript-->>PreflightWorkflow: return aggregated status
  PreflightWorkflow-->>ReleaseWorkflow: allow or block release job
Loading

Suggested reviewers: maximplusov, lonelymidoriya, hnc-jglee, hyunhee-jo


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

1 participant