name: Container CVE Scan (development) on: push: branches: ["development"] workflow_dispatch: jobs: cve-scan: name: Scan backend/frontend images for CVEs runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Docker Hub login (for Scout) if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }} uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Docker Scout login bootstrap continue-on-error: true run: | if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then echo "Docker Scout login skipped: DOCKERHUB_USERNAME/DOCKERHUB_TOKEN not set." exit 0 fi mkdir -p "$RUNNER_TEMP/scout-docker-config" printf '%s' "${{ secrets.DOCKERHUB_TOKEN }}" | docker run --rm -i \ -e DOCKER_CONFIG=/home/scout/.docker \ -v "$RUNNER_TEMP/scout-docker-config:/home/scout/.docker" \ docker/scout-cli:latest login \ --username "${{ secrets.DOCKERHUB_USERNAME }}" \ --password-stdin || true - name: Build backend image (local) uses: docker/build-push-action@v6 with: context: ./backend file: ./backend/Dockerfile push: false load: true tags: nexapg-backend:dev-scan provenance: false sbom: false - name: Build frontend image (local) uses: docker/build-push-action@v6 with: context: ./frontend file: ./frontend/Dockerfile push: false load: true tags: nexapg-frontend:dev-scan build-args: | VITE_API_URL=/api/v1 provenance: false sbom: false - name: Trivy scan (backend) uses: aquasecurity/trivy-action@0.24.0 with: image-ref: nexapg-backend:dev-scan format: json output: trivy-backend.json severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL ignore-unfixed: false exit-code: 0 - name: Trivy scan (frontend) uses: aquasecurity/trivy-action@0.24.0 with: image-ref: nexapg-frontend:dev-scan format: json output: trivy-frontend.json severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL ignore-unfixed: false exit-code: 0 - name: Summarize Trivy severities run: | python - <<'PY' import json from collections import Counter def summarize(path): c = Counter() with open(path, "r", encoding="utf-8") as f: data = json.load(f) for result in data.get("Results", []): for v in result.get("Vulnerabilities", []) or []: c[v.get("Severity", "UNKNOWN")] += 1 for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN"]: c.setdefault(sev, 0) return c for label, path in [("backend", "trivy-backend.json"), ("frontend", "trivy-frontend.json")]: s = summarize(path) print(f"===== Trivy {label} =====") print(f"CRITICAL={s['CRITICAL']} HIGH={s['HIGH']} MEDIUM={s['MEDIUM']} LOW={s['LOW']} UNKNOWN={s['UNKNOWN']}") print() PY - name: Docker Scout scan (backend) continue-on-error: true run: | if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then echo "Docker Hub Scout scan skipped: DOCKERHUB_USERNAME/DOCKERHUB_TOKEN not set." > scout-backend.txt exit 0 fi if [ ! -f "$HOME/.docker/config.json" ]; then echo "Runner Docker config not found; continuing with Scout login cache if present." > scout-backend.txt fi docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$RUNNER_TEMP/scout-docker-config:/home/scout/.docker" \ -e DOCKER_CONFIG=/home/scout/.docker \ docker/scout-cli:latest cves nexapg-backend:dev-scan \ --only-severity critical,high,medium,low > scout-backend.txt 2>&1 || { echo "" >> scout-backend.txt echo "Docker Scout backend scan failed (non-blocking)." >> scout-backend.txt } - name: Docker Scout scan (frontend) continue-on-error: true run: | if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then echo "Docker Hub Scout scan skipped: DOCKERHUB_USERNAME/DOCKERHUB_TOKEN not set." > scout-frontend.txt exit 0 fi if [ ! -f "$HOME/.docker/config.json" ]; then echo "Runner Docker config not found; continuing with Scout login cache if present." > scout-frontend.txt fi docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$RUNNER_TEMP/scout-docker-config:/home/scout/.docker" \ -e DOCKER_CONFIG=/home/scout/.docker \ docker/scout-cli:latest cves nexapg-frontend:dev-scan \ --only-severity critical,high,medium,low > scout-frontend.txt 2>&1 || { echo "" >> scout-frontend.txt echo "Docker Scout frontend scan failed (non-blocking)." >> scout-frontend.txt } - name: Print scan summary run: | echo "===== Docker Scout backend =====" test -f scout-backend.txt && cat scout-backend.txt || echo "scout-backend.txt not available" echo echo "===== Docker Scout frontend =====" test -f scout-frontend.txt && cat scout-frontend.txt || echo "scout-frontend.txt not available" - name: Upload scan reports uses: actions/upload-artifact@v3 with: name: container-cve-scan-reports path: | trivy-backend.json trivy-frontend.json scout-backend.txt scout-frontend.txt