name: scanner-images

# Build and publish the custom security scanner Docker images to
# ghcr.io/smart-mcp-proxy/scanner-*. Vendor images (trivy, semgrep, the
# AI scanner which lives in a separate repo) are NOT built here.
#
# Triggers:
#   - push to main that touches docker/scanners/** or this workflow file
#   - manual dispatch (for ad-hoc rebuilds)
#   - pull requests: builds without pushing (to catch Dockerfile drift)

on:
  push:
    branches: [main]
    paths:
      - 'docker/scanners/**'
      - '.github/workflows/scanner-images.yml'
  pull_request:
    paths:
      - 'docker/scanners/**'
      - '.github/workflows/scanner-images.yml'
  workflow_dispatch:
    inputs:
      scanner:
        description: 'Scanner id to rebuild (blank = all)'
        required: false
        default: ''

permissions:
  contents: read
  packages: write
  security-events: write  # required for SARIF upload to the Security tab

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - id: snyk
            image: ghcr.io/smart-mcp-proxy/scanner-snyk
            context: docker/scanners/snyk
          - id: cisco
            image: ghcr.io/smart-mcp-proxy/scanner-cisco
            context: docker/scanners/cisco
          - id: ramparts
            image: ghcr.io/smart-mcp-proxy/scanner-ramparts
            context: docker/scanners/ramparts
          - id: proximity
            image: ghcr.io/smart-mcp-proxy/scanner-proximity
            context: docker/scanners/proximity
    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.3.1

      - name: Skip if not selected
        if: ${{ github.event_name == 'workflow_dispatch' && inputs.scanner != '' && inputs.scanner != matrix.id }}
        run: echo "Skipping ${{ matrix.id }} (workflow_dispatch selected ${{ inputs.scanner }})" && exit 0

      - name: Set up QEMU
        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a  # v4.0.0

      - name: Set up Buildx
        uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca  # v3.9.0

      - name: Log in to GHCR
        if: ${{ github.event_name != 'pull_request' }}
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9  # v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Derive tags
        id: tags
        run: |
          set -euo pipefail
          short_sha="${GITHUB_SHA:0:7}"
          echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT"
          if [ "${GITHUB_REF}" = "refs/heads/main" ]; then
            echo "tags=${{ matrix.image }}:latest,${{ matrix.image }}:$short_sha" >> "$GITHUB_OUTPUT"
          else
            echo "tags=${{ matrix.image }}:pr-${{ github.event.pull_request.number || 'dev' }}" >> "$GITHUB_OUTPUT"
          fi

      - name: Build and push
        uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75  # v6.9.0
        with:
          context: ${{ matrix.context }}
          platforms: linux/amd64,linux/arm64
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.tags.outputs.tags }}
          cache-from: type=gha,scope=scanner-${{ matrix.id }}
          cache-to: type=gha,scope=scanner-${{ matrix.id }},mode=max
          labels: |
            org.opencontainers.image.source=https://github.com/smart-mcp-proxy/mcpproxy-go
            org.opencontainers.image.revision=${{ github.sha }}

      # Trivy image scan — runs only on push/workflow_dispatch (not pull_request) because
      # multi-platform builds (linux/amd64,linux/arm64) cannot be loaded to the runner with
      # `load: true`, so there is no local image to scan on PRs. On push the image is
      # already in GHCR and Trivy pulls it directly.
      # exit-code: '0' → report-only, never fails the build. Scanner base images routinely
      # carry unfixable CVEs (upstream OS packages); blocking builds on those would create
      # constant noise with no actionable remediation. Visibility via the Security tab and
      # the workflow log is the goal.
      - name: Scan image with Trivy
        if: github.event_name != 'pull_request'
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
        with:
          image-ref: ${{ matrix.image }}:${{ steps.tags.outputs.short_sha }}
          scan-type: image
          severity: 'CRITICAL,HIGH'
          ignore-unfixed: true
          exit-code: '0'
          format: table

      - name: Upload Trivy SARIF to Security tab
        if: github.event_name != 'pull_request'
        uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
        with:
          image-ref: ${{ matrix.image }}:${{ steps.tags.outputs.short_sha }}
          scan-type: image
          severity: 'CRITICAL,HIGH'
          ignore-unfixed: true
          exit-code: '0'
          format: sarif
          output: trivy-results-${{ matrix.id }}.sarif

      - name: Upload SARIF
        if: github.event_name != 'pull_request'
        uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
        with:
          sarif_file: trivy-results-${{ matrix.id }}.sarif
          category: trivy-${{ matrix.id }}
