name: Bearer Security Scan on: workflow_dispatch: workflow_call: # Called by release-gate.yml schedule: # Run security scan daily at 4 AM UTC - cron: '0 4 * * *' permissions: {} # Minimal top-level for OSSF Scorecard Token-Permissions jobs: bearer-scan: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: read security-events: write actions: read steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - name: Checkout code uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Run Bearer SAST Scanner uses: bearer/bearer-action@828eeb928ce2f4a7ca5ed57fb8b59508cb8c79bc # v2 with: scanner: sast,secrets config-file: bearer.yml format: sarif output: bearer-results.sarif # DO NOT change to exit-code: 1 — findings are enforced via SARIF alerts # in the release gate (check-code-scanning-alerts job in release-gate.yml). # Failing here would break CI without adding security value. exit-code: 0 # Fail loudly if Bearer produced no SARIF — never fabricate an empty one. # An empty-results SARIF uploaded under the bearer-security category would # make GitHub mark every previously-open Bearer alert as fixed, silently # clearing real findings. Mirror the Grype/Trivy jobs: error + exit 1, skip # upload. bearer-action writes a SARIF even with zero findings, so a missing # file means a real scan failure, not a clean run. - name: Ensure SARIF file exists id: check-sarif if: always() run: | if [ -f bearer-results.sarif ]; then echo "exists=true" >> "$GITHUB_OUTPUT" else echo "::error::Bearer did not produce a SARIF file — scan needs to be rerun" exit 1 fi - name: Upload Bearer results to GitHub Security tab uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 if: always() && steps.check-sarif.outputs.exists == 'true' with: sarif_file: bearer-results.sarif category: bearer-security - name: Upload Bearer results as artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: always() && steps.check-sarif.outputs.exists == 'true' with: name: bearer-scan-results path: bearer-results.sarif retention-days: 7 - name: Display Bearer summary if: always() run: | { echo "## Bearer Security Scan Summary" echo "" } >> "$GITHUB_STEP_SUMMARY" if [ -f bearer-results.sarif ]; then # Count findings by level ERRORS=$(python3 -c "import json; data=json.load(open('bearer-results.sarif')); results=data.get('runs',[{}])[0].get('results',[]); print(len([r for r in results if r.get('level')=='error']))" 2>/dev/null || echo "0") WARNINGS=$(python3 -c "import json; data=json.load(open('bearer-results.sarif')); results=data.get('runs',[{}])[0].get('results',[]); print(len([r for r in results if r.get('level')=='warning']))" 2>/dev/null || echo "0") NOTES=$(python3 -c "import json; data=json.load(open('bearer-results.sarif')); results=data.get('runs',[{}])[0].get('results',[]); print(len([r for r in results if r.get('level')=='note']))" 2>/dev/null || echo "0") TOTAL=$(python3 -c "import json; data=json.load(open('bearer-results.sarif')); print(len(data.get('runs',[{}])[0].get('results',[])))" 2>/dev/null || echo "0") { echo "📊 **Scan Results:**" echo "- **Errors:** $ERRORS" echo "- **Warnings:** $WARNINGS" echo "- **Notes:** $NOTES" echo "- **Total:** $TOTAL" echo "" } >> "$GITHUB_STEP_SUMMARY" if [ "$ERRORS" -gt 0 ]; then echo "⚠️ **Action Required:** Error-level issues found" >> "$GITHUB_STEP_SUMMARY" elif [ "$WARNINGS" -gt 0 ]; then echo "⚠️ **Review Recommended:** Warning-level issues found" >> "$GITHUB_STEP_SUMMARY" else echo "✅ **No Error or Warning level issues found**" >> "$GITHUB_STEP_SUMMARY" fi { echo "" echo "📋 **Details:**" echo "- Bearer scans for sensitive data flow and secrets exposure" echo "- Results uploaded to GitHub Security tab" echo "- SARIF report available in artifacts" } >> "$GITHUB_STEP_SUMMARY" else echo "❌ Bearer scan failed or no results generated" >> "$GITHUB_STEP_SUMMARY" fi