Security #34
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
| # ───────────────────────────────────────────────────────────────────────────── | |
| # DevSec Vault — Security Scanning Pipeline | |
| # All actions pinned to full commit SHA (Scorecard: Pinned-Dependencies) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| name: Security | |
| on: | |
| push: | |
| branches: ["main", "develop"] | |
| pull_request: | |
| schedule: | |
| - cron: "0 3 * * 1" # Weekly Monday 03:00 UTC | |
| # Minimal global permissions — each job declares only what it needs | |
| permissions: | |
| contents: read | |
| jobs: | |
| # ── 1. Secret Scanning ──────────────────────────────────────────────────── | |
| secret-scan: | |
| name: Secret Scan (file + history) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write # SARIF upload — scoped to this job only | |
| steps: | |
| - name: Checkout (full history) | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: "3.11" | |
| cache: "pip" | |
| - name: Install DevSec Vault dependencies | |
| run: pip install "click==8.1.8" "rich==13.9.4" | |
| - name: Create reports directory | |
| run: mkdir -p reports | |
| - name: Scan source files | |
| run: | | |
| python src/cli.py scan src/ \ | |
| --format json \ | |
| --output reports/file-scan.json \ | |
| --baseline .devsec-baseline.json \ | |
| --fail-on high | |
| env: | |
| DEVSEC_EXCLUDE: "src/fake_secrets.py" | |
| - name: Scan git history (last 300 commits) | |
| run: | | |
| python src/cli.py history \ | |
| --max-commits 300 \ | |
| --format sarif \ | |
| --output reports/history-scan.sarif \ | |
| --baseline .devsec-baseline.json | |
| continue-on-error: true | |
| - name: Upload SARIF — history scan | |
| uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 | |
| if: always() && hashFiles('reports/history-scan.sarif') != '' | |
| with: | |
| sarif_file: reports/history-scan.sarif | |
| category: devsec-vault-history | |
| continue-on-error: true | |
| - name: Upload scan artifacts | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: scan-reports | |
| path: reports/ | |
| retention-days: 30 | |
| # ── 2. Static Analysis (Bandit) ─────────────────────────────────────────── | |
| static-analysis: | |
| name: Static Analysis (Bandit) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install Bandit | |
| run: pip install "bandit==1.8.3" | |
| - name: Create reports directory | |
| run: mkdir -p reports | |
| - name: Run Bandit | |
| run: | | |
| bandit -r src/ \ | |
| --exclude src/fake_secrets.py \ | |
| --format json \ | |
| --output reports/bandit.json \ | |
| --severity-level medium \ | |
| --confidence-level medium || true | |
| - name: Print Bandit summary | |
| if: always() | |
| run: | | |
| python3 -c " | |
| import json, sys | |
| try: | |
| d = json.load(open('reports/bandit.json')) | |
| m = d.get('metrics', {}).get('_totals', {}) | |
| issues = d.get('results', []) | |
| print(f'Bandit: {len(issues)} issue(s) found') | |
| for sev in ['HIGH','MEDIUM']: | |
| count = m.get(f'SEVERITY.{sev}', 0) | |
| if count: print(f' {sev}: {count}') | |
| if any(r[\"issue_severity\"] == \"HIGH\" for r in issues): | |
| print('HIGH severity issues found — failing') | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f'Could not parse bandit report: {e}') | |
| " | |
| - name: Upload Bandit report | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: bandit-report | |
| path: reports/bandit.json | |
| retention-days: 30 | |
| # ── 3. CodeQL ───────────────────────────────────────────────────────────── | |
| codeql: | |
| name: CodeQL (Python) | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| permissions: | |
| contents: read | |
| actions: read | |
| security-events: write # SARIF upload — scoped to this job only | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Initialize CodeQL | |
| uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 | |
| with: | |
| languages: python | |
| queries: security-and-quality | |
| - name: Autobuild | |
| uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 | |
| - name: Perform CodeQL Analysis | |
| uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 | |
| with: | |
| category: "/language:python" | |
| continue-on-error: true | |
| # ── 4. Dependency Audit ─────────────────────────────────────────────────── | |
| dependency-audit: | |
| name: Dependency Audit (pip-audit) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install pip-audit | |
| run: pip install "pip-audit==2.9.0" | |
| - name: Create reports directory | |
| run: mkdir -p reports | |
| - name: Run pip-audit | |
| run: | | |
| pip-audit \ | |
| --requirement requirements.txt \ | |
| --format json \ | |
| --output reports/pip-audit.json \ | |
| --progress-spinner off | |
| continue-on-error: true | |
| - name: Print pip-audit summary | |
| if: always() | |
| run: | | |
| python3 -c " | |
| import json | |
| try: | |
| d = json.load(open('reports/pip-audit.json')) | |
| vulns = [dep for dep in d if dep.get('vulns')] | |
| print(f'pip-audit: {len(vulns)} package(s) with vulnerabilities') | |
| for dep in vulns: | |
| for v in dep['vulns']: | |
| print(f' {dep[\"name\"]}=={dep[\"version\"]}: {v[\"id\"]} ({v.get(\"fix_versions\",[])})') | |
| except Exception as e: | |
| print(f'Could not parse pip-audit report: {e}') | |
| " | |
| - name: Upload pip-audit report | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: always() | |
| with: | |
| name: dependency-audit | |
| path: reports/pip-audit.json | |
| retention-days: 30 | |
| # ── 5. Unit Tests ───────────────────────────────────────────────────────── | |
| test: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: "3.11" | |
| cache: "pip" | |
| - name: Install dependencies | |
| run: pip install "click==8.1.8" "rich==13.9.4" "pytest==9.0.3" | |
| - name: Run tests | |
| run: pytest tests/ -v --tb=short |