feat(security): Add comprehensive security scanning infrastructure #193
Workflow file for this run
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
| name: CI - Continuous Integration | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - develop | |
| pull_request: | |
| branches: | |
| - main | |
| - develop | |
| workflow_dispatch: | |
| # Cancel in-progress runs for the same workflow and ref | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Detect which parts of the codebase changed | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: read | |
| outputs: | |
| python: ${{ steps.filter.outputs.python }} | |
| frontend: ${{ steps.filter.outputs.frontend }} | |
| docker: ${{ steps.filter.outputs.docker }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| python: | |
| - 'phentrieve/**' | |
| - 'api/**' | |
| - 'tests/**' | |
| - 'pyproject.toml' | |
| - 'uv.lock' | |
| - '.github/workflows/ci.yml' | |
| frontend: | |
| - 'frontend/**' | |
| - '.github/workflows/ci.yml' | |
| docker: | |
| - '**/Dockerfile' | |
| - 'docker-compose*.yml' | |
| - '.github/workflows/ci.yml' | |
| # Python Backend CI | |
| python-ci: | |
| name: Python CI | |
| needs: changes | |
| if: needs.changes.outputs.python == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| python-version: ['3.10', '3.11', '3.12'] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| with: | |
| version: "latest" | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Cache uv dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cache/uv | |
| .venv | |
| key: ${{ runner.os }}-uv-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-uv-${{ matrix.python-version }}- | |
| ${{ runner.os }}-uv- | |
| - name: Install dependencies | |
| run: | | |
| uv sync --all-extras --dev | |
| - name: Run Ruff format check | |
| run: | | |
| uv run ruff format --check phentrieve/ api/ tests/ | |
| - name: Run Ruff linting | |
| run: | | |
| uv run ruff check phentrieve/ api/ tests/ | |
| - name: Run mypy type checking | |
| run: | | |
| uv run mypy phentrieve/ api/ | |
| continue-on-error: true # Don't fail on type errors (gradual typing) | |
| - name: Run pytest | |
| run: | | |
| uv run pytest tests/ -v -m "not e2e" --cov=phentrieve --cov=api --cov-report=xml --cov-report=term | |
| - name: Upload coverage to Codecov | |
| if: matrix.python-version == '3.10' | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: ./coverage.xml | |
| flags: python | |
| name: python-${{ matrix.python-version }} | |
| continue-on-error: true | |
| # Frontend CI | |
| frontend-ci: | |
| name: Frontend CI | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Run ESLint | |
| working-directory: frontend | |
| run: npm run lint | |
| - name: Run Prettier format check | |
| working-directory: frontend | |
| run: npm run format:check | |
| - name: Run Vitest tests | |
| working-directory: frontend | |
| run: npm run test:coverage | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: ./frontend/coverage/coverage-final.json | |
| flags: frontend | |
| name: frontend | |
| continue-on-error: true | |
| - name: Build frontend | |
| working-directory: frontend | |
| run: npm run build | |
| env: | |
| VITE_API_URL: /api/v1 | |
| # Docker Build Test | |
| docker-build-test: | |
| name: Docker Build Test | |
| needs: changes | |
| if: needs.changes.outputs.docker == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| service: [api, frontend] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Free disk space | |
| run: | | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf "$AGENT_TOOLSDIRECTORY" | |
| docker system prune -af | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build ${{ matrix.service }} image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ${{ matrix.service == 'api' && '.' || './frontend' }} | |
| file: ${{ matrix.service == 'api' && './api/Dockerfile' || './frontend/Dockerfile' }} | |
| push: false | |
| tags: ghcr.io/berntpopp/phentrieve/${{ matrix.service }}:test | |
| # No caching for test builds - we just need to verify they complete | |
| build-args: | | |
| ${{ matrix.service == 'frontend' && 'VITE_API_URL=/api/v1' || '' }} | |
| # Dependency Review - blocks PRs with vulnerable dependencies | |
| dependency-review: | |
| name: Dependency Review | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Dependency Review | |
| uses: actions/dependency-review-action@v4 | |
| with: | |
| fail-on-severity: high | |
| # Allow common open source licenses | |
| allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD, Unlicense, CC0-1.0, Python-2.0, PSF-2.0 | |
| # Deny copyleft licenses that might conflict | |
| deny-licenses: GPL-3.0-only, GPL-3.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later | |
| # Comment on PR with summary | |
| comment-summary-in-pr: on-failure | |
| # Summary job that requires all checks to pass | |
| ci-summary: | |
| name: CI Summary | |
| needs: [changes, python-ci, frontend-ci, docker-build-test, dependency-review] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check CI results | |
| run: | | |
| echo "Python CI: ${{ needs.python-ci.result }}" | |
| echo "Frontend CI: ${{ needs.frontend-ci.result }}" | |
| echo "Docker Build: ${{ needs.docker-build-test.result }}" | |
| echo "Dependency Review: ${{ needs.dependency-review.result }}" | |
| # Fail if any required job failed | |
| if [[ "${{ needs.python-ci.result }}" == "failure" ]] || \ | |
| [[ "${{ needs.frontend-ci.result }}" == "failure" ]] || \ | |
| [[ "${{ needs.docker-build-test.result }}" == "failure" ]]; then | |
| echo "❌ One or more CI jobs failed" | |
| exit 1 | |
| fi | |
| # Dependency review failure on PRs is critical | |
| if [[ "${{ needs.dependency-review.result }}" == "failure" ]]; then | |
| echo "❌ Dependency review found vulnerabilities" | |
| exit 1 | |
| fi | |
| echo "✅ All CI checks passed" |