Skip to content

Add PR quality, security and build checks #339

Closed
1 of 2 issues completed
Closed

Description

@vermorag

Summary

Add a set of CI checks for pull requests and scheduled repository security analysis.

The goal is to move failures as early as possible into PR validation instead of discovering them during release builds, and to cover areas that are currently either unchecked or only partially checked.

Already in place

  • PHPUnit on MySQL 8.4 — tests.yml / PHPUnit job (no coverage yet).
  • Frontend install, lint and JS tests — tests.yml / Frontend lint and tests job: pnpm install --frozen-lockfile, pnpm lint, pnpm test. This already catches lockfile drift and lint failures.
  • CodeQL — default setup is enabled for actions and javascript-typescript.
  • Secret scanning with push protection, Dependabot alerts and security updates — enabled in repository settings.
  • actionlint — runs only for release.yml and release-check.yml (release-check.yml).
  • Dependabot version updates — npm (pnpm lockfile), Composer, Docker.
  • tests.yml actions and the MySQL service image are pinned to immutable references.

The Main ruleset currently requires only the Cattr CLA status check.

Checks to add

Required PR checks

  • Make existing test jobs required

    • Add PHPUnit and Frontend lint and tests to the required status checks of the Main ruleset.
  • Frontend production build

    • Add pnpm prod to the existing frontend job in tests.yml.
    • Use node-version-file: .nvmrc instead of the hard-coded 24 (engineStrict is enabled in pnpm-workspace.yaml), matching pages.yml.
    • Purpose: catch changes that pass lint but fail the production build that runs inside the release image.
  • Composer validation

    • Run composer validate --strict in the backend job.
    • Purpose: catch composer.json / composer.lock drift, the Composer counterpart of --frozen-lockfile.
  • Dependency Review

    • Use GitHub actions/dependency-review-action.
    • Fail when a PR introduces new high/critical vulnerabilities, in both runtime and development scopes.
    • Covers Composer and pnpm manifests; dependency graph is already enabled.
  • actionlint for all workflows

    • Validate every file under .github/workflows, not only the release workflows.
    • Replace the release-only step in release-check.yml.
  • Hadolint

    • Lint the project Dockerfile.
  • Trivy configuration scan

    • trivy fs --scanners misconfig over the repository: Dockerfile, Helm chart in .helm/, other IaC.
    • Vulnerability scanning is intentionally not part of this required check: it is not diff-based, so a newly published CVE in an existing dependency would block unrelated PRs. New vulnerable dependencies are handled by Dependency Review, existing ones by Dependabot alerts.
    • Secrets are already covered by GitHub secret scanning with push protection.

Required checks must run on every PR (no workflow-level paths filters), otherwise PRs that do not touch the filtered paths stay blocked on a pending check. Use job-level conditions or a lightweight always-passing path when a check is not relevant.

Initially advisory PR checks

  • zizmor

    • Security analysis of GitHub Actions workflows, advisory until the baseline is cleaned up, then required.
    • Known baseline: actions referenced by tag in pages.yml, release.yml, release-check.yml; ghcr.io/wolfi-dev/sdk:latest container; write permissions declared at workflow level in release.yml; expressions interpolated directly into run: scripts.
    • Upload SARIF only on push to main (fork PRs have no security-events: write); on PRs just print findings.
  • Container build and Trivy image scan

    • Build the application image from the PR with the same Dockerfile and build tool (buildah) as release.yml, without pushing.
    • Scan the resulting image with Trivy.
    • Note: the Dockerfile builds on top of the published builder-latest / runtime-latest base images, so this check verifies the application layer only. Changes to build/base/** and build/packages/** (melange/apko) are not covered; a separate path-filtered advisory job can build them if needed.
    • Make it required after runtime/cost is evaluated.
  • PHPUnit coverage + Codecov

    • Enable coverage: pcov in the existing PHPUnit job and produce Clover output (phpunit.xml already defines <source> as app/).
    • Upload to Codecov (CODECOV_TOKEN secret; fork PRs use tokenless upload, which Codecov supports for public repositories).
    • Purpose: enforce patch coverage without requiring high global coverage of the legacy codebase.
  • PHP_CodeSniffer

    • phpcs.xml and squizlabs/php_codesniffer are already in the project but not run in CI.
    • Start advisory (or limited to changed files) until the existing baseline is known.

Scheduled / default-branch checks

  • OpenSSF Scorecard

    • Run on main and on a schedule; not a per-PR check.
  • Scheduled Trivy image scan of published images

    • Scan ghcr.io/cattr-app/server (latest stable) and ghcr.io/cattr-app/server-runtime:runtime-latest on a schedule.
    • Purpose: Wolfi base packages receive CVEs between releases; this signals when a base-image rebuild (IMAGE_BUILD_REVISION / build/base/rebuild) is needed.

CI hardening

  • Pin remaining actions and images

    • Pin third-party actions in pages.yml, release.yml, release-check.yml to full commit SHAs with the version in a comment, as already done in tests.yml.
    • Pin docker://rhysd/actionlint and ghcr.io/wolfi-dev/sdk by digest.
  • Least-privilege permissions in release.yml

    • Move packages: write, id-token: write, attestations: write from workflow level to the jobs that need them; release-metadata needs only contents: read.
  • step-security/harden-runner

    • Add to release jobs in audit mode first, then restrict egress where practical.
    • Check support for the runtime-images job, which runs inside a container:.
  • Dependabot for GitHub Actions

    • Add github-actions to .github/dependabot.yml so pinned SHAs keep receiving updates.
    • Review the existing docker entry: the Dockerfile uses ARG-parameterised FROM images, which Dependabot cannot update.

Out of scope

  • CodeQL — already enabled via default setup; switch to an advanced workflow only if custom queries are needed.
  • reviewdog ESLint annotations — actions/setup-node registers ESLint problem matchers, so lint failures in tests.yml are already annotated on the changed lines without granting write permissions; CodeRabbit reviews PRs as well.
  • PHPStan/Larastan — separate issue.

Implementation notes

  • Existing CLA workflow stays unchanged; release.yml changes are limited to pinning, permissions and harden-runner and must not change what is built or published.
  • zizmor, coverage, PHPCS and container image scanning start as advisory until the current baseline is known.
  • Frontend production build, Composer validation, Dependency Review, actionlint, Hadolint and Trivy configuration scan become required checks together with the existing test jobs.

Expected result

After implementation, a pull request should fail early when it:

  • breaks the frontend production build or PHPUnit tests;
  • modifies dependencies inconsistently with pnpm-lock.yaml or composer.lock;
  • introduces a known vulnerable dependency;
  • introduces Dockerfile, Helm or GitHub Actions errors;
  • introduces obvious workflow security problems.

Metadata

Metadata

Assignees

Labels

dependenciesPull requests that update a dependency filejavascriptPull requests that update Javascript codephpPull requests that update Php code

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions