Skip to content

Harden CI and release supply chain - #24

Open
ndbroadbent wants to merge 4 commits into
mainfrom
nathan/release-hardening
Open

ndbroadbent wants to merge 4 commits into
mainfrom
nathan/release-hardening

Conversation

@ndbroadbent

@ndbroadbent ndbroadbent commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

This PR fixes the code side of RG-05 and related supply-chain findings (SUPPLY-1/4/6/7/9/10) from the 2026-10-09 audit. The GitHub settings part is already applied: rulesets on main and v* tags, secret scanning and push protection, Dependabot, and a read-only default GITHUB_TOKEN.

CI

  • Pinned actions: every third-party action is pinned to a commit SHA, with the version in a comment.
  • No more curl | sh installers:
    • Task, the Convox CLI and TruffleHog are installed from pinned releases with SHA-256 verification.
    • The golangci-lint install.sh, which was fetched from master, is removed; the action installs the pinned version.
    • govulncheck is pinned.
  • Token permissions: ci.yml and e2e.yml run with contents: read. The docs workflow grants Pages and OIDC permissions only to its deploy job.
  • New daily Security Scan workflow: govulncheck, bun audit (high and above), and Trivy on the published image. CI was red for a month without anyone noticing.
  • Dependabot: covers gomod, bun (web, docs, mock-oauth), GitHub Actions and Dockerfiles.

Release

  • Tag-only releases: they run only from v* tags, which only admins can create. workflow_dispatch is removed; it built any branch and overwrote :latest and the latest release's assets.
  • Workflow hardening: least-privilege permissions per job, an anchored tag regex, and values passed through env: instead of ${{ }} interpolation.
  • Image tags and attestations:
    • Images are pushed as :vX.Y.Z, :<short-sha> and :latest.
    • SLSA build provenance attestations cover the image and the CLI binaries (gh attestation verify).
    • No setup-go cache in release jobs.
  • Pinned deploys: convox.yml deploys :e327add instead of :latest. That's v0.1.1, and I checked it has the same digest as the current :latest. bump-version.sh now sets :vX.Y.Z on each bump.
  • Version reporting:
    • The CLI now reports its real version; the ldflags targeted main.Version instead of main.version.
    • The gateway image reports its real commit; the Dockerfile read an unset COMMIT_HASH arg.

Docker

  • Base images: pinned by digest. The runtime image moves from alpine:latest to alpine:3.24.2.
  • Non-root: the gateway runs as UID 10001. Files are root-owned and read-only, and it writes only to /tmp.
  • No curl: removed from the runtime image. The compose healthcheck uses busybox wget.

Verified locally

  • task go:test, lint, shellcheck and actionlint all pass.
  • Web E2E passes 56/56 and CLI E2E passes, both against the production Dockerfile image. I confirmed the container runs as uid=10001(gateway) and has no curl.
  • The workflow changes themselves can only be fully verified on GitHub. This PR's own CI exercises ci.yml/e2e.yml. release.yml will only run on the next v* tag.

Things to check before merging

  • After merging, convox deploy keeps running v0.1.1, now pinned instead of :latest. Future deploys use :vX.Y.Z from bump-version.sh.
  • If you use convox exec … curl to debug the gateway, curl is no longer in the image. busybox wget still is.

Summary by CodeRabbit

  • Release Improvements

    • Releases are now published from version tags after CI and end-to-end checks pass. Each release includes a versioned CLI archive with a SHA-256 checksum, a Docker image, and build provenance attestations.
    • Production deployment configuration now references a specific image version instead of the mutable latest tag.
  • Documentation

    • Updated release and deployment guides explain versioned image tags and how to verify image and CLI provenance.

…okens

- Pin every third-party action to a full commit SHA (newest release
  within the current major), with the version in a comment.
- Install Task, the Convox CLI and TruffleHog from pinned releases with
  SHA-256 verification instead of piping install scripts (including
  golangci-lint's install.sh from master) into sh. The golangci-lint
  action already installs the pinned version and verifies the config
  schema, so the separate install and config-verify steps are gone.
  govulncheck is pinned to v1.8.0.
- ci.yml and e2e.yml run with contents: read. The docs workflow gives
  pages/id-token write only to the deploy job, pins Bun and installs
  with --frozen-lockfile.
- New daily Security Scan workflow: govulncheck, bun audit (high+) for
  web, and a Trivy scan of the published image, so new advisories
  surface without a push (CI was red for a month unnoticed).
- Dependabot for gomod, bun (web, docs, mock-oauth), GitHub Actions and
  Dockerfiles, weekly with grouped minor/patch updates.
…curl

- Pin oven/bun, golang and alpine base images by digest (runtime moves
  from alpine:latest to alpine:3.24.2) in all Dockerfiles, including
  mock-oauth's floating oven/bun:1-alpine.
- The production image runs as UID/GID 10001. Binaries, web assets and
  start-gateway.sh stay root-owned and read-only; the gateway only
  writes temp files under /tmp.
- curl is no longer installed in the gateway images. The compose
  healthcheck uses busybox wget, which is already in alpine.
- Fix the commit hash in the gateway binary: the build stage read an
  unset COMMIT_HASH arg, so every image reported CommitHash=unknown. It
  now uses the required COMMIT_SHA build arg that CI already passes.
- The Release workflow runs only for v* tags. workflow_dispatch is gone:
  it built whatever branch it was dispatched from, then pushed :latest
  and overwrote the newest release's assets. v* tags are admin-only via
  a repository ruleset.
- Per-job least-privilege permissions; the tag regex is anchored; tag
  values reach shell steps through env: instead of ${{ }} interpolation.
- Images are pushed as :vX.Y.Z, :<short-sha> and :latest, with SLSA
  build provenance attestations for the image and the CLI binaries
  (verify with `gh attestation verify`). setup-go caching is off in
  release jobs so a poisoned main-branch cache can't reach a release.
- convox.yml deploys an immutable tag instead of :latest. It's pinned
  to :e327add (v0.1.1, same digest as the current :latest), and
  scripts/bump-version.sh now sets it to :vX.Y.Z on each bump.
- The CLI reports its real version: ldflags set main.version /
  main.buildTime, not the non-existent main.Version / main.BuildTime.
- Go tooling installs are pinned rather than @latest.
- Docs (CLAUDE.md, README, deployment docs) describe the new process.
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

  • Run on-demand review

This review includes 1 billable file and costs up to $0.25.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 29 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available. Your 51 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: c9d00bd8-be84-46c9-92eb-314602abd52b
📥 Commits

Reviewing files that changed from the base of the PR and between 19a114e and b9ad537.

📒 Files selected for processing (1)
  • taskfiles/Taskfile.go.yml

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 1ba00de9-5601-458a-a88e-ef48caa67456
📥 Commits

Reviewing files that changed from the base of the PR and between 3871209 and 19a114e.

📒 Files selected for processing (22)
  • .github/actions/install-convox/action.yml
  • .github/actions/install-task/action.yml
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/docs.yml
  • .github/workflows/e2e.yml
  • .github/workflows/release.yml
  • .github/workflows/security-scan.yml
  • CLAUDE.md
  • Dockerfile
  • Dockerfile.gateway-dev
  • Dockerfile.mock-convox
  • README.md
  • convox.yml
  • docker-compose.yml
  • docs/legacy/DEPLOY.md
  • docs/src/content/docs/deployment/docker.mdx
  • mock-oauth/Dockerfile
  • scripts/build-and-push.sh
  • scripts/bump-version.sh
  • scripts/install.sh
  • taskfiles/Taskfile.go.yml
💤 Files with no reviewable changes (1)
  • scripts/build-and-push.sh

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.


Walkthrough

The changes update CI and security workflows, pin build tools and container images, and replace manual release steps with a tag-verified workflow that publishes versioned Docker and CLI artifacts with provenance attestations. Release and deployment documentation now describes the versioned process.

Changes

Versioned release pipeline

Layer / File(s) Summary
CI tooling and security checks
.github/actions/*, .github/workflows/ci.yml, .github/workflows/e2e.yml, .github/workflows/docs.yml, .github/workflows/security-scan.yml, .github/dependabot.yml
Adds checksum-verified Task and Convox installer actions. CI and E2E use those actions and pin workflow action revisions. The workflows also adjust permissions and tool installation. A scheduled and manually triggered workflow scans Go and web dependencies and the published gateway image. Dependabot adds weekly update configuration.
Versioned build inputs and images
Dockerfile*, mock-oauth/Dockerfile, docker-compose.yml, convox.yml, scripts/bump-version.sh, scripts/install.sh, scripts/build-and-push.sh, taskfiles/Taskfile.go.yml
Pins container base images and changes the gateway runtime to a non-root user. CLI builds read the package version and use updated linker symbols. The version-bump script updates the Convox image tag. The previous image build-and-push script is removed.
Verified tag release workflow
.github/workflows/release.yml
The workflow now runs on version-tag pushes, validates the tag against the package version, and waits for CI and E2E. It builds and attests the Docker image and CLI archive, verifies the archive checksum, and publishes the release.
Release and deployment documentation
README.md, CLAUDE.md, docs/legacy/DEPLOY.md, docs/src/content/docs/deployment/docker.mdx
Documents version-tagged releases and deployments, the image and CLI artifacts, and commands to verify their attestations.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Other

Sequence Diagram(s)

sequenceDiagram
  participant GitHub
  participant verify_tag
  participant CI_and_E2E
  participant docker_build
  participant release_build
  participant release_create
  GitHub->>verify_tag: Push v* tag
  verify_tag->>CI_and_E2E: Wait for checks on tagged commit
  verify_tag->>docker_build: Provide verified version and short SHA
  verify_tag->>release_build: Provide verified version and tag
  docker_build->>release_create: Provide image digest and attestation
  release_build->>release_create: Provide CLI archive and checksum
  release_create->>release_create: Verify checksum and publish release
Loading

Merge Risk: ⚪ Minimal · up to 19a11

This change pins CI actions and Docker images, runs the gateway as a non-root user, and adds a tag-verified release workflow. No concrete merge-blocking issue was found. The release workflow will first run on the next v* tag.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main changes: hardening CI workflows and the release supply chain.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (19 skipped: 1…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit checks the tag at dawn
Then watches checks complete and pass
A versioned image hops along
With signed proofs tucked beneath the grass
The CLI rests in checksum care
And I leave carrot crumbs to share

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 3871209...b9ad537 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Oct 9, 2026 5:25a.m. Review ↗
Go Oct 9, 2026 5:25a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

TruffleHog runs its auto-updater by default. In CI it tried to replace the
checksum-verified binary installed root-owned in /usr/local/bin and failed
("cannot move binary back"), failing the secret scan. A pinned binary
must not update itself anyway, so pass --no-update.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant