Repository navigation
Add PR quality, security and build checks #339
Description
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/PHPUnitjob (no coverage yet). - Frontend install, lint and JS tests —
tests.yml/Frontend lint and testsjob:pnpm install --frozen-lockfile,pnpm lint,pnpm test. This already catches lockfile drift and lint failures. - CodeQL — default setup is enabled for
actionsandjavascript-typescript. - Secret scanning with push protection, Dependabot alerts and security updates — enabled in repository settings.
- actionlint — runs only for
release.ymlandrelease-check.yml(release-check.yml). - Dependabot version updates — npm (pnpm lockfile), Composer, Docker.
tests.ymlactions 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
PHPUnitandFrontend lint and teststo the required status checks of theMainruleset.
- Add
-
Frontend production build
- Add
pnpm prodto the existing frontend job intests.yml. - Use
node-version-file: .nvmrcinstead of the hard-coded24(engineStrictis enabled inpnpm-workspace.yaml), matchingpages.yml. - Purpose: catch changes that pass lint but fail the production build that runs inside the release image.
- Add
-
Composer validation
- Run
composer validate --strictin the backend job. - Purpose: catch
composer.json/composer.lockdrift, the Composer counterpart of--frozen-lockfile.
- Run
-
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.
- Use GitHub
-
actionlint for all workflows
- Validate every file under
.github/workflows, not only the release workflows. - Replace the release-only step in
release-check.yml.
- Validate every file under
-
Hadolint
- Lint the project
Dockerfile.
- Lint the project
-
Trivy configuration scan
trivy fs --scanners misconfigover 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:latestcontainer; write permissions declared at workflow level inrelease.yml; expressions interpolated directly intorun:scripts. - Upload SARIF only on
pushtomain(fork PRs have nosecurity-events: write); on PRs just print findings.
-
Container build and Trivy image scan
- Build the application image from the PR with the same
Dockerfileand build tool (buildah) asrelease.yml, without pushing. - Scan the resulting image with Trivy.
- Note: the Dockerfile builds on top of the published
builder-latest/runtime-latestbase images, so this check verifies the application layer only. Changes tobuild/base/**andbuild/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.
- Build the application image from the PR with the same
-
PHPUnit coverage + Codecov
- Enable
coverage: pcovin the existingPHPUnitjob and produce Clover output (phpunit.xmlalready defines<source>asapp/). - Upload to Codecov (
CODECOV_TOKENsecret; fork PRs use tokenless upload, which Codecov supports for public repositories). - Purpose: enforce patch coverage without requiring high global coverage of the legacy codebase.
- Enable
-
PHP_CodeSniffer
phpcs.xmlandsquizlabs/php_codesnifferare 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
mainand on a schedule; not a per-PR check.
- Run on
-
Scheduled Trivy image scan of published images
- Scan
ghcr.io/cattr-app/server(latest stable) andghcr.io/cattr-app/server-runtime:runtime-lateston 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.
- Scan
CI hardening
-
Pin remaining actions and images
- Pin third-party actions in
pages.yml,release.yml,release-check.ymlto full commit SHAs with the version in a comment, as already done intests.yml. - Pin
docker://rhysd/actionlintandghcr.io/wolfi-dev/sdkby digest.
- Pin third-party actions in
-
Least-privilege permissions in
release.yml- Move
packages: write,id-token: write,attestations: writefrom workflow level to the jobs that need them;release-metadataneeds onlycontents: read.
- Move
-
step-security/harden-runner
- Add to release jobs in audit mode first, then restrict egress where practical.
- Check support for the
runtime-imagesjob, which runs inside acontainer:.
-
Dependabot for GitHub Actions
- Add
github-actionsto.github/dependabot.ymlso pinned SHAs keep receiving updates. - Review the existing
dockerentry: theDockerfileusesARG-parameterisedFROMimages, which Dependabot cannot update.
- Add
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-noderegisters ESLint problem matchers, so lint failures intests.ymlare 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.ymlchanges 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.yamlorcomposer.lock; - introduces a known vulnerable dependency;
- introduces Dockerfile, Helm or GitHub Actions errors;
- introduces obvious workflow security problems.
Activity
ci: enforce image digests and document the CLA App token privileges