name: Security Scan
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
# Run security scan daily at 2 AM UTC
- cron: '0 2 * * *'
jobs:
security:
name: Security Analysis
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install bandit[toml] safety semgrep
- name: Run Bandit Security Linter
run: |
echo "🔍 Running Bandit security analysis..."
echo "Current directory: $(pwd)"
echo "Source directory contents:"
ls -la src/ || echo "src/ directory not found"
# Create empty SARIF file first
echo '{"version": "2.1.0", "runs": [{"tool": {"driver": {"name": "bandit", "version": "1.0.0"}}, "results": []}]}' > bandit-results.sarif
# Run Bandit and capture exit code
bandit_exit_code=0
bandit -r src/ -f sarif -o bandit-results.sarif || bandit_exit_code=$?
echo "Bandit exit code: $bandit_exit_code"
echo "SARIF file exists: $(test -f bandit-results.sarif && echo 'yes' || echo 'no')"
echo "SARIF file size: $(wc -c < bandit-results.sarif 2>/dev/null || echo '0') bytes"
# Ensure file exists and has valid content
if [ ! -f bandit-results.sarif ] || [ ! -s bandit-results.sarif ]; then
echo "Creating fallback SARIF file..."
echo '{"version": "2.1.0", "runs": [{"tool": {"driver": {"name": "bandit", "version": "1.0.0"}}, "results": []}]}' > bandit-results.sarif
fi
echo "Final SARIF file check:"
ls -la bandit-results.sarif
head -n 5 bandit-results.sarif
continue-on-error: true
- name: Upload Bandit results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: bandit-results.sarif
- name: Run Safety - Check for known vulnerabilities
run: |
safety check --json --output safety-report.json
continue-on-error: true
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/python
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout Repository
uses: actions/checkout@v5
- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: moderate
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"