name: Security Scan on: push: branches: [dev] paths: - 'backend/**' - 'builtin-extensions/**' - '**/requirements*.txt' - 'frontend/package*.json' - 'scripts/check-bandit-report.py' - 'scripts/security/**' - '.github/workflows/security-scan.yml' pull_request: branches: [main, dev] paths: - 'backend/**' - 'builtin-extensions/**' - '**/requirements*.txt' - 'frontend/package*.json' - 'scripts/check-bandit-report.py' - 'scripts/security/**' - '.github/workflows/security-scan.yml' schedule: - cron: '17 9 * * 1' # Weekly; advisories change even without repository changes. workflow_dispatch: permissions: contents: read jobs: # ────────────────────────────────────────────────────────────────── # Job 1: Bandit — static analysis for Python security issues # ────────────────────────────────────────────────────────────────── bandit: name: Bandit SAST runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install Bandit run: pip install bandit==1.9.3 - name: Run Bandit scan run: | python -m unittest discover -s scripts/security -p 'test_*.py' bandit -r backend/app builtin-extensions -f json -o bandit-report.json --exit-zero python scripts/check-bandit-report.py bandit-report.json - name: Upload full Bandit report if: always() uses: actions/upload-artifact@v4 with: name: bandit-report path: bandit-report.json if-no-files-found: warn # ────────────────────────────────────────────────────────────────── # Job 2: pip-audit — dependency vulnerability scanning (the gate) # # This is the blocking check. Unlike the Safety job below it needs no # account, no API key and no network-fetched policy: it resolves the pinned # requirements against the PyPI Advisory Database and fails on anything it # finds. Every suppression therefore has to live in THIS file, in a diff a # reviewer can see — which is the whole point. Safety was reporting # "No issues found" while its Platform-side policy silently ignored four # advisories, one of them a real, fixable CVE in a directly pinned package. # ────────────────────────────────────────────────────────────────── pip-audit: name: pip-audit Dependency Scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install pip-audit run: pip install pip-audit # No --ignore-vuln entries today. If one is ever needed, add it here with # a comment saying which advisory, why it does not apply, and when to # revisit — never by silencing the scanner globally. - name: Audit backend requirements run: pip-audit -r backend/requirements.txt --progress-spinner off - name: Audit test-harness requirements run: pip-audit -r scripts/test/harness/requirements.txt --progress-spinner off - name: Audit extension requirements shell: bash run: | while IFS= read -r -d '' requirements; do pip-audit -r "$requirements" --progress-spinner off done < <(find builtin-extensions -type f -name 'requirements*.txt' -print0) npm-audit: name: Frontend Production Dependency Scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' cache: npm cache-dependency-path: frontend/package-lock.json - name: Audit production dependency lockfile working-directory: frontend # No installation or package scripts are needed to audit the lockfile. run: npm audit --package-lock-only --omit=dev --audit-level=high # ────────────────────────────────────────────────────────────────── # Job 3: Safety — advisory only # # Kept for the Safety Platform dashboard, but deliberately NOT a gate. # The action shells out to a hosted service, and a service-side failure # there (results upload erroring with "Sorry, something went wrong" while # the scan itself reported clean) took CI red on 2026-07-20 with no repo # change behind it — requirements had not moved since 2026-07-04. An # outage at a third party should not block merges; pip-audit above is the # check that actually has to pass. # ────────────────────────────────────────────────────────────────── safety: name: Safety Dependency Scan (advisory) runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 - name: Run Safety CLI to check for vulnerabilities continue-on-error: true uses: pyupio/safety-action@v1 with: api-key: ${{ secrets.SAFETY_API_KEY }} args: --full-report