name: Trivy CVE scan & auto-fix PR # Runs weekly to detect HIGH/CRITICAL CVEs in both Docker images. # - Uploads results to the GitHub Security tab (SARIF) # - If fixable CVEs are found AND a Dockerfile change is possible → opens a PR # - The PR triggers docker-publish.yml (build + smoke test) to verify the fix on: schedule: - cron: '0 7 * * 1' # Every Monday 07:00 UTC (after Dependabot at 06:00) workflow_dispatch: # Manual trigger for on-demand scans permissions: contents: write pull-requests: write security-events: write issues: write jobs: scan-and-fix: name: Scan images & create fix PR runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # ── Build both images (amd64 only — faster, sufficient for scanning) ── - name: Build Companion image run: docker build -t companion-scan:latest . - name: Build Sidecar image run: docker build -t sidecar-scan:latest ./sidecar # ── SARIF → GitHub Security tab ───────────────────────────────────── - name: Trivy — Companion (SARIF) uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: companion-scan:latest format: sarif output: trivy-companion.sarif severity: HIGH,CRITICAL ignore-unfixed: false - name: Trivy — Sidecar (SARIF) uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: sidecar-scan:latest format: sarif output: trivy-sidecar.sarif severity: HIGH,CRITICAL ignore-unfixed: false - name: Upload SARIF to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 with: sarif_file: '.' continue-on-error: true # ── JSON → parse CVEs & attempt Dockerfile fix ─────────────────────── - name: Trivy — Companion (JSON) uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: companion-scan:latest format: json output: trivy-companion.json severity: HIGH,CRITICAL ignore-unfixed: false - name: Trivy — Sidecar (JSON) uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: sidecar-scan:latest format: json output: trivy-sidecar.json severity: HIGH,CRITICAL ignore-unfixed: false - name: Compute Dockerfile fixes id: fix run: python3 .github/scripts/trivy_autofix.py # ── Create PR if the script modified any Dockerfile ────────────────── - name: Create fix PR if: steps.fix.outputs.changed == 'true' id: create-pr uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} branch: security/trivy-autofix delete-branch: false title: 'sécurité : mettre à jour les images Docker pour corriger les CVE' body-path: .github/pr_body.md add-paths: | Dockerfile sidecar/Dockerfile labels: | security automated commit-message: | security: bump Docker base images (Trivy CVE auto-fix) Automated update triggered by weekly Trivy scan. See PR body for full CVE list. # PRs created with GITHUB_TOKEN do not emit another pull_request event. # Dispatch the validation workflow explicitly on the generated branch. - name: Validate fix PR if: steps.create-pr.outputs.pull-request-operation == 'created' || steps.create-pr.outputs.pull-request-operation == 'updated' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_BRANCH: ${{ steps.create-pr.outputs.pull-request-branch }} run: gh workflow run docker-publish.yml --ref "$PR_BRANCH" # ── Count remaining fixable CVEs (for issue creation) ──────────────── - name: Count fixable CVEs id: count run: | python3 - <<'EOF' import json, os, pathlib total = 0 for f in ('trivy-companion.json', 'trivy-sidecar.json'): try: data = json.loads(pathlib.Path(f).read_text()) except Exception: continue for result in data.get('Results', []): for v in result.get('Vulnerabilities', []): if v.get('FixedVersion'): total += 1 with open(os.environ['GITHUB_OUTPUT'], 'a') as out: out.write(f'total={total}\n') EOF # ── Open / update issue if CVEs remain and no auto-fix PR was created ─ - name: Open issue if CVEs remain and no PR was created if: steps.fix.outputs.changed != 'true' && steps.count.outputs.total > 0 uses: actions/github-script@v7 with: script: | const fs = require('fs'); const body = fs.readFileSync('.github/pr_body.md', 'utf8'); const total = '${{ steps.count.outputs.total }}'; const title = '🔴 Trivy: ' + total + ' fixable CVE(s) require manual update'; const issues = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, labels: 'security,automated', state: 'open', }); const existing = issues.data.find(i => i.title.startsWith('🔴 Trivy:')); if (existing) { await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: existing.number, title, body, }); console.log('Updated existing issue #' + existing.number); } else { const issue = await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title, body, labels: ['security', 'automated'], }); console.log('Created issue #' + issue.data.number); }