Quality Security #15
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: Quality Security | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| schedule: | |
| - cron: '0 0 * * 1' # Weekly on Monday at midnight UTC | |
| # Cancel in-progress runs for the same branch/PR | |
| concurrency: | |
| group: security-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| security-events: write | |
| actions: read | |
| jobs: | |
| codeql: | |
| name: CodeQL (${{ matrix.language }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| language: [python, javascript-typescript] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Initialize CodeQL | |
| uses: github/codeql-action/init@v3 | |
| with: | |
| languages: ${{ matrix.language }} | |
| queries: +security-extended,security-and-quality | |
| - name: Autobuild | |
| uses: github/codeql-action/autobuild@v3 | |
| - name: Perform CodeQL Analysis | |
| uses: github/codeql-action/analyze@v3 | |
| with: | |
| category: "/language:${{ matrix.language }}" | |
| python-security: | |
| name: Python Security (Bandit) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Install Bandit | |
| run: pip install bandit | |
| - name: Run Bandit security scan | |
| id: bandit | |
| run: | | |
| echo "::group::Running Bandit security scan" | |
| # Run Bandit; exit code 1 means issues found (expected), other codes are errors | |
| # Flags: -r=recursive, -ll=severity LOW+, -ii=confidence LOW+, -f=format, -o=output | |
| bandit -r apps/backend/ -ll -ii -f json -o bandit-report.json || BANDIT_EXIT=$? | |
| if [ "${BANDIT_EXIT:-0}" -gt 1 ]; then | |
| echo "::error::Bandit scan failed with exit code $BANDIT_EXIT" | |
| exit 1 | |
| fi | |
| echo "::endgroup::" | |
| - name: Analyze Bandit results | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| // Check if report exists | |
| if (!fs.existsSync('bandit-report.json')) { | |
| core.setFailed('Bandit report not found - scan may have failed'); | |
| return; | |
| } | |
| const report = JSON.parse(fs.readFileSync('bandit-report.json', 'utf8')); | |
| const results = report.results || []; | |
| // Categorize by severity | |
| const high = results.filter(r => r.issue_severity === 'HIGH'); | |
| const medium = results.filter(r => r.issue_severity === 'MEDIUM'); | |
| const low = results.filter(r => r.issue_severity === 'LOW'); | |
| console.log(`::group::Bandit Security Scan Results`); | |
| console.log(`Found ${results.length} issues:`); | |
| console.log(` π΄ HIGH: ${high.length}`); | |
| console.log(` π‘ MEDIUM: ${medium.length}`); | |
| console.log(` π’ LOW: ${low.length}`); | |
| console.log(''); | |
| // Print high severity issues | |
| if (high.length > 0) { | |
| console.log('High Severity Issues:'); | |
| console.log('β'.repeat(60)); | |
| for (const issue of high) { | |
| console.log(` ${issue.filename}:${issue.line_number}`); | |
| console.log(` ${issue.issue_text}`); | |
| console.log(` Test: ${issue.test_id} (${issue.test_name})`); | |
| console.log(''); | |
| } | |
| } | |
| console.log('::endgroup::'); | |
| // Build summary | |
| let summary = `## π Python Security Scan (Bandit)\n\n`; | |
| summary += `| Severity | Count |\n`; | |
| summary += `|----------|-------|\n`; | |
| summary += `| π΄ High | ${high.length} |\n`; | |
| summary += `| π‘ Medium | ${medium.length} |\n`; | |
| summary += `| π’ Low | ${low.length} |\n\n`; | |
| if (high.length > 0) { | |
| summary += `### High Severity Issues\n\n`; | |
| for (const issue of high) { | |
| summary += `- **${issue.filename}:${issue.line_number}**\n`; | |
| summary += ` - ${issue.issue_text}\n`; | |
| summary += ` - Test: \`${issue.test_id}\` (${issue.test_name})\n\n`; | |
| } | |
| } | |
| core.summary.addRaw(summary); | |
| await core.summary.write(); | |
| // Fail if high severity issues found | |
| if (high.length > 0) { | |
| core.setFailed(`Found ${high.length} high severity security issue(s)`); | |
| } else { | |
| console.log('β No high severity security issues found'); | |
| } | |
| # Summary job that waits for all security checks | |
| security-summary: | |
| name: Security Summary | |
| runs-on: ubuntu-latest | |
| needs: [codeql, python-security] | |
| if: always() | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Check security results | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const codeql = '${{ needs.codeql.result }}'; | |
| const bandit = '${{ needs.python-security.result }}'; | |
| console.log('Security Check Results:'); | |
| console.log(` CodeQL: ${codeql}`); | |
| console.log(` Bandit: ${bandit}`); | |
| // Only 'failure' is a real failure; 'skipped' is acceptable (e.g., path filters) | |
| const acceptable = ['success', 'skipped']; | |
| const codeqlOk = acceptable.includes(codeql); | |
| const banditOk = acceptable.includes(bandit); | |
| const allPassed = codeqlOk && banditOk; | |
| if (allPassed) { | |
| console.log('\nβ All security checks passed'); | |
| core.summary.addRaw('## β Security Checks Passed\n\nAll security scans completed successfully.'); | |
| } else { | |
| console.log('\nβ Some security checks failed'); | |
| core.summary.addRaw('## β Security Checks Failed\n\nOne or more security scans found issues.'); | |
| core.setFailed('Security checks failed'); | |
| } | |
| await core.summary.write(); |