Skip to content

ci(notify): require owner-gated publication#111

Merged
psimaker merged 1 commit into
mainfrom
agent/publish-safety-owner-release-gate
Jul 13, 2026
Merged

ci(notify): require owner-gated publication#111
psimaker merged 1 commit into
mainfrom
agent/publish-safety-owner-release-gate

Conversation

@psimaker

@psimaker psimaker commented Jul 13, 2026

Copy link
Copy Markdown
Owner

What and why

A normal main push touching notify/** previously received package, OIDC, and attestation write permissions, logged into GHCR, and pushed the mutable latest image. This change makes pull requests and normal main pushes validation-only and reserves every image/binary publication for a separate explicit repository-owner action.

Normal-event safety

  • Pull requests and normal main pushes run the policy guard, Notify tests, a local linux/amd64 image build with push: false, and a local Trivy scan.
  • Default workflow permissions are exactly contents: read.
  • Normal events contain no registry login, image push, binary upload, release command, or write-scoped token.
  • Every checkout in the publication workflow uses persist-credentials: false.
  • All external actions in the publication workflow are pinned to immutable commit SHAs.

Owner publication gate

The two privileged jobs can run only after a manual workflow_dispatch where all of the following hold:

  • both the original actor and triggering actor equal the repository owner;
  • the selected workflow ref is an existing strict notify-vX.Y.Z tag;
  • the release_tag input exactly matches that ref;
  • the exact confirmation is PUBLISH_NOTIFY_RELEASE;
  • the tag resolves to the workflow SHA; and
  • that SHA is an ancestor of origin/main.

The gate itself is read-only. Image publication receives only package/provenance scopes; binary publication receives only contents: write. The workflow never creates a tag. No manual dispatch was triggered by this change.

Automated policy

.github/scripts/notify-publish-safety.rb parses both workflows and enforces trigger, permission, checkout, immutable-action, dependency, login, build-push, release-command, and main-ancestry invariants. Its model rejects eight negative event/ref/tag/input/actor cases and permits only the exact owner/tag/confirmation case. The merge-blocking Notify Tests job executes this policy on every pull request.

Local validation

  • ruby -c .github/scripts/notify-publish-safety.rb — passed
  • ruby .github/scripts/notify-publish-safety.rb — 8 negative cases blocked; 1 exact positive model case passed
  • actionlint v1.7.12 with ShellCheck v0.10.0 — passed for docker.yml and ci.yml
  • zizmor v1.26.1 --pedantic .github/workflows/docker.yml — no findings
  • cd notify && go test ./... -count=1 — passed
  • cd notify && go vet ./... — passed
  • gofmt -l notify — empty
  • cd go && make patch && go test -tags noassets ./bridge -count=1 — passed in 57.891 seconds
  • gofmt -l go/bridge — empty
  • notify/tests/m4-docker/run.sh — passed
  • notify/scripts/tests/install-dry-run-test.sh — passed without privileged or mutating commands
  • design-token lint — passed
  • strings parity — 759 identical keys across en/de/es/zh-Hans
  • local no-push Notify image build — passed
  • Trivy v0.70.0 on the local image — 0 HIGH/CRITICAL findings
  • temporary cross-compilation for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, and windows/amd64 — passed
  • make xcframework — passed
  • full iOS simulator xcodebuild build test with code signing disabled — passed
  • Gitleaks v8.30.1 over .github — no leaks
  • git diff --check origin/main...HEAD — passed

Compatibility, privacy, and rollback

No app, helper, bridge, Relay, Trigger v1, APNs, StoreKit, wire, persisted-state, namespace, credential, pairing, cleanup, backup, versioning, conflict, or tombstone behavior changes. Existing users receive no runtime or migration change.

A plain repository revert would restore unsafe automatic main-push publication. Safe rollback is to disable the Docker workflow through an explicit owner action or land a forward safety replacement before reverting this commit. No published artifact exists from this milestone.

Release gate

VaultSync 2.0 remains NO-GO. The strongest implemented runtime proof remains a fresh successful local file ItemFinished apply after cursor, RFC3339-nanosecond time, and stable engine-generation baselines.

No image or binary was pushed. No tag, release, deployment, rollout, archive, signing, notarization, TestFlight/App Store Connect mutation, submission, or publication was performed.

Make pull requests and main pushes run Notify tests and a local image build without registry authentication or publication. Move image and binary publication behind a manual repository-owner dispatch bound to a pre-existing notify-vX.Y.Z tag, matching input, exact confirmation, and origin/main ancestry.

Keep default workflow permissions read-only, grant narrowly scoped write permissions only to the gated jobs, disable checkout credential persistence, and pin every action in the publication workflow. Add a merge-blocking policy test with negative event, ref, actor, tag, and confirmation cases.

A plain revert would restore automatic main-push publication; disable the workflow or land a forward safety fix before any rollback. No image or binary was pushed, and no tag, release, deployment, archive, signing, or store action was performed.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Ruby policy checker and CI invocation, restructures Docker workflow validation, and introduces owner-gated manual publication of multi-architecture images and cross-compiled release binaries.

Changes

Publication safety workflow

Layer / File(s) Summary
Policy model and CI enforcement
.github/scripts/notify-publish-safety.rb, .github/workflows/ci.yml
Defines publication eligibility and validates workflow permissions, triggers, immutable actions, privileged operations, build modes, gate dependencies, and CI execution.
Pull-request validation build
.github/workflows/docker.yml
Configures pull-request policy and test prerequisites, a no-push Docker build, and Trivy scanning.
Manual release gate
.github/workflows/docker.yml
Requires manual release inputs and validates ownership, confirmation, tag format, commit identity, and origin/main ancestry.
Docker and binary publication
.github/workflows/docker.yml
Publishes multi-architecture images with attestations and digest scanning, then builds binaries and creates a verified release.

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

Suggested reviewers: dependabot[bot]

Poem

Tags wait beneath a careful gate,
Owners nod before they propagate.
Images rise, binaries gleam,
Checks guard every release stream.
Safe hands steer the publish dream.

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
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.
No Private Note Leakage ✅ Passed Changes only add workflow safety checks and gated publish jobs; no code logs or transmits note contents, sensitive paths, keys, or receipt data.
Bounded Ios Background Work ✅ Passed PR changes only .github workflows/scripts; no Swift/iOS files changed, so the bounded-background-work check is not applicable.
Bridge Contract Compatibility ✅ Passed Only workflow/policy files changed; no Go bridge or Swift bridge-service code, types, or tests were modified.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the CI change to require owner-gated publication.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/publish-safety-owner-release-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@psimaker
psimaker marked this pull request as ready for review July 13, 2026 16:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
.github/workflows/docker.yml (1)

213-219: 🔒 Security & Privacy | 🔵 Trivial

Publish-then-scan: the image (and latest) is public before Trivy can gate it.

push: true on the preceding build ships the multi-arch image and moves latest before this scan runs, so a HIGH/CRITICAL finding fails the job only after the artifact is already exposed on GHCR. This is largely inherent (multi-arch images can't be loaded for a pre-push local scan the way the validation job does), and the PR/main validation build already scans amd64, but arm64 layers and newly-disclosed CVEs are only caught after publication. If tightening this matters, consider building without push, scanning per-platform, then pushing on success — otherwise treat latest reassignment as provisional until this step passes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker.yml around lines 213 - 219, Adjust the publish
workflow so the multi-architecture image is not pushed or assigned to latest
until Trivy scanning completes successfully. Update the preceding build/publish
flow around the image digest and the “Scan published image” step to build
without publication, scan each platform’s image (including arm64), and push the
final manifest only after the scan passes; preserve the existing HIGH/CRITICAL
and ignore-unfixed gating.
.github/scripts/notify-publish-safety.rb (1)

43-47: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

flattened_step_text skips env, leaving a hole in the privileged-op scan.

The forbidden-operation guard at Lines 128-136 (and the secrets.GITHUB_TOKEN check in particular) only sees uses/run/with values. A normal validation job could still expose the token via a step env: such as TOKEN: ${{ secrets.GITHUB_TOKEN }} and pass the policy unchallenged. Including step env values closes that gap without affecting the login/release/gate detections (their env blocks don't contain those markers).

🔒️ Include step env in the scan
 def flattened_step_text(job)
   steps(job).flat_map do |step|
-    [step["uses"], step["run"], step.fetch("with", {}).values]
+    [step["uses"], step["run"], step.fetch("with", {}).values, step.fetch("env", {}).values]
   end.flatten.compact.join("\n")
 end

As per path instructions, review CI for "secret scoping ... and unnecessary privilege escalation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/notify-publish-safety.rb around lines 43 - 47, Update
flattened_step_text to include values from each step’s env mapping alongside
uses, run, and with values. Preserve the existing flattening, compaction, and
newline-joining behavior so the privileged-operation and secrets.GITHUB_TOKEN
scans inspect environment values as well.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/scripts/notify-publish-safety.rb:
- Around line 43-47: Update flattened_step_text to include values from each
step’s env mapping alongside uses, run, and with values. Preserve the existing
flattening, compaction, and newline-joining behavior so the privileged-operation
and secrets.GITHUB_TOKEN scans inspect environment values as well.

In @.github/workflows/docker.yml:
- Around line 213-219: Adjust the publish workflow so the multi-architecture
image is not pushed or assigned to latest until Trivy scanning completes
successfully. Update the preceding build/publish flow around the image digest
and the “Scan published image” step to build without publication, scan each
platform’s image (including arm64), and push the final manifest only after the
scan passes; preserve the existing HIGH/CRITICAL and ignore-unfixed gating.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a4433d09-e743-4242-a815-cd46f013a581

📥 Commits

Reviewing files that changed from the base of the PR and between 4525c91 and 82d630e.

📒 Files selected for processing (3)
  • .github/scripts/notify-publish-safety.rb
  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.yml

⚙️ CodeRabbit configuration file

.github/workflows/**/*.yml: Review CI for correct Go test tags, Xcode/iOS simulator assumptions, secret scoping,
dependency integrity, permissions, Docker publishing safety, and unnecessary privilege escalation.

Files:

  • .github/workflows/ci.yml
  • .github/workflows/docker.yml
🔇 Additional comments (4)
.github/scripts/notify-publish-safety.rb (1)

29-57: LGTM!

Also applies to: 59-124, 138-197

.github/workflows/ci.yml (1)

83-85: LGTM!

.github/workflows/docker.yml (2)

4-38: LGTM!

Also applies to: 40-104, 106-151, 154-212, 220-277


152-153: 🩺 Stability & Availability

No issue: origin/main is available in this checkout. actions/checkout with fetch-depth: 0 fetches the remote-tracking branches and tags here, so the ancestry gate can read refs/remotes/origin/main as written.

			> Likely an incorrect or invalid review comment.

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