Skip to content

feat(release): notarize the macOS binary - #1378

Merged
jdx merged 1 commit into
mainfrom
feat/notarize-macos
Sep 5, 2026
Merged

feat(release): notarize the macOS binary#1378
jdx merged 1 commit into
mainfrom
feat/notarize-macos

Conversation

@jdx

@jdx jdx commented Sep 5, 2026

Copy link
Copy Markdown
Owner

usage is the last jdx.dev CLI that signs its macOS binary without notarizing
it. mise, hk, fnox, pitchfork, and packslip all submit to notarytool; this
brings usage in line.

A signature settles who built the binary. Notarization is what Gatekeeper
demands once a download carries the quarantine bit — whenever someone fetches
an archive through a browser rather than with curl. Without a ticket the
binary sits behind the "cannot be verified" dialog that only a deliberate
override gets past.

Two changes:

  • codesign_options: runtime on the existing upload-rust-binary-action
    step. Apple rejects a submission signed without the hardened runtime, and
    that input is the only part of the signature the action exposes. The secure
    timestamp Apple also requires is codesign's own default for a Developer ID
    identity, so it needs no flag.
  • A notarize step that submits the built universal-apple-darwin binary and
    requires an Accepted verdict. --wait is not a gate on its own — it can
    return zero on an Invalid submission — so the reported status is what
    decides, and the notary log is dumped before failing.

Nothing is stapled. The ticket is keyed to the binary's cdhash and lives on
Apple's side, so this rewrites neither the archive nor the uploaded asset;
stapler only writes into bundles, disk images, and installer packages, and
this is a bare Mach-O in an archive. Gatekeeper resolves the ticket online.

Secrets

This repository has APPLE_DEVELOPER_ID_APPLICATION_CERTS_P12 and its
password, but none of the App Store Connect credentials the other repos got on
2026-08-26. Three are needed, and they are the same values already in
jdx/mise, jdx/hk, jdx/fnox, and jdx/pitchfork:

  • APPLE_API_KEY_P8 — the base64-encoded .p8
  • APPLE_API_KEY_ID
  • APPLE_API_ISSUER_ID

The step follows hk's behaviour when they are missing: it warns that the
release ships signed but un-notarized and carries on, rather than failing the
build. So this is safe to merge before the secrets are added — releases keep
working, and the warning is visible in the job log until they land.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: 2.1.176.

🤖 Generated with Claude Code


Note

Low Risk
Changes are confined to the macOS CI release workflow; missing API secrets only warn, though configured credentials make notarization failure block the macOS publish job.

Overview
Adds Apple notarization to the publish-cli macOS release path so browser downloads pass Gatekeeper, matching the other jdx.dev CLIs.

The existing upload-rust-binary-action step now sets codesign_options: runtime (required for notarization). A new Notarize macOS binary job runs only on macos-latest: it stages the built usage binary, zips it for notarytool, checks for hardened runtime and secure timestamp (workflow warnings only), submits with App Store Connect API secrets, and fails the job unless Apple returns Accepted (with notary logs on failure). API key material is decoded in a temp directory and removed on exit.

If APPLE_API_KEY_* secrets are missing, the step emits a workflow warning and skips notarization so signed-but-un-notarized releases can still ship until credentials are added. No stapling — Gatekeeper is expected to validate the online ticket for the shipped Mach-O.

Reviewed by Cursor Bugbot for commit 4829238. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features
    • macOS command-line binaries are now signed with enhanced security protections.
    • Releases can now be notarized by Apple, helping macOS recognize the binaries as trusted software and reducing security warnings during installation or execution.
    • Notarization is applied when the required release credentials are available; otherwise, the signed binary remains available without notarization.

usage was the last jdx.dev CLI signing its macOS binary without
notarizing it. A signature settles who built the binary; notarization is
what Gatekeeper demands once a download carries the quarantine bit, which
is to say whenever someone fetches an archive in a browser rather than
with curl. Without a ticket it sits behind the "cannot be verified"
dialog that only a deliberate override gets past.

`codesign_options: runtime` is the half the action can supply — Apple
rejects a submission without the hardened runtime. The secure timestamp
it also requires is codesign's own default for a Developer ID identity.

Nothing is stapled: the ticket is keyed to the binary's cdhash and lives
on Apple's side, so this rewrites neither the archive nor the uploaded
asset. `stapler` writes only into bundles, disk images, and installer
packages, and this is a bare Mach-O in an archive; Gatekeeper resolves
the ticket online.

The step matches hk's, including its behaviour when App Store Connect
credentials are absent: warn that the release ships un-notarized and
carry on, rather than failing the build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 01M1QDXPJ6F41KCH69ZNHZBAMW
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Team

Run ID: 82000511-41e2-4597-8521-782ca720dc7d

📥 Commits

Reviewing files that changed from the base of the PR and between f03b70f and 4829238.

📒 Files selected for processing (1)
  • .github/workflows/publish-cli.yml

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The macOS release workflow now enables hardened-runtime signing and adds optional Apple notarization. It stages and inspects binaries, submits them with xcrun notarytool, and fails with diagnostic logs when Apple rejects the submission.

Changes

macOS notarization

Layer / File(s) Summary
Hardened runtime signing
.github/workflows/publish-cli.yml
The upload step enables codesign_options: runtime. Comments describe notarization for bare Mach-O binaries.
Credential and binary staging
.github/workflows/publish-cli.yml
The macOS-only step validates credentials, decodes the API key, stages binaries in temporary storage, and checks hardened-runtime and secure-timestamp metadata.
Apple submission and result handling
.github/workflows/publish-cli.yml
The workflow archives the staged binaries, submits them with xcrun notarytool, parses the result, and retrieves logs for non-Accepted statuses.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to 48292

A macOS binary may be uploaded before Apple accepts its notarization, potentially leaving a rejected artifact attached to the release. The upload should be delayed until acceptance or failed uploads should be reliably removed before merge.

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions
  participant upload-rust-binary-action
  participant xcrunNotarytool
  participant AppleNotaryService
  GitHubActions->>upload-rust-binary-action: Build and sign macOS binary with hardened runtime
  GitHubActions->>GitHubActions: Decode credentials and stage binary
  GitHubActions->>xcrunNotarytool: Submit zipped binary
  xcrunNotarytool->>AppleNotaryService: Request notarization and wait
  AppleNotaryService-->>xcrunNotarytool: Return status and submission id
  xcrunNotarytool-->>GitHubActions: Return JSON result
  GitHubActions->>xcrunNotarytool: Fetch log if status is not Accepted
Loading

Poem

A rabbit signs the binary bright
With runtime guards for Apple’s sight
It packs the code in a tidy store
Then sends it through the notarization door
Accepted hops back through the run
Rejected logs explain what’s undone

🚥 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 change: adding macOS notarization to the release workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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.

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.

@jdx
jdx merged commit 5301982 into main Sep 5, 2026
12 checks passed
@jdx
jdx deleted the feat/notarize-macos branch September 5, 2026 00:14
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