name: Security & Quality on: push: branches: [main] pull_request: branches: [main] permissions: contents: read jobs: quality: name: Lint, Typecheck & Test runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: "pnpm" - run: pnpm install --frozen-lockfile - run: pnpm db:generate # The hero-geometry check in the unit suite renders the real panel in a # real browser and measures the boxes. Without a browser here it caught # its own launch failure and passed anyway, so from its first day until # v1.38 it measured nothing on any run of this job. The browser is part # of the gate now; the test refuses to skip when CI is set. # # Headless shell only: `chromium.launch()` resolves to # chromium_headless_shell, which is what the failing runs named, and the # full headed build is several times the download for nothing this job # uses. The e2e job keeps `--with-deps chromium` — it drives a real app. - name: Cache Playwright headless shell uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.cache/ms-playwright key: playwright-shell-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | playwright-shell-${{ runner.os }}- - run: pnpm exec playwright install --only-shell chromium # Blocking since v1.15.20 — the settings-split refactor that kept # this warn-only is long done and the tree lints clean. - name: ESLint run: pnpm lint # Prettier formatting gate. The format pass excludes generator-owned # artifacts via .prettierignore (notably docs/api/openapi.yaml, which # the OpenAPI drift check below enforces byte-for-byte). - name: Prettier format check run: pnpm format:check - name: TypeScript strict check run: pnpm typecheck - name: Unit tests run: pnpm test # OpenAPI spec drift gate — hard-fail (flipped from warn-only in v1.4.25). # # The Zod registry is the source of truth for the public API contract. # Any route signature change must be reflected in `docs/api/openapi.yaml` # so downstream consumers (notably the v1.5 iOS Swift codegen) stay in # lockstep. If this step fails, run `pnpm openapi:generate` locally and # commit the regenerated spec. - name: OpenAPI spec drift check run: | pnpm openapi:generate if ! git diff --exit-code docs/api/openapi.yaml; then echo "::error::OpenAPI drift detected — regenerate the spec via 'pnpm openapi:generate' and commit the result. The Zod registry under src/lib/openapi/ is the source of truth for the public API contract." exit 1 fi echo "::notice::OpenAPI spec in sync with Zod source schemas." dependency-audit: name: Dependency Audit runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 22 cache: "pnpm" - run: pnpm install --frozen-lockfile # Blocking since v1.15.20 — a HIGH/CRITICAL advisory against a # production dependency fails the build. If an advisory has no # upstream fix yet, pin an `auditConfig.ignoreCves` entry in # package.json (pnpm's documented suppression mechanism) with a # comment referencing the tracking issue, rather than flipping # this step back to warn-only. - name: Audit for known vulnerabilities run: pnpm audit --prod --audit-level=high secret-scan: name: Secret Scanning runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Gitleaks secret scan uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} docker-security: name: Container Security runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Build Docker image run: docker build -t healthlog:scan . - name: Install Trivy run: | sudo apt-get install -y wget apt-transport-https gnupg wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list sudo apt-get update && sudo apt-get install -y trivy # Blocking since v1.15.20 — a HIGH/CRITICAL finding in the built # image fails the job. For a finding without an upstream fix # (e.g. a base-image CVE Alpine has not patched yet), add the CVE # id to a `.trivyignore` file at the repository root — Trivy reads # it automatically — one CVE per line, each with a comment naming # the tracking issue and the re-check condition. Never suppress a # finding that has a fixed version available; bump instead. - name: Trivy container scan run: trivy image --format table --severity CRITICAL,HIGH --exit-code 1 healthlog:scan