diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 400f5404..d55668ef 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -69,6 +69,11 @@ jobs: permissions: contents: read packages: write + # Trivy uploads its SARIF to the Security tab from this job — see + # the "Run Trivy" step below. Scanning per-arch by digest (#476) + # is reliable; scanning the multi-arch index by tag from the + # merge-* job was not. + security-events: write steps: - name: Checkout code @@ -153,13 +158,47 @@ jobs: if-no-files-found: error retention-days: 1 + # Per-arch vulnerability scan (#476). Scanning the multi-arch + # manifest from the merge-* job by tag is unreliable — Trivy's + # remote resolver crashes intermittently with "no child with + # platform linux/amd64 in index". The fix is to scan each leg + # by its single-platform digest right here, where it just landed + # in GHCR. Tag pinned (was @master) so the action + bundled + # Trivy binary don't float between runs. + # + # exit-code is left unset (=0) for now: Trivy reports findings + # to the Security tab but doesn't fail the build. Flipping that + # to '1' to actually gate CI is a deliberate follow-up — needs an + # audit pass first so the next beta build doesn't surprise red. + - name: Run Trivy vulnerability scanner (per-arch, by digest) + if: steps.push-decision.outputs.push == 'true' + uses: aquasecurity/trivy-action@0.28.0 + with: + image-ref: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}@${{ steps.build.outputs.digest }} + format: 'sarif' + output: 'trivy-backend-${{ env.PLATFORM_PAIR }}.sarif' + severity: 'CRITICAL,HIGH' + timeout: '10m' + + - name: Upload Trivy scan results to GitHub Security tab + if: steps.push-decision.outputs.push == 'true' + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: 'trivy-backend-${{ env.PLATFORM_PAIR }}.sarif' + # Distinct category per arch so the Security tab surfaces + # per-platform findings independently — an amd64-only CVE in + # a base layer doesn't get masked by the arm64 scan. + category: 'backend-vulnerabilities-${{ env.PLATFORM_PAIR }}' + merge-backend: needs: build-backend runs-on: ubuntu-latest + # No security-events permission here — vulnerability scanning moved + # to per-arch build-backend jobs (#476). This job's only job is to + # combine the per-arch digests into a multi-arch manifest. permissions: contents: read packages: write - security-events: write # Only run when at least one digest was pushed (i.e. not on PRs without push intent). if: github.event_name != 'pull_request' || github.event.inputs.push == 'true' @@ -231,23 +270,6 @@ jobs: run: | docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.meta-backend.outputs.version }} - - name: Run Trivy vulnerability scanner - if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success' - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.meta-backend.outputs.version }} - format: 'sarif' - output: 'trivy-backend.sarif' - severity: 'CRITICAL,HIGH' - timeout: '10m' - - - name: Upload Trivy scan results to GitHub Security tab - if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success' - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: 'trivy-backend.sarif' - category: 'backend-vulnerabilities' - # ----------------------------------------------------------------------------- # Frontend: per-arch build, then merge into a multi-arch manifest # ----------------------------------------------------------------------------- @@ -264,6 +286,9 @@ jobs: permissions: contents: read packages: write + # See build-backend for the rationale (#476). Same pattern: per-arch + # vulnerability scan by digest, SARIF uploaded to the Security tab. + security-events: write steps: - name: Checkout code @@ -348,13 +373,34 @@ jobs: if-no-files-found: error retention-days: 1 + # Per-arch vulnerability scan (#476). See build-backend for the + # full rationale; identical pattern here, only the image-ref + + # SARIF filename + category change. + - name: Run Trivy vulnerability scanner (per-arch, by digest) + if: steps.push-decision.outputs.push == 'true' + uses: aquasecurity/trivy-action@0.28.0 + with: + image-ref: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}@${{ steps.build.outputs.digest }} + format: 'sarif' + output: 'trivy-frontend-${{ env.PLATFORM_PAIR }}.sarif' + severity: 'CRITICAL,HIGH' + timeout: '10m' + + - name: Upload Trivy scan results to GitHub Security tab + if: steps.push-decision.outputs.push == 'true' + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: 'trivy-frontend-${{ env.PLATFORM_PAIR }}.sarif' + category: 'frontend-vulnerabilities-${{ env.PLATFORM_PAIR }}' + merge-frontend: needs: build-frontend runs-on: ubuntu-latest + # See merge-backend — vulnerability scanning moved to the per-arch + # build-frontend matrix (#476). This job only publishes the manifest. permissions: contents: read packages: write - security-events: write if: github.event_name != 'pull_request' || github.event.inputs.push == 'true' steps: @@ -425,23 +471,6 @@ jobs: run: | docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.meta-frontend.outputs.version }} - - name: Run Trivy vulnerability scanner - if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success' - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.meta-frontend.outputs.version }} - format: 'sarif' - output: 'trivy-frontend.sarif' - severity: 'CRITICAL,HIGH' - timeout: '10m' - - - name: Upload Trivy scan results to GitHub Security tab - if: github.event_name != 'pull_request' && steps.login-ghcr.outcome == 'success' - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: 'trivy-frontend.sarif' - category: 'frontend-vulnerabilities' - summary: needs: [build-backend, merge-backend, build-frontend, merge-frontend] if: always()