Repository navigation
Conversation
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.
190845d to
cdc45be
Compare
Hamdy-khader
left a comment
There was a problem hiding this comment.
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...
| - name: Resolve release identifier | ||
| id: resolve | ||
| run: | | ||
| tag="${{ github.event.release.tag_name }}" |
There was a problem hiding this comment.
${{ }} 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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Merges
docker-image-2.ymlinto 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 theworkflow_dispatchtrigger, since a one-off partial push to a single registry is something done by hand, not through CI.