Skip to content

ci: merge docker-image-2.yml into release.yml - #1500

Open
mxsrc wants to merge 2 commits into
mainfrom
ci-release-workflow-merge
Open

mxsrc wants to merge 2 commits into
mainfrom
ci-release-workflow-merge

Conversation

@mxsrc

@mxsrc mxsrc commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Merges docker-image-2.yml into release.yml so the PyPI version and every published image tag (docker.io, ECR, Quay) are derived from one validated release identifier instead of two independently-reimplemented ones — a release tag that doesn't match <major>.<minor>[.<patch>[.<hotfix>]][-<suffix>] now fails the workflow before anything publishes, rather than silently producing a tag nothing downstream can resolve back to a branch. Publishing on a release is also now unconditional: dropped the per-registry toggle inputs and the workflow_dispatch trigger, since a one-off partial push to a single registry is something done by hand, not through CI.

@mxsrc
mxsrc requested a review from Hamdy-khader October 18, 2026 15:20
@github-actions github-actions Bot added the target/main Targets the development branch label Oct 18, 2026
Comment thread .github/workflows/release.yml Fixed
Comment thread .github/workflows/release.yml Fixed
The PyPI version and every published image tag now come from one
validated release identifier instead of two independently-reimplemented
ones. Publishing on a release is also unconditional -- dropped the
per-registry toggle inputs and the dispatch trigger; a manual partial
push is done by hand outside this workflow.
@mxsrc
mxsrc force-pushed the ci-release-workflow-merge branch from 190845d to cdc45be Compare October 19, 2026 08:45

@Hamdy-khader Hamdy-khader left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The consolidation is right and the direction is good — one release trigger instead of two racing workflows, one derived identifier instead of two re-implementations. Few findings worth fixing before merge...

Comment thread .github/workflows/release.yml Outdated
- name: Resolve release identifier
id: resolve
run: |
tag="${{ github.event.release.tag_name }}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${{ }} is textually substituted into the shell script before bash parses it, so the regex guard on line 31 never sees a crafted tag — it sees whatever the injected code left behind. Git ref rules permit ",
$, (, ), ;, |, &, so a tag name like a";echo "tag=anything-at-all" >> $GITHUB_OUTPUT;exit 0;# is a legal tag that terminates the assignment, writes an arbitrary tag output, and exits 0 — the regex block
is never reached, and the forged tag flows straight into publish-pypi and publish-image, both of which hold Docker Hub / ECR / Quay / PyPI credentials.

This isn't new (docker-image-2.yml:36 had the same echo "TAG=${{github.event.release.tag_name}}"), but this PR's entire premise is "validate the release identifier once, centrally", and in its current form
the validation is the one thing an attacker-shaped tag can step around. Fix is one line:

    - name: Resolve release identifier
      id: resolve
      env:
        tag: ${{ github.event.release.tag_name }}
      run: |
        if [[ ! "$tag" =~ ^[0-9]+(\.[0-9]+){1,3}(-[A-Za-z0-9]+)?$ ]]; then

Same treatment for ${{ env.TAG }} in the Report published tags step (release.yml:193-195) — lower risk since it's validated by then, but it's the same pattern and free to fix.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I added a fix.

# matching this exact tag shape -- a release published under a tag
# that doesn't match it would be invisible to that resolver, so
# fail here rather than publish one.
if [[ ! "$tag" =~ ^[0-9]+(\.[0-9]+){1,3}(-[A-Za-z0-9]+)?$ ]]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

release.yml:27-31 says the regex is "this exact tag shape" that security.yml's branch resolver matches. I tested both against the resolver on origin/main (security.yml:67-70, grep -E "^${escaped}(.|$)"):

┌────────────────────────┬─────────────┬───────────────────────┐
│          tag           │ release.yml │ security.yml resolver │
├────────────────────────┼─────────────┼───────────────────────┤
│ 26.3, 26.3.1, 26.3.1.2 │ PASS        │ found                 │
├────────────────────────┼─────────────┼───────────────────────┤
│ 26.3-beta              │ PASS        │ invisible             │
├────────────────────────┼─────────────┼───────────────────────┤
│ 26.3-rc.1, v26.3, 26   │ FAIL        │ invisible             │
└────────────────────────┴─────────────┴───────────────────────┘

So release.yml happily publishes 26.3-beta, and the resolver's grep skips it and silently resolves to the newest unsuffixed tag instead — sort -V | tail -1 has no suffixed candidate to pick. That's worse
than the failure the comment is guarding against: no error, just a daily security scan quietly pointing at a stale released image. The defensive shape check at security.yml:75 doesn't catch it either,
because the tag it resolved is well-shaped — just not the latest.

Two honest options: drop (-[A-Za-z0-9]+)? from release.yml so the two really do agree, or widen the resolver's grep to ^26.3([.-]|$) and make sort -V handle it. Either way the comment should describe
what's enforced.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the changes in #1499 this concern is not valid anymore, as the tag scan has been removed.

@@ -1,133 +0,0 @@
name: Docker Image Builder (docker.io, ecr.aws, quay.io)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing workflow_dispatch costs more than the PR body claims

The body frames the dropped trigger as "a one-off partial push to a single registry". That's true of the push_to_* toggles, but the dispatch trigger also bought a full rebuild of an already-released tag —
which is exactly what the CACHE_KEY mechanism at release.yml:129-130 exists to enable, and exactly the remedy when security.yml's non-gating scan of a released image reports a base-OS CVE.

The fallback is "Re-run all jobs" on the release run, which does work — but it also re-runs publish-pypi, which will fail on PyPI's duplicate-version rejection after the images are already pushed, leaving
a red run on a successful rebuild. If you keep the trigger removed, add skip-existing: true to the pypa/gh-action-pypi-publish step (release.yml:111) so re-run is a clean operation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-publishing released images is something that I'd like to avoid, even for this use-case. My impression is that while there's no strong consensus on this, in particular more security sensitive users frown upon changing release tags, even if that can be mitigated/spotted by using digests. So generally I'd avoid this practice and instead ship a patch release, if we do ever want to do this, we can selectively rerun the image publish jobs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

target/main Targets the development branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants