Skip to content

fix(actions): pin github-actions runtime image to the release version#353

Closed
Cre-eD wants to merge 1 commit into
mainfrom
fix/pin-gha-image-to-release
Closed

fix(actions): pin github-actions runtime image to the release version#353
Cre-eD wants to merge 1 commit into
mainfrom
fix/pin-gha-image-to-release

Conversation

@Cre-eD

@Cre-eD Cre-eD commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Problem

The four composite actions — deploy-client-stack, provision-parent-stack, cancel-stack, destroy — hardcode their runtime image as docker://simplecontainer/github-actions:latest.

A Docker-based action's image: field is a parse-time literal (GitHub Actions does not allow ${{ }} interpolation there). So when a consumer carefully pins the action git ref — e.g. uses: simple-container-com/api/.github/actions/deploy-client-stack@2026.6.14 — the pin only fixes the thin action.yml wrapper. The code that actually runs is whatever simplecontainer/github-actions:latest points to at job time.

Result: any release that re-tags :latest silently changes the runtime for every downstream consumer that believes it is pinned, defeating the entire purpose of version pinning.

This already bit production on 2026-06-30: 2026.6.17 was published and moved :latest at 07:48 UTC, and a downstream deploy that was pinned to an older action tag immediately started running the new image with no change on its side.

There's a second, latent bug: branch-preview.yaml already substitutes :staging:${VERSION}, but main never contained :staging (it was :latest), so that substitution was a no-op — preview builds shipped :latest too.

Fix

Unify on the :staging placeholder that branch-preview.yaml already expects:

  • action.yml ×4: :latest:staging. @main / dev now resolves to the maintained staging image (built and pushed by build-staging.yml), and branch-preview.yaml's existing :staging → version substitution finally applies.
  • welder.yaml tag-release: substitute :staging:${project:version} and commit before tagging, mirroring branch-preview.yaml. Prod release tags now ship action.yml pinned to their own version's image — the same image push.yaml already builds, signs, and attests.

After this, @2026.6.Xsimplecontainer/github-actions:2026.6.X (reproducible), @main:staging, preview tags ⇒ their preview version.

Compatibility

The :latest and :staging Docker tags are still published by welder.yaml / push.yaml / build-staging.yml, so anyone referencing them directly is unaffected. Only future release tags change behaviour; already-published tags (2026.6.17 and earlier) keep their :latest reference and are not rewritten.

Test plan

  • All five YAML files parse.
  • Simulated the tag-release sed with VERSION=2026.6.18image: 'docker://simplecontainer/github-actions:2026.6.18'.
  • First release after merge: confirm the 2026.6.x tag's action.yml references :2026.6.x (not :latest), and that the tagged commit's image is signed/attested.

Follow-up (not in this PR)

Add a release-gate check that fails if a pushed release tag's action.yml still references :staging/:latest, so this can't silently regress.

The four composite actions (deploy-client-stack, provision-parent-stack,
cancel-stack, destroy) hardcoded image docker://simplecontainer/github-actions:latest.

A Docker-based action's image field is a parse-time literal with no expression
interpolation, so pinning the action git ref (e.g. @2026.6.14) only pinned the
thin action.yml wrapper. The runtime image still floated to whatever :latest
pointed at. Any release that re-tags :latest therefore silently changed the
runtime for every downstream consumer that believed it was pinned. This already
bit a PAY-SPACE deploy on 2026-06-30 when 2026.6.17 moved :latest mid-day.

Fix unifies on the :staging placeholder that branch-preview.yaml already
expects:

- action.yml x4: :latest -> :staging, so @main resolves to the maintained
  staging image and branch-preview.yaml's existing :staging -> version
  substitution actually applies (previously a no-op, so previews shipped
  :latest too).
- welder.yaml tag-release: substitute :staging -> :${project:version} and
  commit before tagging, mirroring branch-preview.yaml. Prod release tags now
  ship action.yml pinned to their own version's image, which is the image
  push.yaml already builds and signs.

The :latest and :staging Docker tags are still published, so any consumer
referencing them directly is unaffected.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
@github-actions

Copy link
Copy Markdown

Semgrep Scan Results

Repository: api | Commit: 85bca66

Check Status Details
⚠️ Semgrep Warning 1 warning(s), 1 total

Scanned at 2026-06-30 09:49 UTC

@github-actions

Copy link
Copy Markdown

📊 Statement coverage

Measured on the documented included set (see docs/TESTING.md → Coverage scope). Observe-only — no regression gate is enforced yet.

Scope This PR main baseline Δ
Included set (Gold-tier denominator) 90.3% 90.3% +0.0 pp
Full set (whole repo, transparency) 27.9% 27.9% +0.0 pp

Baseline: main @ dea14c1

@github-actions

Copy link
Copy Markdown

Security Scan Results

Repository: api | Commit: 85bca66

Check Status Details
✅ Secret Scan Pass No secrets detected
✅ Dependencies (Trivy) Pass 0 total (no critical/high)
✅ Dependencies (Grype) Pass 0 total (no critical/high)
📦 SBOM Generated 523 components (CycloneDX)

Scanned at 2026-06-30 09:50 UTC

@Cre-eD

Cre-eD commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Closing — handling this on the PAY-SPACE consumer side instead of changing the upstream release mechanism.

@Cre-eD Cre-eD closed this Jun 30, 2026
@Cre-eD Cre-eD deleted the fix/pin-gha-image-to-release branch June 30, 2026 10:01
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