name: Docker Vulnerability Scan on: schedule: # Run Monday at midnight UTC - cron: '0 0 * * 1' workflow_dispatch: inputs: image-tag: description: 'Image tag to scan' required: true default: main type: string permissions: contents: read env: DOCKER_REGISTRY: "ghcr.io/comet-ml/opik" jobs: scan-docker-images: runs-on: ubuntu-latest strategy: fail-fast: false matrix: image: - opik-backend - opik-frontend - opik-frontend-comet - opik-python-backend - opik-sandbox-executor-python name: Scan ${{ matrix.image }} steps: - name: Pull Docker image id: pull run: | IMAGE_URL="${{ env.DOCKER_REGISTRY }}/${{ matrix.image }}:${{ inputs.image-tag || 'main' }}" echo "Pulling Docker image: ${IMAGE_URL}" docker pull ${IMAGE_URL} BASENAME=$(basename ${IMAGE_URL} | cut -d: -f1) REPORT_NAME="trivy_${BASENAME}.txt" echo "image-url=${IMAGE_URL}" >> "$GITHUB_OUTPUT" echo "report-name=${REPORT_NAME}" >> "$GITHUB_OUTPUT" - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@v0.35.0 env: TRIVY_DISABLE_VEX_NOTICE: true with: image-ref: '${{ steps.pull.outputs.image-url }}' scan-type: 'image' format: 'table' ignore-unfixed: 'false' output: '${{ steps.pull.outputs.report-name }}' scanners: vuln severity: 'CRITICAL,HIGH' hide-progress: true timeout: 10m0s - name: Set outputs id: set-outputs run: | REPORT_NAME="${{ steps.pull.outputs.report-name }}" echo "report-file=${REPORT_NAME}" >> "$GITHUB_OUTPUT" if [ -f "${REPORT_NAME}" ]; then echo "Report generated: ${REPORT_NAME}" ls -lh "${REPORT_NAME}" else echo "Warning: Report file not found at ${REPORT_NAME}" fi - name: Write to job summary run: | { echo "## Trivy Vulnerability Scan: \`${{ steps.pull.outputs.image-url }}\`" echo "" echo "
Click to expand report" echo "" echo '```' cat "${{ steps.pull.outputs.report-name }}" 2>/dev/null || echo "Report not found" echo '```' echo "" echo "
" } >> "$GITHUB_STEP_SUMMARY" - name: Upload scan results as artifact uses: actions/upload-artifact@v7 with: name: trivy-scan-${{ matrix.image }} path: ${{ steps.pull.outputs.report-name }} retention-days: 30 if-no-files-found: warn - name: Checkout scripts uses: actions/checkout@v6 with: sparse-checkout: scripts/analyze_trivy_report.sh sparse-checkout-cone-mode: false path: repo - name: Analyze vulnerabilities id: analyze run: | ./repo/scripts/analyze_trivy_report.sh \ "${{ steps.pull.outputs.report-name }}" \ "${{ steps.pull.outputs.image-url }}" \ --workflow-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ --artifact-name "trivy-scan-${{ matrix.image }}" - name: Format base image Slack message if: steps.analyze.outputs.has-base-image-vulns == 'true' id: format-base env: MESSAGE: ${{ steps.analyze.outputs.base-image-message }} MENTION: ${{ secrets.SLACK_MENTION_USERS }} run: | python3 << 'PYTHON_SCRIPT' import re, os input_message = os.environ.get('MESSAGE', '').strip() mention = os.environ.get('MENTION', '').strip() result = re.sub(r"'(.*?)'", r"`\1`", input_message) if mention: # Ensure each user ID is wrapped in <@...> for Slack mention syntax parts = mention.split() wrapped = ' '.join( uid if uid.startswith('<@') else f'<@{uid}>' for uid in parts ) result = wrapped + '\n\n' + result with open(os.environ.get('GITHUB_OUTPUT', '/dev/null'), 'a') as f: f.write('message< for Slack mention syntax parts = mention.split() wrapped = ' '.join( uid if uid.startswith('<@') else f'<@{uid}>' for uid in parts ) result = wrapped + '\n\n' + result with open(os.environ.get('GITHUB_OUTPUT', '/dev/null'), 'a') as f: f.write('message< ${{ steps.format-base.outputs.message }} - name: Notify Slack - application vulnerabilities if: steps.analyze.outputs.has-app-vulns == 'true' uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{ secrets.SLACK_VULNERABILITY_WEBHOOK_URL }} SLACK_COLOR: danger SLACK_USERNAME: 'GitHub Actions' SLACK_ICON: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png' SLACK_LINK_NAMES: true MSG_MINIMAL: false ENABLE_ESCAPES: true SLACK_TITLE: ${{ steps.analyze.outputs.app-title }} SLACK_MESSAGE: | *Run:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}> ${{ steps.format-app.outputs.message }}