ci(content): sign the content bump so it stops publishing unverified commits - #2729
Conversation
…commits Every content bump since Feb 2026 has been unsigned. The step built its commit with `POST /git/commits`, added in fab5971 under the title "use GitHub API for signed commits" — but that endpoint does not sign. It only attaches a signature the caller computes and passes in. Nothing caught it. `required_signatures` is active on the org "All branches" ruleset, but CONTENT_BOT_TOKEN belongs to an org admin and admins hold `bypass_mode: always`, so ref creation succeeded quietly for six months. The commits then reach `dev` through the main->dev back-merge, which is where they get noticed (#2724). createCommitOnBranch, the one write path GitHub signs for you, is not an option here: its FileAddition input carries `path` and `contents` only, with no file mode, so it cannot write a 160000 gitlink. A real commit signed on the runner is the only route to a Verified submodule bump. Sign with an SSH signing key (CONTENT_BOT_SIGNING_KEY), keeping the identity GitHub already stamped so the sole change is that the commit is now signed. Then ask GitHub whether the signature verified and fail the step if it did not — a missing key or a rejected signature now stops the publish instead of shipping another unverified commit.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe content update workflow now requires a signing key, creates signed commits locally, pushes them, and verifies their status through GitHub. The previous Git Data API commit and reference flow was removed. ChangesContent update signing
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
Code-analysis diffPainscore total: 7095.78 → 7095.78 (0) |
Review findings on the first commit. The push inherited actions/checkout's persisted GITHUB_TOKEN, not the PAT the old ref-creation call used. Two consequences. A GITHUB_TOKEN push raises no workflow events, so `Tests` would fire for the `pull_request` event alone — content-publish-automerge.yml is written for two `ci-success` runs per commit and merges on all-green-of-N, so at N=1 one concurrency cancellation makes N=0 and the publish stalls with no error. It also dropped the bypass the ref creation had, leaving the pipeline exposed to any rule added to the "All branches" ruleset later. Push over CONTENT_BOT_TOKEN instead. The verify gate also ran after the push, so a rejected signature left an `auto/update-content-*` branch with no PR. The superseded-PR sweep only walks branches that have an open PR, so nothing would ever collect it. Delete the branch on that path. Checked and not changed: the "All branches" ruleset carries only restrict- deletions, restrict-force-push and require-signed-commits, and the bump branch matches none of the PR or status-check rulesets, so a signed push needs no bypass today. The PAT is belt and braces.
Summary
Every
Update content submodule to latest maincommit since Feb 2026 has been unsigned, and they surface as a wall of "Unverified" whenever amain→devback-merge carries them across (most recently #2724).The step built its commit with
POST /git/commits, added in fab5971 under the title "use GitHub API for signed commits". That endpoint does not sign — it only attaches a signature the caller computes and passes in. The title has been wrong for six months.Nothing caught it because
required_signaturesis active on the org All branches ruleset, butCONTENT_BOT_TOKENbelongs to an org admin and admins holdbypass_mode: always. So ref creation succeeded quietly instead of failing.createCommitOnBranch— the one write path GitHub signs for you — is not an option here. ItsFileAdditioninput carriespathandcontentsonly, with no file mode, so it cannot write a160000gitlink. Confirmed against the live schema. A real commit signed on the runner is the only route to a Verified submodule bump.This PR: signs the bump with an SSH signing key (
CONTENT_BOT_SIGNING_KEY), keeping the exact identity GitHub already stamped so the only change is that the commit is now signed. Then it asks GitHub whether the signature actually verified and fails the step if it did not — a missing key or a rejected signature now stops the publish instead of shipping another unverified commit.Task
TASK-21002 — Prevent unverified commits from Hugo's account
Why base
main, notdevrepository_dispatchruns workflows from the default branch, which ismain. The fix has no effect until it is onmain. The regularmain→devback-merge carries it todevafterwards.Prerequisites already in place
CONTENT_BOT_SIGNING_KEYset on peanut-ui (2026-08-18).Hugo0account, fingerprintSHA256:U6N8EsIfBAZKtLGFT9CJkYOCiJwMMFUBdD85PFdaXlY. It is a signing key only — it is not in Authentication keys and grants no repo access.h@hugomontenegro.comconfirmed Primary + Verified on that account, which is what makes GitHub accept the signature.Design notes / accepted trade-offs
CONTENT_BOT_TOKENalready sits in the same store with repo write and can push as him outright — the key adds no meaningful blast radius. A dedicated bot account is the stronger alternative and was deliberately deferred: it costs an org seat and a replacement PAT.workflow_dispatchmakes this workflow reachable from any ref, so anyone with write access can run a modified copy of it with these secrets. That means the signing key can be read, and commits can be signed as Hugo. Flagged rather than fixed, because it is pre-existing and strictly smaller than what is already exposed:CONTENT_BOT_TOKEN, an org-admin PAT, sits in the same secret store behind the same trigger. Worth addressing on its own (bot account, or dropworkflow_dispatch), not by holding up this fix.Risk
Low, and CI-only — no app code, no runtime behavior. Worst case is that the content publish pipeline fails loudly and content stops auto-reaching peanut.me until fixed, which is the intended failure mode and strictly better than the current silent-unsigned one. No cross-repo deploy ordering.
QA
gpgsig -----BEGIN SSH SIGNATURE-----present,git verify-commitreturnsGood "git" signature,%G?=G.bash -n; theBODYheredoc terminator sits at column 0 after YAML dedent.Screenshots: N/A (no visible change — CI workflow only).
Review
/code-review mediumraised four findings; CodeRabbit raised none.GITHUB_TOKEN, not the PAT. AGITHUB_TOKENpush raises no workflow events, soTestswould run forpull_requestonly.content-publish-automerge.ymlis written for twoci-successruns per commit and merges on all-green-of-N, so at N=1 one concurrency cancellation gives N=0 and the publish stalls silently. It also dropped the bypass ref creation had. Now pushes overCONTENT_BOT_TOKEN.auto/update-content-*would never be collected. It is now deleted on that path.workflow_dispatchexposure above. Pre-existing, and smaller than the org-admin PAT already reachable the same way.Summary by CodeRabbit