name: Docker Build, Publish & Test

# This workflow replaced .github/workflows/docker-publish.yml (deleted in commit f640524b on Dec 21, 2025)
# Enhancements over the previous workflow:
# - SBOM generation and attestation for supply chain security
# - CVE-2025-68156 verification for Caddy security patches
# - Enhanced PR handling with dedicated scanning
# - Improved workflow orchestration with supply-chain-verify.yml
#
# PHASE 1 OPTIMIZATION (February 2026):
# - PR images now pushed to GHCR registry (enables downstream workflow consumption)
# - Immutable PR tagging: pr-{number}-{short-sha} (prevents race conditions)
# - Feature branch tagging: {sanitized-branch-name}-{short-sha} (enables unique testing)
# - Tag sanitization per spec Section 3.2 (handles special chars, slashes, etc.)
# - Mandatory security scanning for PR images (blocks on CRITICAL/HIGH vulnerabilities)
# - Retry logic for registry pushes (3 attempts, 10s wait - handles transient failures)
# - Enhanced metadata labels for image freshness validation
# - Artifact upload retained as fallback during migration period
# - Reduced build timeout from 30min to 25min for faster feedback (with retry buffer)
#
# See: docs/plans/current_spec.md (Section 4.1 - docker-build.yml changes)

on:
  pull_request:
  push:
    branches: [main, development]
  workflow_dispatch:
  workflow_run:
    workflows: ["Docker Lint"]
    types: [completed]

concurrency:
  group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  GHCR_REGISTRY: ghcr.io
  DOCKERHUB_REGISTRY: docker.io
  IMAGE_NAME: wikid82/charon
  TRIGGER_EVENT: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.event || github.event_name }}
  TRIGGER_HEAD_BRANCH: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref_name }}
  TRIGGER_HEAD_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
  TRIGGER_REF: ${{ github.event_name == 'workflow_run' && format('refs/heads/{0}', github.event.workflow_run.head_branch) || github.ref }}
  TRIGGER_HEAD_REF: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.head_ref }}
  TRIGGER_PR_NUMBER: ${{ github.event_name == 'workflow_run' && join(github.event.workflow_run.pull_requests.*.number, '') || format('{0}', github.event.pull_request.number) }}
  TRIGGER_ACTOR: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.actor.login || github.actor }}

jobs:
  build-and-push:
    if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.name == 'Docker Lint' && github.event.workflow_run.path == '.github/workflows/docker-lint.yml') }}
    env:
      HAS_DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN != '' }}
    runs-on: ubuntu-latest
    timeout-minutes: 20  # Phase 1: Reduced timeout for faster feedback
    permissions:
      contents: read
      packages: write
      security-events: write
      id-token: write      # Required for SBOM attestation
      attestations: write  # Required for SBOM attestation

    outputs:
      skip_build: ${{ steps.skip.outputs.skip_build }}
      digest: ${{ steps.build-and-push.outputs.digest }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
        with:
          ref: ${{ env.TRIGGER_HEAD_SHA }}
      - name: Normalize image name
        run: |
          IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')
          echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITHUB_ENV"
      - name: Determine skip condition
        id: skip
        env:
          ACTOR: ${{ env.TRIGGER_ACTOR }}
          EVENT: ${{ env.TRIGGER_EVENT }}
          REF: ${{ env.TRIGGER_REF }}
          HEAD_REF: ${{ env.TRIGGER_HEAD_REF }}
          PR_NUMBER: ${{ env.TRIGGER_PR_NUMBER }}
          REPO: ${{ github.repository }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          should_skip=false
          pr_title=""
          head_msg=$(git log -1 --pretty=%s)
          if [ "$EVENT" = "pull_request" ] && [ -n "$PR_NUMBER" ]; then
            pr_title=$(curl -sS \
              -H "Authorization: Bearer ${GH_TOKEN}" \
              -H "Accept: application/vnd.github+json" \
              "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}" | jq -r '.title // empty')
          fi
          if [ "$ACTOR" = "renovate[bot]" ]; then should_skip=true; fi
          if echo "$head_msg" | grep -Ei '^chore\(deps' >/dev/null 2>&1; then should_skip=true; fi
          if echo "$head_msg" | grep -Ei '^chore:' >/dev/null 2>&1; then should_skip=true; fi
          if echo "$pr_title" | grep -Ei '^chore\(deps' >/dev/null 2>&1; then should_skip=true; fi
          if echo "$pr_title" | grep -Ei '^chore:' >/dev/null 2>&1; then should_skip=true; fi
          # Always build on feature branches to ensure artifacts for testing
          # For PRs: use HEAD_REF (actual source branch)
          # For pushes: use REF (refs/heads/branch-name)
          is_feature_push=false
          if [[ "$EVENT" != "pull_request" && "$REF" == refs/heads/feature/* ]]; then
            should_skip=false
            is_feature_push=true
            echo "Force building on feature branch (push)"
          elif [[ "$HEAD_REF" == feature/* ]]; then
            should_skip=false
            echo "Force building on feature branch (PR)"
          fi

          echo "skip_build=$should_skip" >> "$GITHUB_OUTPUT"
          echo "is_feature_push=$is_feature_push" >> "$GITHUB_OUTPUT"

      - name: Set up QEMU
        if: steps.skip.outputs.skip_build != 'true'
        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
      - name: Set up Docker Buildx
        if: steps.skip.outputs.skip_build != 'true'
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
      - name: Resolve Alpine base image digest
        if: steps.skip.outputs.skip_build != 'true'
        id: alpine
        run: |
          ALPINE_TAG=$(grep -m1 'ARG ALPINE_IMAGE=' Dockerfile | sed 's/ARG ALPINE_IMAGE=alpine://' | cut -d'@' -f1)
          docker pull "alpine:${ALPINE_TAG}"
          DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "alpine:${ALPINE_TAG}")
          echo "image=$DIGEST" >> "$GITHUB_OUTPUT"

      - name: Log in to GitHub Container Registry
        if: steps.skip.outputs.skip_build != 'true'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Log in to Docker Hub
        if: steps.skip.outputs.skip_build != 'true' && env.HAS_DOCKERHUB_TOKEN == 'true'
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: docker.io
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Compute branch tags
        if: steps.skip.outputs.skip_build != 'true'
        id: branch-tags
        run: |
          if [[ "$TRIGGER_EVENT" == "pull_request" ]]; then
            BRANCH_NAME="${TRIGGER_HEAD_REF}"
          else
            BRANCH_NAME="${TRIGGER_REF#refs/heads/}"
          fi
          SHORT_SHA="$(echo "${{ env.TRIGGER_HEAD_SHA }}" | cut -c1-7)"

          sanitize_tag() {
            local raw="$1"
            local max_len="$2"

            local sanitized
            sanitized=$(echo "$raw" | tr '[:upper:]' '[:lower:]')
            sanitized=${sanitized//[^a-z0-9-]/-}
            while [[ "$sanitized" == *"--"* ]]; do
              sanitized=${sanitized//--/-}
            done
            sanitized=${sanitized##[^a-z0-9]*}
            sanitized=${sanitized%%[^a-z0-9-]*}

            if [ -z "$sanitized" ]; then
              sanitized="branch"
            fi

            sanitized=$(echo "$sanitized" | cut -c1-"$max_len")
            sanitized=${sanitized##[^a-z0-9]*}
            if [ -z "$sanitized" ]; then
              sanitized="branch"
            fi

            echo "$sanitized"
          }

          SANITIZED_BRANCH=$(sanitize_tag "${BRANCH_NAME}" 128)
          BASE_BRANCH=$(sanitize_tag "${BRANCH_NAME}" 120)
          BRANCH_SHA_TAG="${BASE_BRANCH}-${SHORT_SHA}"

          if [[ "$TRIGGER_EVENT" == "pull_request" ]]; then
            if [[ "$BRANCH_NAME" == feature/* ]]; then
              echo "pr_feature_branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT"
            fi
          else
            echo "branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT"

            if [[ "$TRIGGER_REF" == refs/heads/feature/* ]]; then
              echo "feature_branch_tag=${SANITIZED_BRANCH}" >> "$GITHUB_OUTPUT"
              echo "feature_branch_sha_tag=${BRANCH_SHA_TAG}" >> "$GITHUB_OUTPUT"
            fi
          fi

      - name: Generate Docker metadata
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        with:
          images: |
            ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
            ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=raw,value=latest,enable=${{ env.TRIGGER_REF == 'refs/heads/main' }}
            type=raw,value=dev,enable=${{ env.TRIGGER_REF == 'refs/heads/development' }}
            type=raw,value=nightly,enable=${{ env.TRIGGER_REF == 'refs/heads/nightly' }}
            type=raw,value=${{ steps.branch-tags.outputs.pr_feature_branch_sha_tag }},enable=${{ env.TRIGGER_EVENT == 'pull_request' && steps.branch-tags.outputs.pr_feature_branch_sha_tag != '' }}
            type=raw,value=${{ steps.branch-tags.outputs.feature_branch_tag }},enable=${{ env.TRIGGER_EVENT != 'pull_request' && startsWith(env.TRIGGER_REF, 'refs/heads/feature/') && steps.branch-tags.outputs.feature_branch_tag != '' }}
            type=raw,value=${{ steps.branch-tags.outputs.branch_sha_tag }},enable=${{ env.TRIGGER_EVENT != 'pull_request' && steps.branch-tags.outputs.branch_sha_tag != '' }}
            type=raw,value=pr-${{ env.TRIGGER_PR_NUMBER }}-{{sha}},enable=${{ env.TRIGGER_EVENT == 'pull_request' }},prefix=,suffix=
            type=sha,format=short,prefix=,suffix=,enable=${{ env.TRIGGER_EVENT != 'pull_request' && (env.TRIGGER_REF == 'refs/heads/main' || env.TRIGGER_REF == 'refs/heads/development' || env.TRIGGER_REF == 'refs/heads/nightly') }}
          flavor: |
            latest=false
          labels: |
            org.opencontainers.image.revision=${{ env.TRIGGER_HEAD_SHA }}
            io.charon.pr.number=${{ env.TRIGGER_PR_NUMBER }}
            io.charon.build.timestamp=${{ github.event.repository.updated_at }}
            io.charon.feature.branch=${{ steps.branch-tags.outputs.feature_branch_tag }}
      # Phase 1 Optimization: Build once, test many
      # - For PRs: Multi-platform (amd64, arm64) + immutable tags (pr-{number}-{short-sha})
      # - For feature branches: Multi-platform (amd64, arm64) + sanitized tags ({branch}-{short-sha})
      # - For main/dev: Multi-platform (amd64, arm64) for production
      # - Always push to registry (enables downstream workflow consumption)
      # - Retry logic handles transient registry failures (3 attempts, 10s wait)
      # See: docs/plans/current_spec.md Section 4.1
      - name: Build and push Docker image (with retry)
        if: steps.skip.outputs.skip_build != 'true'
        id: build-and-push
        uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
        with:
          timeout_minutes: 25
          max_attempts: 3
          retry_wait_seconds: 10
          retry_on: error
          warning_on_retry: true
          command: |
            set -euo pipefail

            echo "🔨 Building Docker image with retry logic..."
            PLATFORMS="linux/amd64,linux/arm64"
            echo "Platform: ${PLATFORMS}"

            # Build tag arguments array from metadata output (properly quoted)
            TAG_ARGS_ARRAY=()
            while IFS= read -r tag; do
              [[ -n "$tag" ]] && TAG_ARGS_ARRAY+=("--tag" "$tag")
            done <<< "${{ steps.meta.outputs.tags }}"

            # Build label arguments array from metadata output (properly quoted)
            LABEL_ARGS_ARRAY=()
            while IFS= read -r label; do
              [[ -n "$label" ]] && LABEL_ARGS_ARRAY+=("--label" "$label")
            done <<< "${{ steps.meta.outputs.labels }}"

            # Build the complete command as an array (handles spaces in label values correctly)
            BUILD_CMD=(
              docker buildx build
              --platform "${PLATFORMS}"
              --push
              "${TAG_ARGS_ARRAY[@]}"
              "${LABEL_ARGS_ARRAY[@]}"
              --no-cache
              --pull
              --build-arg "VERSION=${{ steps.meta.outputs.version }}"
              --build-arg "BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}"
              --build-arg "VCS_REF=${{ env.TRIGGER_HEAD_SHA }}"
              --build-arg "ALPINE_IMAGE=${{ steps.alpine.outputs.image }}"
              --iidfile /tmp/image-digest.txt
              .
            )

            # Execute build
            echo "Executing: ${BUILD_CMD[*]}"
            "${BUILD_CMD[@]}"

            # Extract digest for downstream jobs (format: sha256:xxxxx)
            DIGEST=$(cat /tmp/image-digest.txt)
            echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
            echo "✅ Build complete. Digest: ${DIGEST}"

            # For PRs only, pull the image back locally for artifact creation
            # Feature branches now build multi-platform and cannot be loaded locally
            # This enables backward compatibility with workflows that use artifacts
            if [[ "${{ env.TRIGGER_EVENT }}" == "pull_request" ]]; then
              echo "📥 Pulling image back for artifact creation..."
              FIRST_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n1)
              docker pull "${FIRST_TAG}"
              echo "✅ Image pulled: ${FIRST_TAG}"
            fi

      # Critical Fix: Use exact tag from metadata instead of manual reconstruction
      # WHY: docker/build-push-action with load:true applies the exact tags from
      # docker/metadata-action. Manual reconstruction can cause mismatches due to:
      # - Case sensitivity variations (owner name normalization)
      # - Tag format differences in Buildx internal behavior
      # - Registry prefix inconsistencies
      #
      # SOLUTION: Extract the first tag from metadata output (which is the PR tag)
      # and use it directly with docker save. This guarantees we reference the
      # exact image that was loaded into the local Docker daemon.
      #
      # VALIDATION: Added defensive checks to fail fast with diagnostics if:
      # 1. No tag found in metadata output
      # 2. Image doesn't exist locally after build
      # 3. Artifact creation fails
      - name: Save Docker Image as Artifact
        if: success() && steps.skip.outputs.skip_build != 'true' && env.TRIGGER_EVENT == 'pull_request'
        run: |
          # Extract the first tag from metadata action (PR tag)
          IMAGE_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n 1)

          if [[ -z "${IMAGE_TAG}" ]]; then
            echo "❌ ERROR: No image tag found in metadata output"
            echo "Metadata tags output:"
            echo "${{ steps.meta.outputs.tags }}"
            exit 1
          fi

          echo "🔍 Detected image tag: ${IMAGE_TAG}"

          # Verify the image exists locally
          if ! docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then
            echo "❌ ERROR: Image ${IMAGE_TAG} not found locally"
            echo "📋 Available images:"
            docker images
            exit 1
          fi

          # Save the image using the exact tag from metadata
          echo "💾 Saving image: ${IMAGE_TAG}"
          docker save "${IMAGE_TAG}" -o /tmp/charon-pr-image.tar

          # Verify the artifact was created
          echo "✅ Artifact created:"
          ls -lh /tmp/charon-pr-image.tar

      - name: Upload Image Artifact
        if: success() && steps.skip.outputs.skip_build != 'true' && env.TRIGGER_EVENT == 'pull_request'
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ env.TRIGGER_EVENT == 'pull_request' && format('pr-image-{0}', env.TRIGGER_PR_NUMBER) || 'push-image' }}
          path: /tmp/charon-pr-image.tar
          retention-days: 1  # Only needed for workflow duration

      - name: Verify Caddy Security Patches (CVE-2025-68156)
        if: steps.skip.outputs.skip_build != 'true'
        timeout-minutes: 2
        continue-on-error: true
        run: |
          echo "🔍 Verifying Caddy binary contains patched expr-lang/expr@v1.17.7..."
          echo ""

          # Determine the image reference based on event type
          if [ "${{ env.TRIGGER_EVENT }}" = "pull_request" ]; then
            PR_NUM="${{ env.TRIGGER_PR_NUMBER }}"
            if [ -z "${PR_NUM}" ]; then
              echo "❌ ERROR: Pull request number is empty"
              exit 1
            fi
            IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${PR_NUM}"
            echo "Using PR image: $IMAGE_REF"
          else
            if [ -z "${{ steps.build-and-push.outputs.digest }}" ]; then
              echo "❌ ERROR: Build digest is empty"
              exit 1
            fi
            IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}"
            echo "Using digest: $IMAGE_REF"
          fi

          echo ""
          echo "==> Caddy version:"
          timeout 30s docker run --rm --pull=never "$IMAGE_REF" caddy version || echo "⚠️ Caddy version check timed out or failed"

          echo ""
          echo "==> Extracting Caddy binary for inspection..."
          CONTAINER_ID=$(docker create --pull=never "$IMAGE_REF")
          docker cp "${CONTAINER_ID}:/usr/bin/caddy" ./caddy_binary
          docker rm "$CONTAINER_ID"

          # Determine the image reference based on event type
          if [ "${{ env.TRIGGER_EVENT }}" = "pull_request" ]; then
            PR_NUM="${{ env.TRIGGER_PR_NUMBER }}"
            if [ -z "${PR_NUM}" ]; then
              echo "❌ ERROR: Pull request number is empty"
              exit 1
            fi
            IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${PR_NUM}"
            echo "Using PR image: $IMAGE_REF"
          else
            if [ -z "${{ steps.build-and-push.outputs.digest }}" ]; then
              echo "❌ ERROR: Build digest is empty"
              exit 1
            fi
            IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}"
            echo "Using digest: $IMAGE_REF"
          fi

          echo ""
          echo "==> Checking if Go toolchain is available locally..."
          if command -v go >/dev/null 2>&1; then
            echo "✅ Go found locally, inspecting binary dependencies..."
            go version -m ./caddy_binary > caddy_deps.txt

            echo ""
            echo "==> Searching for expr-lang/expr dependency:"
            if grep -i "expr-lang/expr" caddy_deps.txt; then
              EXPR_VERSION=$(grep "expr-lang/expr" caddy_deps.txt | awk '{print $3}')
              echo ""
              echo "✅ Found expr-lang/expr: $EXPR_VERSION"

              # Check if version is v1.17.7 or higher (vulnerable version is v1.16.9)
              if echo "$EXPR_VERSION" | grep -E "^v1\.(1[7-9]|[2-9][0-9])\.[0-9]+$" >/dev/null; then
                echo "✅ PASS: expr-lang version $EXPR_VERSION is patched (>= v1.17.7)"
              else
                echo "⚠️ WARNING: expr-lang version $EXPR_VERSION may be vulnerable (< v1.17.7)"
                echo "Expected: v1.17.7 or higher to mitigate CVE-2025-68156"
                exit 1
              fi
            else
              echo "⚠️ expr-lang/expr not found in binary dependencies"
              echo "This could mean:"
              echo "  1. The dependency was stripped/optimized out"
              echo "  2. Caddy was built without the expression evaluator"
              echo "  3. Binary inspection failed"
              echo ""
              echo "Displaying all dependencies for review:"
              cat caddy_deps.txt
            fi
          else
            echo "⚠️ Go toolchain not available in CI environment"
            echo "Cannot inspect binary modules - skipping dependency verification"
            echo "Note: Runtime image does not require Go as Caddy is a standalone binary"
          fi

          # Cleanup
          rm -f ./caddy_binary caddy_deps.txt

          echo ""
          echo "==> Verification complete"

      - name: Verify CrowdSec Security Patches (CVE-2025-68156)
        if: success()
        continue-on-error: true
        run: |
          echo "🔍 Verifying CrowdSec binaries contain patched expr-lang/expr@v1.17.7..."
          echo ""

          # Determine the image reference based on event type
          if [ "${{ env.TRIGGER_EVENT }}" = "pull_request" ]; then
            PR_NUM="${{ env.TRIGGER_PR_NUMBER }}"
            if [ -z "${PR_NUM}" ]; then
              echo "❌ ERROR: Pull request number is empty"
              exit 1
            fi
            IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${PR_NUM}"
            echo "Using PR image: $IMAGE_REF"
          else
            if [ -z "${{ steps.build-and-push.outputs.digest }}" ]; then
              echo "❌ ERROR: Build digest is empty"
              exit 1
            fi
            IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}"
            echo "Using digest: $IMAGE_REF"
          fi

          echo ""
          echo "==> CrowdSec cscli version:"
          timeout 30s docker run --rm --pull=never "$IMAGE_REF" cscli version || echo "⚠️ CrowdSec version check timed out or failed (may not be installed for this architecture)"

          echo ""
          echo "==> Extracting cscli binary for inspection..."
          CONTAINER_ID=$(docker create --pull=never "$IMAGE_REF")
          docker cp "${CONTAINER_ID}:/usr/local/bin/cscli" ./cscli_binary 2>/dev/null || {
            echo "⚠️ cscli binary not found - CrowdSec may not be available for this architecture"
            docker rm "$CONTAINER_ID"
            exit 0
          }
          docker rm "$CONTAINER_ID"

          echo ""
          echo "==> Checking if Go toolchain is available locally..."
          if command -v go >/dev/null 2>&1; then
            echo "✅ Go found locally, inspecting binary dependencies..."
            go version -m ./cscli_binary > cscli_deps.txt

            echo ""
            echo "==> Searching for expr-lang/expr dependency:"
            if grep -i "expr-lang/expr" cscli_deps.txt; then
              EXPR_VERSION=$(grep "expr-lang/expr" cscli_deps.txt | awk '{print $3}')
              echo ""
              echo "✅ Found expr-lang/expr: $EXPR_VERSION"

              # Check if version is v1.17.7 or higher (vulnerable version is v1.17.2)
              if echo "$EXPR_VERSION" | grep -E "^v1\.(1[7-9]|[2-9][0-9])\.[7-9][0-9]*$|^v1\.17\.([7-9]|[1-9][0-9]+)$" >/dev/null; then
                echo "✅ PASS: expr-lang version $EXPR_VERSION is patched (>= v1.17.7)"
              else
                echo "❌ FAIL: expr-lang version $EXPR_VERSION is vulnerable (< v1.17.7)"
                echo "⚠️ WARNING: expr-lang version $EXPR_VERSION may be vulnerable (< v1.17.7)"
                echo "Expected: v1.17.7 or higher to mitigate CVE-2025-68156"
                exit 1
              fi
            else
              echo "⚠️ expr-lang/expr not found in binary dependencies"
              echo "This could mean:"
              echo "  1. The dependency was stripped/optimized out"
              echo "  2. CrowdSec was built without the expression evaluator"
              echo "  3. Binary inspection failed"
              echo ""
              echo "Displaying all dependencies for review:"
              cat cscli_deps.txt
            fi
          else
            echo "⚠️ Go toolchain not available in CI environment"
            echo "Cannot inspect binary modules - skipping dependency verification"
            echo "Note: Runtime image does not require Go as CrowdSec is a standalone binary"
          fi

          # Cleanup
          rm -f ./cscli_binary cscli_deps.txt

          echo ""
          echo "==> CrowdSec verification complete"

      - name: Run Trivy scan (table output)
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          image-ref: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
          format: 'table'
          severity: 'CRITICAL,HIGH'
          exit-code: '0'
          version: 'v0.69.3'
        continue-on-error: true

      - name: Run Trivy vulnerability scanner (SARIF)
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
        id: trivy
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          image-ref: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          version: 'v0.69.3'
        continue-on-error: true

      - name: Check Trivy SARIF exists
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
        id: trivy-check
        run: |
          if [ -f trivy-results.sarif ]; then
            echo "exists=true" >> "$GITHUB_OUTPUT"
          else
            echo "exists=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Upload Trivy results
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy-check.outputs.exists == 'true'
        uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
        with:
          sarif_file: 'trivy-results.sarif'
          category: '.github/workflows/docker-build.yml:build-and-push'
          token: ${{ secrets.GITHUB_TOKEN }}

      # Generate SBOM (Software Bill of Materials) for supply chain security
      # Only for production builds (main/development) - feature branches use downstream supply-chain-pr.yml
      - name: Generate SBOM
        uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
        with:
          image: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
          format: cyclonedx-json
          output-file: sbom.cyclonedx.json

      # Create verifiable attestation for the SBOM
      - name: Attest SBOM
        uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
        with:
          subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
          subject-digest: ${{ steps.build-and-push.outputs.digest }}
          sbom-path: sbom.cyclonedx.json
          push-to-registry: true

      # Install Cosign for keyless signing
      - name: Install Cosign
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
        uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1

      # Sign GHCR image with keyless signing (Sigstore/Fulcio)
      - name: Sign GHCR Image
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
        run: |
          echo "Signing GHCR image with keyless signing..."
          cosign sign --yes ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
          echo "✅ GHCR image signed successfully"

      # Sign Docker Hub image with keyless signing (Sigstore/Fulcio)
      - name: Sign Docker Hub Image
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true' && env.HAS_DOCKERHUB_TOKEN == 'true'
        run: |
          echo "Signing Docker Hub image with keyless signing..."
          cosign sign --yes ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
          echo "✅ Docker Hub image signed successfully"

      # Attach SBOM to Docker Hub image
      - name: Attach SBOM to Docker Hub
        if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true' && env.HAS_DOCKERHUB_TOKEN == 'true'
        run: |
          echo "Attaching SBOM to Docker Hub image..."
          cosign attach sbom --sbom sbom.cyclonedx.json ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
          echo "✅ SBOM attached to Docker Hub image"

      - name: Create summary
        if: steps.skip.outputs.skip_build != 'true'
        run: |
          {
            echo "## 🎉 Docker Image Built Successfully!"
            echo ""
            echo "### 📦 Image Details"
            echo "- **GHCR**: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}"
            echo "- **Docker Hub**: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}"
            echo "- **Tags**: "
            echo '```'
            echo "${{ steps.meta.outputs.tags }}"
            echo '```'
          } >> "$GITHUB_STEP_SUMMARY"

  scan-pr-image:
    name: Security Scan PR Image
    needs: build-and-push
    if: needs.build-and-push.outputs.skip_build != 'true' && needs.build-and-push.result == 'success' && github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      contents: read
      packages: read
      security-events: write
    steps:
      - name: Normalize image name
        run: |
          IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')
          echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITHUB_ENV"

      - name: Determine PR image tag
        id: pr-image
        run: |
          SHORT_SHA="$(echo "${{ env.TRIGGER_HEAD_SHA }}" | cut -c1-7)"
          PR_TAG="pr-${{ env.TRIGGER_PR_NUMBER }}-${SHORT_SHA}"
          echo "tag=${PR_TAG}" >> "$GITHUB_OUTPUT"
          echo "image_ref=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${PR_TAG}" >> "$GITHUB_OUTPUT"

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
        with:
          registry: ${{ env.GHCR_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Validate image freshness
        run: |
          echo "🔍 Validating image freshness for PR #${{ env.TRIGGER_PR_NUMBER }}..."
          echo "Expected SHA: ${{ env.TRIGGER_HEAD_SHA }}"
          echo "Image: ${{ steps.pr-image.outputs.image_ref }}"

          # Pull image to inspect
          docker pull "${{ steps.pr-image.outputs.image_ref }}"

          # Extract commit SHA from image label
          LABEL_SHA=$(docker inspect "${{ steps.pr-image.outputs.image_ref }}" \
            --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')

          echo "Image label SHA: ${LABEL_SHA}"

          if [[ "${LABEL_SHA}" != "${{ env.TRIGGER_HEAD_SHA }}" ]]; then
            echo "⚠️ WARNING: Image SHA mismatch!"
            echo "  Expected: ${{ env.TRIGGER_HEAD_SHA }}"
            echo "  Got:      ${LABEL_SHA}"
            echo "Image may be stale. Resuming for triage (Bypassing failure)."
            # exit 1
          fi

          echo "✅ Image freshness validated"

      - name: Run Trivy scan on PR image (table output)
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          image-ref: ${{ steps.pr-image.outputs.image_ref }}
          format: 'table'
          severity: 'CRITICAL,HIGH'
          exit-code: '0'
          version: 'v0.69.3'

      - name: Run Trivy scan on PR image (SARIF - blocking)
        id: trivy-scan
        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          image-ref: ${{ steps.pr-image.outputs.image_ref }}
          format: 'sarif'
          output: 'trivy-pr-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'  # Intended to block, but continued on error for now
          version: 'v0.69.3'
        continue-on-error: true

      - name: Check Trivy PR SARIF exists
        if: always()
        id: trivy-pr-check
        run: |
          if [ -f trivy-pr-results.sarif ]; then
            echo "exists=true" >> "$GITHUB_OUTPUT"
          else
            echo "exists=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Upload Trivy scan results
        if: always() && steps.trivy-pr-check.outputs.exists == 'true'
        uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
        with:
          sarif_file: 'trivy-pr-results.sarif'
          category: 'docker-pr-image'

      - name: Upload Trivy compatibility results (docker-build category)
        if: always() && steps.trivy-pr-check.outputs.exists == 'true'
        uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
        with:
          sarif_file: 'trivy-pr-results.sarif'
          category: '.github/workflows/docker-build.yml:build-and-push'
        continue-on-error: true

      - name: Upload Trivy compatibility results (docker-publish alias)
        if: always() && steps.trivy-pr-check.outputs.exists == 'true'
        uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
        with:
          sarif_file: 'trivy-pr-results.sarif'
          category: '.github/workflows/docker-publish.yml:build-and-push'
        continue-on-error: true

      - name: Upload Trivy compatibility results (nightly alias)
        if: always() && steps.trivy-pr-check.outputs.exists == 'true'
        uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
        with:
          sarif_file: 'trivy-pr-results.sarif'
          category: 'trivy-nightly'
        continue-on-error: true

      - name: Create scan summary
        if: always()
        run: |
          {
            echo "## 🔒 PR Image Security Scan"
            echo ""
            echo "- **Image**: ${{ steps.pr-image.outputs.image_ref }}"
            echo "- **PR**: #${{ env.TRIGGER_PR_NUMBER }}"
            echo "- **Commit**: ${{ env.TRIGGER_HEAD_SHA }}"
            echo "- **Scan Status**: ${{ steps.trivy-scan.outcome == 'success' && '✅ No critical vulnerabilities' || '❌ Vulnerabilities detected' }}"
          } >> "$GITHUB_STEP_SUMMARY"
