Repository navigation
fix: bind release builds to the workflow commit - #197
Conversation
Signed-off-by: lucarlig <luca.carlig@ibm.com>
msureshkumar88
left a comment
There was a problem hiding this comment.
Approve: no blocking correctness, security, or compatibility defects found in PR #197 at 04d62d92c46d2f2932c000a3aadfeb747d1b8fd6.
Reviewed the three changed files against base bee7a6965a341abc6f51eecb505a32fda0862cdd, plus their CI callers, catalog release validation, publishing gates, and relevant repository tests. Surrounding code informed the assessment; unrelated existing behavior was not treated as a new defect.
The underlying problem is real: manually supplied tags previously selected executable source independently of the triggering workflow commit. Code could run inside the triggering ref's cache scope before the PyPI main-ancestry gate was evaluated. Checking ancestry only at publication cannot prevent that execution. Pinning every checkout to the immutable workflow commit fixes the ownership boundary; checking that an existing tag peels to the same commit also prevents misleading release metadata. This is a necessary, appropriately scoped fix. Disabling caches alone would not enforce source identity.
| Requested check | Assessment |
|---|---|
| Issue and implementation match | No linked GitHub issue. The PR explicitly references CodeQL alerts #1–#12. Verified the actual alerts: actions/cache-poisoning/poisonable-step, classified as CWE-349, Acceptance of Extraneous Untrusted Data With Trusted Data. Base analysis reports 12 findings; analysis of this exact PR head reports zero. |
| UI/backend/schema | CI/release security change only. No product UI, backend runtime, Alembic migration, database schema, or stored-data change. No schema migration is necessary. |
| Minimal required scope | Both language workflows are affected. All eight checkouts are pinned, the dynamic checkout_ref output is removed, and manual-dispatch documentation is updated. The existing catalog and publishing controls are reused. No new dependencies or speculative abstraction. |
| Security and trust boundaries | Existing tags are validated and peeled with ^{commit} before their commit is compared with GITHUB_SHA. Mismatched tags fail closed before downstream jobs. Unpublished PR validation can use a nonexistent tag; publication cannot use that exception. Inputs reach shell through environment variables and quoted expansions. Existing read-only workflow permissions and publish-job OIDC scope are retained. No new exploitable weakness identified. |
| Performance | No request-path impact. The added checks are bounded Git operations; remote lookup/fetch already existed. No material performance regression or justified performance refactor found in this diff. |
| Refactoring/dead code | Removal of the unused checkout-selection output and temporary variables is coherent; no remaining readers were found. Two similar resolvers predate this PR. Moving them into a shared component solely for this fix would broaden scope without a correctness benefit. |
| Scope/versioning | Only the two affected workflows and relevant release documentation change. No unrelated edits or plugin-core changes; no plugin version bump needed. |
| Documentation/logs/errors | The manual-dispatch requirement and unpublished-PR exception are documented. Invalid refs, missing tags, and mismatches produce failures. One small diagnostic improvement is listed below. |
The intentional breaking change is confined to release automation. A manual or reusable-workflow caller that starts at commit A while supplying an existing tag at commit B now fails, including when publishing is disabled. The migration is to dispatch with --ref <tag> -f tag=<tag> so the workflow commit and tag agree. Both repository automatic release callers already supply --ref "${tag}", so they satisfy the new contract. The blast radius is manual release/retry tooling and external reusable-workflow callers for both language workflows; installed plugin consumers, APIs, hooks, configuration, and database contents are unaffected. Keep the documented dispatch pattern rather than restoring arbitrary tag checkout as a workaround. Existing workflow runs and historical refs containing the old workflow are not retroactively hardened by merging this change.
Nonblocking improvements:
- Retain a reproducible behavioral regression check for matching lightweight/annotated tags, mismatch rejection, missing-tag PR validation, and its publishing restriction. Existing catalog tests cover release tag format, language routing, and version matching, but do not execute the new commit-identity decision. Test real behavior rather than exact YAML text or step ordering. The temporary executable checks below provide review evidence, but are not committed regression coverage.
- In both resolver mismatch messages, include the resolved
tag_shaalongside the expectedGITHUB_SHA(Python lines 83–86, Rust lines 83–86). This makes incorrect dispatch or moved-tag incidents easier to diagnose without exposing secrets.
Independent verification:
make plugins-validate: catalog validation and all 41 repository tests passed.- Executed the actual complete resolver scripts with the real catalog against temporary local Git repositories: 22 scenarios passed across both workflows. Covered matching and mismatched lightweight/annotated tags, missing manual tags, unpublished PR validation, rejection of publishing through the missing-tag exception, matching feature tags with the main-ancestry result preserved, tag pushes, invalid refs, and mismatched existing tags during unpublished PR validation. Verified failed cases emit no job outputs and resolution never changes the checked-out commit.
- Both workflow files parse as YAML; explicit Bash scripts pass
bash -n; all eight checkouts use${{ github.sha }};git diff --checkpassed. - GitHub checks are successful where executed. Both
release-validationjobs were skipped on this PR, so ordinary CI success alone does not exercise these changed resolver branches. The CodeQL analysis for the reviewed head contains zero results.
No full publish, hosted runner matrix, or gateway E2E execution was performed. Gateway E2E is not the owning layer for this workflow-only change. An unpublished hosted artifact-validation run would provide additional GitHub-event/runner integration evidence; local Git scenarios do not prove that platform behavior. actionlint was not installed locally, so its reported author validation was not independently repeated. No numerical line-coverage claim is made for embedded workflow shell.
A manually supplied release tag could make the Python and Rust release workflows execute code from a different commit within the triggering workflow's cache scope. The existing main-ancestry check only gated PyPI publishing, after the builds and artifact tests had already run. This addresses all 12 open CodeQL
actions/cache-poisoning/poisonable-stepalerts (#1–#12).Pin every checkout to
github.shaand reject an existing input tag unless its peeled commit matches that SHA. The input no longer controls a checkout ref. Manual releases must dispatch on the release tag, matching the existing CI dispatch behavior. PR artifact validation still permits a nonexistent tag with publishing disabled, and the PyPI main-ancestry gate remains in place. Document the manual dispatch requirement in DEVELOPING.md.Validation:
make plugins-validate: catalog validation and all 41 repository tests passed.actionlinton both release workflows passed.The alerts are actionable and are addressed by this change; none were dismissed.