name: CI - Security & Lint on: push: branches: [main, master] pull_request: branches: [main, master] workflow_dispatch: # Allow manual triggers concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true # Least privilege: nothing here writes back to the repository. permissions: contents: read env: PYTHON_VERSION: '3.12' jobs: security-audit: name: Security Audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install pip-audit run: pip install pip-audit==2.9.0 # Previously: `pip-audit --require-hashes --no-deps || pip-audit`. # Neither form named the requirements file, so the fallback audited the # runner's environment — which contained pip-audit and nothing else. # The job passed green while checking none of the application's # dependencies. -r makes it audit what the application actually pins. - name: Scan declared dependencies for known vulnerabilities run: pip-audit -r requirements.txt lint: name: Lint with Ruff runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install ruff run: pip install ruff==0.14.4 # Rule selection and per-file ignores live in pyproject.toml. Before it # existed, this step ran ruff's bare defaults with no configuration. - name: Run ruff linter run: ruff check . --output-format=github # `ruff format --check` is deliberately absent for now: the codebase has # never been formatted, so it would fail on 62 of 64 files for reasons # unrelated to correctness. Reformatting in one isolated commit and then # enforcing it here is tracked as QUA-002. security-scan: name: Security Scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install app dependencies run: pip install -r requirements.txt -r requirements-dev.txt # The path was `security_scan.py`, but the script lives under # app/supporting_scripts/. The step had therefore failed on every run # since the file was moved. - name: Run security scan env: SECRET_KEY: ${{ secrets.CI_SECRET_KEY || 'test-key-not-for-production-1234567890' }} FLASK_DEBUG: 'false' run: python app/supporting_scripts/security_scan.py --skip-http test: name: Tests runs-on: ubuntu-latest needs: [security-audit, lint] steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: 'pip' - name: Install dependencies run: pip install -r requirements.txt -r requirements-dev.txt # Previously an `echo` guarded by continue-on-error: the job reported # success without executing anything. The suite needs no environment # variables and no database server: create_app() takes its configuration # as an argument and the fixtures use a temporary SQLite file. - name: Run tests run: pytest --cov=app --cov-report=term-missing --cov-report=xml