fix(ci): harden promote workflow — env-isolated untrusted input, main-ancestry check, concurrency guard
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m38s
PR Checks / Backend Smoke (pull_request) Successful in 6s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 44s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 2m39s
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m38s
PR Checks / Backend Smoke (pull_request) Successful in 6s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 44s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 2m39s
Addresses the AI review findings on PR #33: - severe: the workflow_dispatch sha input was interpolated directly into the run script (shell injection with REGISTRY_TOKEN + prod webhook in scope). It now reaches the shell only via env, is rejected if it starts with '-', and is resolved locally with git rev-parse. - minor: the resolved sha must be a 40-hex ancestor of origin/main — non-main refs are refused explicitly instead of implicitly. - minor: a prod-promotion concurrency group serialises promotions (cancel-in-progress: false). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
This commit is contained in:
@@ -14,6 +14,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
|
|
||||||
|
# Only one promotion at a time; never cancel an in-flight promotion.
|
||||||
|
concurrency:
|
||||||
|
group: prod-promotion
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: privaterepo.sitaru.org
|
REGISTRY: privaterepo.sitaru.org
|
||||||
BACKEND_IMAGE_NAME: ${{ gitea.repository }}-backend
|
BACKEND_IMAGE_NAME: ${{ gitea.repository }}-backend
|
||||||
@@ -25,18 +30,37 @@ jobs:
|
|||||||
name: Promote approved commit to Production
|
name: Promote approved commit to Production
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Resolve target SHA
|
- name: Checkout repository (full history for ancestry check)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Resolve and validate target SHA
|
||||||
id: resolve
|
id: resolve
|
||||||
|
# SECURITY: the dispatch input is untrusted — it reaches the shell
|
||||||
|
# only via env (never spliced into `run:` with ${{ }}) and is only
|
||||||
|
# used as a quoted argument. The resolved value is validated as a
|
||||||
|
# 40-hex sha and required to be an ancestor of main before any
|
||||||
|
# later step interpolates it.
|
||||||
|
env:
|
||||||
|
SHA_INPUT: ${{ gitea.event.inputs.sha }}
|
||||||
run: |
|
run: |
|
||||||
SHA_INPUT="${{ gitea.event.inputs.sha }}"
|
set -euo pipefail
|
||||||
|
case "$SHA_INPUT" in
|
||||||
|
-*) echo "REFUSED: SHA input may not start with '-'." >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
if [ -z "$SHA_INPUT" ]; then
|
if [ -z "$SHA_INPUT" ]; then
|
||||||
SHA_INPUT="${{ gitea.sha }}"
|
SHA_INPUT="$(git rev-parse origin/main)"
|
||||||
|
fi
|
||||||
|
FULL_SHA=$(git rev-parse --verify --quiet "${SHA_INPUT}^{commit}") || {
|
||||||
|
echo "REFUSED: not a commit in this repository." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "$FULL_SHA" | grep -Eq '^[0-9a-f]{40}$'
|
||||||
|
if ! git merge-base --is-ancestor "$FULL_SHA" origin/main; then
|
||||||
|
echo "REFUSED: $FULL_SHA is not on main — only main commits are promotable." >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
# Normalise to the full sha via the API so short inputs work
|
|
||||||
FULL_SHA=$(curl -fsS \
|
|
||||||
-H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \
|
|
||||||
"https://${REGISTRY}/api/v1/repos/${{ gitea.repository }}/git/commits/${SHA_INPUT}" \
|
|
||||||
| python3 -c "import json,sys; print(json.load(sys.stdin)['sha'])")
|
|
||||||
SHORT_SHA="sha-$(echo "$FULL_SHA" | cut -c1-7)"
|
SHORT_SHA="sha-$(echo "$FULL_SHA" | cut -c1-7)"
|
||||||
echo "full=$FULL_SHA" >> "$GITHUB_OUTPUT"
|
echo "full=$FULL_SHA" >> "$GITHUB_OUTPUT"
|
||||||
echo "short=$SHORT_SHA" >> "$GITHUB_OUTPUT"
|
echo "short=$SHORT_SHA" >> "$GITHUB_OUTPUT"
|
||||||
|
|||||||
Reference in New Issue
Block a user