Container Security Scan #365
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Container Security Scan | |
| on: | |
| # Allow manual triggering | |
| workflow_dispatch: | |
| # Run automatically once a day at 2 AM UTC | |
| schedule: | |
| - cron: '0 2 * * *' | |
| jobs: | |
| container-scan: | |
| name: Scan SuperTokens PostgreSQL Container | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Run Trivy vulnerability scanner | |
| id: container-scan | |
| uses: aquasecurity/trivy-action@master | |
| continue-on-error: true | |
| with: | |
| image-ref: supertokens/supertokens-postgresql:latest | |
| format: json | |
| output: trivy-results.json | |
| severity: LOW,MEDIUM,HIGH,CRITICAL | |
| exit-code: '0' | |
| - name: Upload scan results | |
| id: upload-scan-results | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: container-scan-results | |
| path: trivy-results.json | |
| retention-days: 30 | |
| - name: Generate Security Summary | |
| id: security-summary | |
| run: | | |
| SCAN_REPORT_PATH="trivy-results.json" | |
| # Build the Slack summary as a SINGLE LINE using \n escape sequences (literal | |
| # backslash-n in bash double quotes). When GitHub Actions substitutes this | |
| # single-line value into the double-quoted YAML scalar in the Slack payload, | |
| # the YAML parser treats \n as the newline character — avoiding the YAML | |
| # newline-folding-to-space problem that breaks multiline output variables. | |
| if [ -f "$SCAN_REPORT_PATH" ]; then | |
| critical=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| high=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "HIGH")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| medium=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "MEDIUM")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| low=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "LOW")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| total_vulns=$(jq '[.Results[].Vulnerabilities[]?] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| summary="*Image:* \`supertokens/supertokens-postgresql:latest\`\n*Scan Date:* \`$(date -u)\`\n\n*Total Vulnerabilities:* ${total_vulns}\n\n:red_circle: *Critical*: ${critical}\n:large_orange_circle: *High*: ${high}\n:large_yellow_circle: *Medium*: ${medium}\n:large_green_circle: *Low*: ${low}" | |
| else | |
| summary=":x: *Scan results not found or scan failed*" | |
| fi | |
| echo "summary=$summary" >> "$GITHUB_OUTPUT" | |
| - name: Add to Action Summary | |
| run: | | |
| SCAN_REPORT_PATH="trivy-results.json" | |
| echo "**Image:** \`supertokens/supertokens-postgresql:latest\`" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "**Scan Date:** \`$(date -u)\`" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| if [ -f "$SCAN_REPORT_PATH" ]; then | |
| critical=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| high=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "HIGH")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| medium=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "MEDIUM")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| low=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity == "LOW")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| total_vulns=$(jq '[.Results[].Vulnerabilities[]?] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0") | |
| echo "**Total Vulnerabilities:** $total_vulns" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- 🔴 **Critical**: $critical" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- 🟠 **High**: $high" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- 🟡 **Medium**: $medium" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- 🟢 **Low**: $low" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "**Vulnerabilities:**" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| ID | Package | Severity | | Description |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "|----|---------|----------|-|-------------|" >> "$GITHUB_STEP_SUMMARY" | |
| jq -r ' | |
| .Results[].Vulnerabilities[]? | |
| | select(.Severity != "LOW") | |
| | "| \(.VulnerabilityID // "N/A") | \(.PkgName // "N/A") | \(.Severity // "UNKNOWN") | \( | |
| if .Severity == "CRITICAL" then "🔴" | |
| elif .Severity == "HIGH" then "🟠" | |
| elif .Severity == "MEDIUM" then "🟡" | |
| else "🟢" end | |
| ) | \((.Description // "No description available") | gsub("[[:cntrl:]]"; " ")) |" | |
| ' "$SCAN_REPORT_PATH" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "❌ **Scan results not found or scan failed**" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "[📃 Download the full report](${{ steps.upload-scan-results.outputs.artifact-url }})" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Post notification on Slack channel | |
| id: deployment_message | |
| uses: slackapi/slack-github-action@v2.1.0 | |
| with: | |
| method: chat.postMessage | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| payload: | | |
| channel: ${{ secrets.SLACK_CHANNEL_ID }} | |
| text: "" | |
| blocks: | |
| - type: "header" | |
| text: | |
| type: "plain_text" | |
| text: "${{ steps.container-scan.outcome == 'success' && '✅' || '❌' }} Vulnerability Report: ${{ steps.container-scan.outcome == 'success' && 'All okay' || 'Needs attention' }}" | |
| - type: "section" | |
| text: | |
| type: "mrkdwn" | |
| text: "${{ steps.security-summary.outputs.summary }}" |