| |
| |
| |
| |
|
|
| name: VulnGraph Security Scan |
|
|
| on: |
| push: |
| branches: [main, master, develop] |
| pull_request: |
| branches: [main, master] |
| env: |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true |
|
|
| jobs: |
| security-scan: |
| name: Security Scan |
| runs-on: ubuntu-latest |
|
|
| steps: |
| - name: Checkout code |
| 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 SAST |
| id: bandit |
| run: | |
| bandit -r app/ \ |
| -f json \ |
| --quiet \ |
| --exclude app/.venv,app/__pycache__,app/tmp,app/data \ |
| -o bandit-report.json || true |
| |
| |
| HIGHS=$(python3 -c " |
| import json, sys |
| try: |
| data = json.load(open('bandit-report.json')) |
| highs = sum(1 for r in data.get('results', []) if r.get('issue_severity') == 'HIGH') |
| print(highs) |
| except: print(0) |
| ") |
| |
| MEDIUMS=$(python3 -c " |
| import json |
| try: |
| data = json.load(open('bandit-report.json')) |
| mediums = sum(1 for r in data.get('results', []) if r.get('issue_severity') == 'MEDIUM') |
| print(mediums) |
| except: print(0) |
| ") |
| |
| LOWS=$(python3 -c " |
| import json |
| try: |
| data = json.load(open('bandit-report.json')) |
| lows = sum(1 for r in data.get('results', []) if r.get('issue_severity') == 'LOW') |
| print(lows) |
| except: print(0) |
| ") |
| |
| echo "highs=$HIGHS" >> $GITHUB_OUTPUT |
| echo "mediums=$MEDIUMS" >> $GITHUB_OUTPUT |
| echo "lows=$LOWS" >> $GITHUB_OUTPUT |
| echo "Bandit: HIGH=$HIGHS MEDIUM=$MEDIUMS LOW=$LOWS" |
|
|
| - name: Run Trivy vulnerability scan |
| uses: aquasecurity/trivy-action@master |
| with: |
| scan-type: fs |
| scan-ref: . |
| format: json |
| output: trivy-report.json |
| severity: CRITICAL,HIGH,MEDIUM |
| skip-dirs: .venv,data,tmp,tools,node_modules |
|
|
| - name: Parse Trivy results |
| id: trivy |
| run: | |
| CRITICALS=$(python3 -c " |
| import json |
| try: |
| data = json.load(open('trivy-report.json')) |
| crits = sum( |
| len([v for v in r.get('Vulnerabilities', []) if v.get('Severity') == 'CRITICAL']) |
| for r in data.get('Results', []) |
| ) |
| print(crits) |
| except: print(0) |
| ") |
| |
| HIGHS=$(python3 -c " |
| import json |
| try: |
| data = json.load(open('trivy-report.json')) |
| highs = sum( |
| len([v for v in r.get('Vulnerabilities', []) if v.get('Severity') == 'HIGH']) |
| for r in data.get('Results', []) |
| ) |
| print(highs) |
| except: print(0) |
| ") |
| |
| echo "criticals=$CRITICALS" >> $GITHUB_OUTPUT |
| echo "highs=$HIGHS" >> $GITHUB_OUTPUT |
| echo "Trivy: CRITICAL=$CRITICALS HIGH=$HIGHS" |
|
|
| - name: Post PR comment with findings |
| if: github.event_name == 'pull_request' |
| uses: actions/github-script@v7 |
| with: |
| script: | |
| const banditHighs = '${{ steps.bandit.outputs.highs }}' || 0; |
| const banditMediums = '${{ steps.bandit.outputs.mediums }}' || 0; |
| const banditLows = '${{ steps.bandit.outputs.lows }}' || 0; |
| const trivyCriticals = '${{ steps.trivy.outputs.criticals }}'|| 0; |
| const trivyHighs = '${{ steps.trivy.outputs.highs }}'|| 0; |
| |
| const statusEmoji = trivyCriticals > 0 ? 'π΄' : trivyHighs > 0 || banditHighs > 0 ? 'π‘' : 'π’'; |
| |
| const body = `## ${statusEmoji} VulnGraph Security Scan Results |
| |
| | Scanner | Critical | High | Medium | Low | |
| |---------|----------|------|--------|-----| |
| | π Trivy (SCA) | ${trivyCriticals} | ${trivyHighs} | - | - | |
| | π Bandit (SAST) | - | ${banditHighs} | ${banditMediums} | ${banditLows} | |
| |
| ${trivyCriticals > 0 ? 'β **CRITICAL vulnerabilities found β merge blocked**' : ''} |
| ${trivyHighs > 0 || banditHighs > 0 ? 'β οΈ High severity findings require review before merge' : ''} |
| ${trivyCriticals == 0 && trivyHighs == 0 && banditHighs == 0 ? 'β
No critical or high severity findings' : ''} |
| |
| > Powered by [VulnGraph](https://github.com/your-username/VulnGraph) β AI-powered ASPM |
| `; |
| |
| github.rest.issues.createComment({ |
| issue_number: context.issue.number, |
| owner: context.repo.owner, |
| repo: context.repo.repo, |
| body: body |
| }); |
|
|
| - name: Fail on CRITICAL vulnerabilities |
| if: steps.trivy.outputs.criticals != '0' |
| run: | |
| echo "CRITICAL vulnerabilities found. Failing workflow." |
| echo "Fix these before merging:" |
| python3 -c " |
| import json |
| data = json.load(open('trivy-report.json')) |
| for r in data.get('Results', []): |
| for v in r.get('Vulnerabilities', []): |
| if v.get('Severity') == 'CRITICAL': |
| print(f' - {v[\"VulnerabilityID\"]} in {v.get(\"PkgName\",\"unknown\")} ({r.get(\"Target\",\"\")})') |
| " |
| exit 1 |
| |
| - name: Upload scan reports |
| if: always() |
| uses: actions/upload-artifact@v4 |
| with: |
| name: security-scan-reports |
| path: | |
| bandit-report.json |
| trivy-report.json |
| retention-days: 30 |