Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
32f2d53
add: github action to publish docker image
nevil-mathew Aug 6, 2025
b7f6970
Merge pull request #784 from ELEVATE-Project/githib-actions
nevil-mathew Aug 6, 2025
020da8d
Update docker-image.yml
nevil-mathew Aug 6, 2025
35df28e
chore(ci): allow Docker build from specified branch with default staging
nevil-mathew Oct 15, 2025
b8f4948
fix: get-version safely
nevil-mathew Oct 15, 2025
f7d7bf3
Merge pull request #842 from ELEVATE-Project/update-github-work
nevil-mathew Oct 15, 2025
0ebebe1
Merge pull request #855 from ELEVATE-Project/develop
nevil-mathew Oct 27, 2025
6eeb08a
Merge pull request #859 from ELEVATE-Project/develop
nevil-mathew Oct 28, 2025
a3bcc4a
Merge pull request #861 from ELEVATE-Project/develop
nevil-mathew Oct 28, 2025
bc9ff33
Simplify version retrieval in Docker workflow
nevil-mathew Oct 31, 2025
f78daa7
Merge pull request #863 from ELEVATE-Project/docker-workflow
nevil-mathew Oct 31, 2025
3c9ed3d
Merge pull request #760 from ELEVATE-Project/staging
nevil-mathew Nov 18, 2025
d9066aa
udpated: github actions
nevil-mathew Mar 5, 2026
68fcbfa
updated: github actions
nevil-mathew Mar 5, 2026
200f719
updated: github actions
nevil-mathew Mar 5, 2026
b7bc66c
added: prod release job
nevil-mathew Mar 6, 2026
7a6321d
updated: prod relase action
nevil-mathew Mar 6, 2026
6203236
updated: release job
nevil-mathew Mar 6, 2026
f942210
update: release action
nevil-mathew Mar 6, 2026
d143e13
fix: main to master
nevil-mathew Mar 6, 2026
8a61870
updated prod release
nevil-mathew Mar 6, 2026
b8d8a3a
fix: issue with actions
nevil-mathew Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Build, Tag, and Push Docker Image

on:
workflow_dispatch:
inputs:
tag_version:
description: 'Docker image tag version (e.g., 3.3.11)'
required: true
branch:
description: 'Branch to build from (default: staging)'
required: false
default: 'staging'

env:
DOCKER_IMAGE_NAME: elevate-user
DOCKER_REGISTRY: docker.io
DOCKER_NAMESPACE: shikshalokamqa

permissions:
contents: write

jobs:
docker-image-build-and-push:
runs-on: ubuntu-latest
Comment on lines +22 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize runs per release tag.

The “tag absent” checks and the later push/tag steps are not atomic. Two dispatches for the same tag_version can both pass validation, and the losing run can still overwrite the Docker tag before its Git tag push fails. Add a concurrency group keyed by the normalized version so only one publish for a given tag can run at a time.

Also applies to: 55-128

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-image.yml around lines 22 - 24, Add a
workflow-level concurrency guard in the docker-image publish job so only one run
per normalized release version can proceed at a time. Update the
docker-image-build-and-push job in the workflow to use a concurrency group
derived from the same normalized tag version used by the tag validation and push
steps, and ensure it cancels or blocks duplicate dispatches for that version
before the later Docker tag and Git tag operations run. Use the existing version
normalization logic and the publish job name as the anchors when wiring this in.


steps:
- name: Checkout code from target branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch || 'staging' }}
Comment on lines +27 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Don't persist the workflow token in the build context.

actions/checkout leaves the write-scoped GitHub token in .git/config by default. Because this job then builds a Docker image from a user-selected branch using the repo workspace as context, a branch-controlled Dockerfile can exfiltrate that token during the build. Set persist-credentials: false on checkout, then pass GITHUB_TOKEN only to the final git push step.

Suggested change
     - name: Checkout code from target branch
       uses: actions/checkout@v4
       with:
         ref: ${{ github.event.inputs.branch || 'staging' }}
+        persist-credentials: false
...
     - name: Create and push Git tag
+      env:
+        GITHUB_TOKEN: ${{ github.token }}
       run: |
         VERSION="${{ steps.get-version.outputs.version }}"
         git config user.name "github-actions"
         git config user.email "github-actions@github.com"
         git tag -a "$VERSION" -m "Release $VERSION"
-        git push origin "$VERSION"
+        git push "https://x-access-token:${GITHUB_TOKEN}`@github.com/`${GITHUB_REPOSITORY}.git" "$VERSION"

Also applies to: 109-120, 122-128

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 27-30: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-image.yml around lines 27 - 30, The checkout step
in the Docker image workflow is leaving the GitHub token in the workspace, which
can be exposed during the image build. Update the existing actions/checkout
usage in the workflow to disable credential persistence, then ensure the token
is only provided to the final git push step that needs it. Use the checkout step
and the later push step in the workflow to locate the change.

Source: Linters/SAST tools


- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
Comment on lines +28 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pin every third-party action to a full commit SHA.

These uses: entries are all mutable tags today. With a release workflow that has Docker Hub credentials and contents: write, that leaves the pipeline open to upstream action compromise. Please pin each action to its published commit digest.

Also applies to: 103-111

🧰 Tools
🪛 zizmor (1.26.1)

[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 36-36: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-image.yml around lines 28 - 36, The workflow
currently uses mutable third-party action tags, which should be replaced with
full commit SHAs for supply-chain safety. Update each `uses:` entry in the
release workflow, including the checkout, buildx setup, Docker login, metadata,
build/push, and any other action references in this job, to pinned commit
digests instead of version tags. Keep the existing behavior and inputs the same
while changing only the action references, and use the action names like
actions/checkout, docker/setup-buildx-action, and docker/login-action to locate
all affected steps.

Source: Linters/SAST tools

with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Get Docker tag version
id: get-version
shell: bash
run: |
VERSION="${{ github.event.inputs.tag_version }}"
# Strip leading "v" if present
VERSION="${VERSION#v}"
# Enforce x.y.z or x.y.z-rc.1 format
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then
echo "Error: tag_version must look like 3.4.0 or 3.4.0-rc.1 (no other suffixes allowed)"
exit 1
fi
printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT"
Comment on lines +41 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Move tag_version into env before the shell parses it.

Line 45 expands a user-controlled workflow input directly into bash, so the injection happens before your regex validation. Read the input from an environment variable instead of embedding ${{ ... }} in the script body.

Suggested change
     - name: Get Docker tag version
       id: get-version
       shell: bash
+      env:
+        INPUT_TAG_VERSION: ${{ github.event.inputs.tag_version }}
       run: |
-        VERSION="${{ github.event.inputs.tag_version }}"
+        VERSION="$INPUT_TAG_VERSION"
         # Strip leading "v" if present
         VERSION="${VERSION#v}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Get Docker tag version
id: get-version
shell: bash
run: |
VERSION="${{ github.event.inputs.tag_version }}"
# Strip leading "v" if present
VERSION="${VERSION#v}"
# Enforce x.y.z or x.y.z-rc.1 format
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then
echo "Error: tag_version must look like 3.4.0 or 3.4.0-rc.1 (no other suffixes allowed)"
exit 1
fi
printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT"
- name: Get Docker tag version
id: get-version
shell: bash
env:
INPUT_TAG_VERSION: ${{ github.event.inputs.tag_version }}
run: |
VERSION="$INPUT_TAG_VERSION"
# Strip leading "v" if present
VERSION="${VERSION#v}"
# Enforce x.y.z or x.y.z-rc.1 format
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then
echo "Error: tag_version must look like 3.4.0 or 3.4.0-rc.1 (no other suffixes allowed)"
exit 1
fi
printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT"
🧰 Tools
🪛 zizmor (1.26.1)

[error] 45-45: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-image.yml around lines 41 - 53, The Get Docker tag
version step is expanding the user-controlled tag_version input directly inside
the bash script, so move that input into an env variable on the workflow step
and read it from the shell there instead. Update the get-version step in
docker-image.yml so the script uses the environment value for VERSION, keeping
the existing validation and output logic intact.

Source: Linters/SAST tools


- name: Check if Git tag already exists
run: |
VERSION="${{ steps.get-version.outputs.version }}"
git fetch --tags
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Error: Git tag $VERSION already exists"
exit 1
fi

- name: Check if version exists on Docker Hub
run: |
VERSION="${{ steps.get-version.outputs.version }}"

LOGIN_RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-d '{"username": "${{ secrets.DOCKERHUB_USERNAME }}", "password": "${{ secrets.DOCKERHUB_TOKEN }}"}' \
"https://hub.docker.com/v2/users/login")
Comment on lines +64 to +72

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Build the Docker Hub login JSON safely.

This payload is assembled with raw string interpolation, so a valid Docker Hub username/token containing JSON-significant characters can make the login request malformed. prod-release.yml already fixes the same call with jq --arg; mirror that here to avoid spurious auth failures.

🧰 Tools
🪛 zizmor (1.26.1)

[info] 66-66: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-image.yml around lines 64 - 72, The Docker Hub
login payload is being built with raw string interpolation, which can break the
JSON when the credentials contain special characters. Update the Docker Hub
login step in the workflow to construct the request body safely the same way
`prod-release.yml` does, using the existing login command flow and a JSON-safe
builder such as `jq --arg` around the `curl` call. Keep the change localized to
the Docker Hub login request so the `Check if version exists on Docker Hub` step
always sends valid JSON.

LOGIN_HTTP=$(echo "$LOGIN_RESPONSE" | tail -n1)
LOGIN_BODY=$(echo "$LOGIN_RESPONSE" | head -n-1)

if [ "$LOGIN_HTTP" -ne 200 ]; then
echo "Error: Docker Hub login failed with HTTP $LOGIN_HTTP"
exit 1
fi

TOKEN=$(echo "$LOGIN_BODY" | jq -r .token)
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "Error: Docker Hub login succeeded but returned no token"
exit 1
fi

RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"https://hub.docker.com/v2/namespaces/${{ env.DOCKER_NAMESPACE }}/repositories/${{ env.DOCKER_IMAGE_NAME }}/tags/$VERSION")

if [ "$RESPONSE" -eq 200 ]; then
echo "Error: Tag $VERSION already exists on Docker Hub"
exit 1
elif [ "$RESPONSE" -eq 404 ]; then
echo "Tag $VERSION not found on Docker Hub — safe to push"
else
echo "Error: Unexpected HTTP $RESPONSE from Docker Hub tag check; aborting to fail safe"
exit 1
fi

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}
tags: |
type=raw,value=${{ steps.get-version.outputs.version }}

- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Create and push Git tag
run: |
VERSION="${{ steps.get-version.outputs.version }}"
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"

- name: Job summary
run: |
echo "### Docker Image Published 🚀" >> $GITHUB_STEP_SUMMARY
echo "**Tag:** ${{ steps.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Digest:** ${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY
162 changes: 162 additions & 0 deletions .github/workflows/prod-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
name: Promote RC to Production Release

on:
workflow_dispatch:
inputs:
rc_tag:
description: "Approved RC tag (example: 3.4.0-rc.2)"
required: true

env:
DOCKER_IMAGE_NAME: elevate-user
DOCKER_REGISTRY: docker.io
DOCKER_NAMESPACE: shikshalokamqa

permissions:
contents: write

concurrency:
group: promote
cancel-in-progress: false

jobs:
promote:
runs-on: ubuntu-latest

steps:
- name: Validate RC tag
id: version
env:
RC_TAG: ${{ github.event.inputs.rc_tag }}
run: |
if ! [[ "$RC_TAG" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-rc\.[0-9]+$ ]]; then
echo "Invalid rc_tag format. Must look like 3.4.0-rc.2"
exit 1
fi
RELEASE_TAG=$(echo "$RC_TAG" | sed 's/-rc\.[0-9]*//')
echo "rc=$RC_TAG" >> $GITHUB_OUTPUT
echo "release=$RELEASE_TAG" >> $GITHUB_OUTPUT

- name: Checkout repository
uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for ref in \
  "actions/checkout v4" \
  "docker/login-action v3" \
  "docker/setup-buildx-action v3"
do
  repo="${ref% *}"
  tag="${ref#* }"
  echo "== ${repo}@${tag} =="
  git ls-remote --tags "https://github.com/${repo}.git" "refs/tags/${tag}" "refs/tags/${tag}^{}"
done

Repository: ELEVATE-Project/user

Length of output: 411


Pin release workflow Actions to immutable SHAs.

This workflow holds contents: write permissions, making mutable Action tags (@v4, @v3) an unacceptable supply-chain risk for a production release path. Resolve each tag to the following verified commit SHAs to ensure deterministic execution:

  • actions/checkout@v434e114876b0b11c390a56381ad16ebd13914f8d5
  • docker/login-action@v3c94ce9fb468520275223c153574b00df6fe4bcc9
  • docker/setup-buildx-action@v38d2750c68a42422c14e847fe6c8ac0403b4cbd6f
Current Action References
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
      - uses: docker/setup-buildx-action@v3
🧰 Tools
🪛 zizmor (1.26.1)

[error] 41-41: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-release.yml at line 41, The prod release workflow is
using mutable Action tags, which should be replaced with immutable SHAs for
deterministic and safer execution. Update the workflow steps that use
actions/checkout, docker/login-action, and docker/setup-buildx-action to pin
them to the provided verified commit SHAs instead of `@v4/`@v3 tags, keeping the
existing step structure in the release job intact.

Source: Linters/SAST tools

with:
fetch-depth: 0
Comment on lines +40 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid persisting the write token across all later steps.

actions/checkout leaves the contents: write token in Git config by default. Set persist-credentials: false and provide the token only for the tag push step.

🔒 Proposed credential scoping change
     - name: Checkout repository
       uses: actions/checkout@v4
       with:
         fetch-depth: 0
+        persist-credentials: false
...
     - name: Create production Git tag
+      env:
+        GITHUB_TOKEN: ${{ github.token }}
       run: |
         VERSION="${{ steps.version.outputs.release }}"
         git config user.name "github-actions"
         git config user.email "github-actions@github.com"
         git fetch --tags
         if git rev-parse "$VERSION" >/dev/null 2>&1; then
           echo "Error: Git tag $VERSION already exists"
           exit 1
         fi
         git tag -a "$VERSION" "${{ steps.verify.outputs.rc_commit }}" -m "Release $VERSION"
-        git push origin "$VERSION"
+        git -c http.https://github.com/.extraheader="AUTHORIZATION: bearer $GITHUB_TOKEN" push origin "$VERSION"

Also applies to: 131-142

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 40-43: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 41-41: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-release.yml around lines 40 - 43, The checkout step
is persisting the repository write token in Git config, which can expose
credentials to later steps; update the workflow’s actions/checkout usage to
disable credential persistence and scope the token only to the tag-push step.
Use the checkout step and the later tag publishing/push step in the same
workflow as the places to adjust, ensuring only the push operation receives the
write token while all other steps run without stored credentials.

Source: Linters/SAST tools


- name: Verify RC commit exists in master
id: verify
run: |
RC_TAG="${{ steps.version.outputs.rc }}"
git fetch origin master --tags
RC_COMMIT=$(git rev-list -n 1 "$RC_TAG")
if git merge-base --is-ancestor "$RC_COMMIT" origin/master; then
echo "RC commit is present in master — safe to promote."
else
echo "Error: RC tag commit is NOT in master."
echo "Merge staging → master before promoting."
exit 1
fi
echo "rc_commit=$RC_COMMIT" >> $GITHUB_OUTPUT

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Check RC image exists and production tag is absent on Docker Hub
run: |
RC_TAG="${{ steps.version.outputs.rc }}"
RELEASE_TAG="${{ steps.version.outputs.release }}"

# FIX 1: Build JSON safely with jq --arg to avoid special-character injection
LOGIN_RESPONSE=$(jq -n \
--arg username "${{ secrets.DOCKERHUB_USERNAME }}" \
--arg password "${{ secrets.DOCKERHUB_TOKEN }}" \
'{"username": $username, "password": $password}' \
| curl -s -w "\n%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-d @- \
"https://hub.docker.com/v2/users/login")
Comment on lines +71 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pass Docker Hub secrets through env, not inline shell expansion.

jq --arg protects the JSON, but the secret values are still template-expanded into the shell script first. Put them in step env vars so shell metacharacters in a token cannot alter the script.

🔐 Proposed safer secret handling
     - name: Check RC image exists and production tag is absent on Docker Hub
+      env:
+        DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
+        DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
       run: |
...
         LOGIN_RESPONSE=$(jq -n \
-          --arg username "${{ secrets.DOCKERHUB_USERNAME }}" \
-          --arg password "${{ secrets.DOCKERHUB_TOKEN }}" \
+          --arg username "$DOCKERHUB_USERNAME" \
+          --arg password "$DOCKERHUB_TOKEN" \
           '{"username": $username, "password": $password}' \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# FIX 1: Build JSON safely with jq --arg to avoid special-character injection
LOGIN_RESPONSE=$(jq -n \
--arg username "${{ secrets.DOCKERHUB_USERNAME }}" \
--arg password "${{ secrets.DOCKERHUB_TOKEN }}" \
'{"username": $username, "password": $password}' \
| curl -s -w "\n%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-d @- \
"https://hub.docker.com/v2/users/login")
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
# FIX 1: Build JSON safely with jq --arg to avoid special-character injection
LOGIN_RESPONSE=$(jq -n \
--arg username "$DOCKERHUB_USERNAME" \
--arg password "$DOCKERHUB_TOKEN" \
'{"username": $username, "password": $password}' \
| curl -s -w "\n%{http_code}" \
-X POST \
-H "Content-Type: application/json" \
-d `@-` \
"https://hub.docker.com/v2/users/login")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-release.yml around lines 71 - 80, The Docker Hub
login step still interpolates secrets directly into the shell script, which can
allow shell metacharacters in the values to affect execution. Update the
workflow step that builds LOGIN_RESPONSE to pass DOCKERHUB_USERNAME and
DOCKERHUB_TOKEN through step-level env vars, then reference those env vars
inside the jq --arg call instead of using inline GitHub expression expansion.
Keep the existing jq and curl flow in place, but ensure the secret values are
only read from env within the login step.

LOGIN_HTTP=$(echo "$LOGIN_RESPONSE" | tail -n1)
LOGIN_BODY=$(echo "$LOGIN_RESPONSE" | head -n-1)

if [ "$LOGIN_HTTP" -ne 200 ]; then
echo "Error: Docker Hub login failed with HTTP $LOGIN_HTTP"
exit 1
fi

TOKEN=$(echo "$LOGIN_BODY" | jq -r .token)
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "Error: Docker Hub login succeeded but returned no token"
exit 1
fi

# Verify RC image exists
RC_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"https://hub.docker.com/v2/namespaces/${{ env.DOCKER_NAMESPACE }}/repositories/${{ env.DOCKER_IMAGE_NAME }}/tags/$RC_TAG")
if [ "$RC_RESPONSE" -eq 404 ]; then
echo "Error: RC image $RC_TAG not found on Docker Hub"
exit 1
elif [ "$RC_RESPONSE" -ne 200 ]; then
echo "Error: Unexpected HTTP $RC_RESPONSE checking RC image — aborting to fail safe"
exit 1
fi
echo "RC image $RC_TAG confirmed on Docker Hub."

# Verify production tag does not already exist
PROD_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
"https://hub.docker.com/v2/namespaces/${{ env.DOCKER_NAMESPACE }}/repositories/${{ env.DOCKER_IMAGE_NAME }}/tags/$RELEASE_TAG")
if [ "$PROD_RESPONSE" -eq 200 ]; then
echo "Error: Production tag $RELEASE_TAG already exists on Docker Hub"
exit 1
elif [ "$PROD_RESPONSE" -ne 404 ]; then
echo "Error: Unexpected HTTP $PROD_RESPONSE checking production tag — aborting to fail safe"
exit 1
fi
echo "Production tag $RELEASE_TAG is absent — safe to promote."

# FIX 2: Replace pull/tag/push with imagetools create to preserve multi-arch manifest
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Retag RC image as production (multi-arch)
run: |
docker buildx imagetools create \
--tag ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.release }} \
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.rc }}

- name: Create production Git tag
run: |
VERSION="${{ steps.version.outputs.release }}"
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git fetch --tags
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Error: Git tag $VERSION already exists"
exit 1
fi
git tag -a "$VERSION" "${{ steps.verify.outputs.rc_commit }}" -m "Release $VERSION"
git push origin "$VERSION"
Comment on lines +125 to +142

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Check the Git production tag before mutating Docker Hub.

The workflow creates the production Docker tag before checking whether the Git tag already exists. If Line 137 fails, Docker Hub has already been changed and reruns will then fail at the Docker tag gate.

Move the Git tag absence check before docker buildx imagetools create, and use refs/tags/$VERSION for the check.

🧰 Tools
🪛 zizmor (1.26.1)

[info] 128-128: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 129-129: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 133-133: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 141-141: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/prod-release.yml around lines 125 - 142, The production
release workflow in the retag-and-tag section is checking for an existing Git
tag too late, after mutating the Docker image tag. Move the Git tag existence
validation ahead of the `docker buildx imagetools create` step, and update the
check in the `Create production Git tag` flow to use `refs/tags/$VERSION` so the
gate prevents any Docker Hub changes when the tag already exists.


- name: Job summary
run: |
DIGEST=$(docker buildx imagetools inspect \
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.release }} \
--format '{{json .Manifest}}' \
| jq -r '.digest')
{
echo "### Production Promotion Complete 🚀"
echo ""
echo "| Field | Value |"
echo "| --- | --- |"
echo "| **RC tag** | \`${{ steps.version.outputs.rc }}\` |"
echo "| **Release tag** | \`${{ steps.version.outputs.release }}\` |"
echo "| **Validated commit** | \`${{ steps.verify.outputs.rc_commit }}\` |"
echo "| **Docker image** | \`${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.release }}\` |"
echo "| **Digest** | \`$DIGEST\` |"
echo "| **Triggered by** | @${{ github.actor }} |"
echo "| **Run** | [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |"
} >> $GITHUB_STEP_SUMMARY
Loading