name: Reusable Image Security Checks on: workflow_call: inputs: image_name: required: true type: string image_tag: type: string default: "edge" scan_severity: type: string default: "HIGH,CRITICAL" secrets: REGISTRY_USERNAME: { required: true } REGISTRY_PASSWORD: { required: true } DOCKER_REGISTRY: { required: true } jobs: scan: runs-on: docker container: image: gitea.tech-buddy.at/bitbuddydev/gitea_runner_python314:1.1.0 credentials: username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} steps: - name: Docker Login run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "${{ secrets.DOCKER_REGISTRY }}" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin - name: Pull Image shell: bash run: | set -euo pipefail FULL_IMAGE="${{ secrets.DOCKER_REGISTRY }}/${{ inputs.image_name }}:${{ inputs.image_tag }}" echo "=== Pulling ${FULL_IMAGE} ===" docker pull "${FULL_IMAGE}" - name: Image Vulnerability Scan shell: bash run: | set -euo pipefail # Fallback for runner images without Trivy baked in. Pinned version + # checksum, never "latest": Trivy releases were compromised in the # March 2026 supply-chain incident (malicious v0.69.4). Keep in sync # with the ARGs in the PipelineImage Dockerfiles. TRIVY_VERSION=0.74.0 TRIVY_SHA256=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a if ! command -v trivy >/dev/null 2>&1; then echo "=== Trivy not in runner image, installing v${TRIVY_VERSION} ===" curl -sSfLO "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" echo "${TRIVY_SHA256} trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" | sha256sum -c - tar -xzf "trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" -C /usr/local/bin trivy rm "trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" fi trivy --version FULL_IMAGE="${{ secrets.DOCKER_REGISTRY }}/${{ inputs.image_name }}:${{ inputs.image_tag }}" # Trivy's default vulnerability-DB source (mirror.gcr.io/aquasec/trivy-db) is a # Google-hosted cache of the upstream GHCR image, used to dodge GHCR's unauthenticated # rate limits. That cache occasionally 404s on a specific layer digest (a known, # recurring upstream issue: aquasecurity/trivy). Point at the canonical GHCR source # instead, and retry the DB download alone a few times — trivy exits 1 both for "DB # download failed" and for "vulnerabilities found", so the download is a separate step # from the scan to keep those two outcomes distinguishable. export TRIVY_DB_REPOSITORY="ghcr.io/aquasecurity/trivy-db:2" export TRIVY_JAVA_DB_REPOSITORY="ghcr.io/aquasecurity/trivy-java-db:1" echo "=== Ensuring vulnerability DB is up to date ===" db_attempt=1 db_max_attempts=3 until trivy image --download-db-only; do if [ "$db_attempt" -ge "$db_max_attempts" ]; then echo "=== Failed to download the Trivy vulnerability DB after ${db_max_attempts} attempts ===" exit 1 fi echo "=== DB download failed, retrying (${db_attempt}/${db_max_attempts})... ===" db_attempt=$((db_attempt + 1)) sleep $((db_attempt * 5)) done echo "=== Scanning ${FULL_IMAGE} (fail on fixable ${{ inputs.scan_severity }}) ===" trivy image \ --exit-code 1 \ --severity "${{ inputs.scan_severity }}" \ --ignore-unfixed \ --skip-db-update \ --scanners vuln,secret \ "${FULL_IMAGE}"