Skip to content

ci: harden PR workflow with docker build, codeql, security scans, and pr-lint#53

Merged
mattstrayer merged 14 commits into
mainfrom
ci/pr-workflow-hardening
May 10, 2026
Merged

ci: harden PR workflow with docker build, codeql, security scans, and pr-lint#53
mattstrayer merged 14 commits into
mainfrom
ci/pr-workflow-hardening

Conversation

@mattstrayer

Copy link
Copy Markdown
Owner

Summary

Adds Tier 1+2 of the CI/CD hardening plan. Five new/extended workflows plus documentation to enable a defensible OSS PR workflow.

New workflows

  • docker.yml — builds api and web images on every PR (no push), publishes :main and :sha-<short> tags on main push, runs a docker-compose smoke test against the prod profile (Caddy → web/api), and Trivy-scans published images. Image names match deploy.yml (ghcr.io/mattstrayer/pypx/{api,web}); :latest and :vX.Y.Z remain owned by deploy.yml (workflow_dispatch).
  • codeql.yml — weekly + per-PR CodeQL for Go (manual build of both api/ and goopy/) and JavaScript/TypeScript.
  • security.yml — gitleaks (full history), govulncheck (matrix over api/goopy), osv-scanner with SARIF upload to Code scanning, and actions/dependency-review-action (PR-only, fails on high severity, license allow-list).
  • pr-lint.yml — enforces Conventional Commit PR titles via amannn/action-semantic-pull-request. Closes the gap where pre-commit's hook only catches local commits, not squash-merge titles.

CI extension

  • web-build added to ci.yml. Runs pnpm run build to catch SSR-time regressions that lint/unit-tests miss.

Documentation

  • docs/CONTRIBUTING-MAINTAINERS.md — branch protection ruleset checklist, repo settings checklist (Copilot review, Dependabot, secret scanning, push protection, squash-only merges), GHCR package visibility, and a process for adding new required checks over time.
  • CONTRIBUTING.md — documents PR-only workflow, squash-merge requirement, and points to the maintainer doc.

Deferred (not in this PR)

  • web-typecheck job — tracked in Enable nuxi typecheck in CI #52. Adding vue-tsc+typescript triggers a pnpm peer-dep regression that breaks nuxt-site-config for @nuxtjs/sitemap. Combined with a pile of pre-existing TS errors, this needs its own focused PR.
  • Branch protection itself is configured manually in the GitHub UI per the new maintainer doc — applied after these checks have run green on 2–3 unrelated PRs (per the rollout note in the maintainer doc).

Action SHA pinning

All third-party actions pinned by full commit SHA, matching the project's existing scorecard-friendly convention.

Test plan

These checks should appear on this PR and run green before any required-status configuration:

  • CI / API (Go)
  • CI / goopy (Go)
  • CI / Web (Nuxt)
  • CI / Web build
  • Docker / build (api)
  • Docker / build (web)
  • Docker / compose-smoke
  • Security / gitleaks
  • Security / govulncheck (api)
  • Security / govulncheck (goopy)
  • Security / osv-scanner
  • Security / dependency-review
  • CodeQL / Analyze (javascript-typescript)
  • CodeQL / Analyze (go)
  • PR Lint / conventional-title

After all green, follow the maintainer-doc checklist to enable Copilot code review, branch protection, secret scanning, and the rest. Then mark the GHCR packages public after the first main push.

Likely friction (anticipated)

  • dependency-review may flag a license outside the allow-list — extend list or replace dep.
  • osv-scanner may surface pre-existing CVEs in transitive deps; doesn't fail the workflow but inspect the Code Scanning tab.
  • compose-smoke could time out on slow runners; bump iterations if so.

- Remove .github/workflows/codeql.yml: repo uses CodeQL default setup
  which already covers actions/go/javascript-typescript/python.
- Switch osv-scanner to the subdirectory action path
  (google/osv-scanner-action/osv-scanner-action) and pin v2.3.5.
  The previous root-path SHA pointed to v1.6.2-beta1 which has no
  top-level runs: section.
- Update maintainer doc required-checks list to match default-setup
  CodeQL check names.
The web Dockerfile expects to be built from the repo root (it COPYs
pnpm-workspace.yaml + pnpm-lock.yaml from /), but the compose file
still pointed at ./web. This made docker compose up --build fail on
the web service. The deploy workflow already does this; align compose
with that.
osv-scanner flagged 16 vulnerabilities in Go stdlib 1.26.2 affecting
both api/go.mod and goopy/go.mod. All fixed in 1.26.3.

Refs: GO-2026-4918, 4971, 4976, 4977, 4980, 4981, 4982, 4986
Aligns with the recent pnpm 11 migration in deploy and the open
Renovate PR #45. Lockfile is v9 which both pnpm 10 and 11 support,
so this is a transparent bump.
The TestPackageHandler_Get_DifferentPackagesIndependent test threshold
was tuned to Go 1.26.2 scheduler timing. Under 1.26.3 on Ubuntu CI,
goroutines occasionally interleave such that 4/5 concurrent requests
share a singleflight call instead of all 5. The test still catches
the no-dedup regression (>4 = bug).
The previous version raced: it relied on goroutines arriving at
singleflight before the first upstream call returned. Under Go 1.26.3
on Ubuntu CI, scheduler timing changed and one goroutine would slip
past, producing 4 upstream calls instead of the expected 1.

Replace the brittle threshold with a deterministic gate: pin every
upstream request open on a release channel, poll until the counters
settle at the expected state (exactly 1 per package), then release.
While the gate is held, any singleflight regression would surface
as upstream count >1 long before the deadline.

Reverts the threshold loosening (>3 -> >4) and asserts == 1 instead.
Verified locally with -race -count=10.
@mattstrayer
mattstrayer merged commit 11c81d7 into main May 10, 2026
20 checks passed
@mattstrayer
mattstrayer deleted the ci/pr-workflow-hardening branch May 10, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant