The compiled macOS omp binaries shipped on GitHub Releases are signed with a
Developer ID Application certificate and notarized by Apple. This makes
them Gatekeeper-acceptable and is the prerequisite for an official Homebrew
submission (see #776).
Signing happens in CI, in the release_binary jobβs darwin matrix legs
(.github/workflows/ci.yml), via scripts/ci-macos-sign.sh. It auto-skips
until the APPLE_* repository secrets below are configured, so releases keep
working (ad-hoc signed, as before) in the meantime.
How it works
ci:release:build-binariesbuilds and ad-hoc signs the binary (so it can run on the build runner).scripts/ci-macos-sign.shthen:- imports the Developer ID cert into a throwaway keychain;
- re-signs with
--options runtime --timestamp(hardened runtime + secure timestamp) and--entitlements scripts/macos-entitlements.plist; - runs
--versionand--smoke-testunder the new signature to fail fast; - notarizes the binary via
notarytool submit --wait.
release_github_verifyre-downloads the published arm64 asset and asserts it is not ad-hoc, passescodesign --verify --strict, and boots cleanly.
Why the entitlements are mandatory
The binary is a Bun single-file executable, so the hardened runtime needs:
| Entitlement | Reason |
|---|---|
com.apple.security.cs.allow-jit | JavaScriptCore JITs at runtime. |
com.apple.security.cs.allow-unsigned-executable-memory | JSC executable memory pages. |
com.apple.security.cs.disable-library-validation | omp extracts its native addon (pi_natives.<triple>.node) and other optional dylibs to a runtime cache and dlopen()s them. They do not share the main binaryβs Team ID, so without this the hardened runtime aborts with βmapping process and mapped file have different Team IDsβ β breaking effectively every command. |
Without disable-library-validation, a signed+notarized binary signs and
notarizes fine but fails at first real use. scripts/ci-macos-sign.sh runs
--smoke-test after signing specifically to catch this before notarizing.
Stapling limitation (important)
A bare Mach-O executable cannot be stapled (stapler only supports
.app/.pkg/.dmg). The binary is genuinely notarized β notarytool returns
Accepted and the ticket exists on Appleβs servers keyed to its cdhash β but
because there is no stapled ticket, a direct spctl -a -t exec assessment
reports rejected / source=Unnotarized Developer ID. This is expected and is
not a signing or credential failure.
What this means in practice:
curl https://omp.sh/install | shβcurlsets no quarantine bit, so Gatekeeper is never consulted; the binary just runs. β- Homebrew formula installs β Homebrew does not quarantine formula files, so Gatekeeper is never consulted. β
- Anything that quarantines the binary (a browser download, or a Homebrew
cask) and is assessed offline will be blocked, because there is no stapled
ticket. For that route, wrap the binary in a stapleable, notarized
.pkgor.dmg(xcrun stapler stapleworks on those). That is a follow-up and is not required for thecurl/formula paths.
Required GitHub secrets
Add these under Settings β Secrets and variables β Actions (repo secrets). All five secrets (cert, password, and API key trio) must be present for signing to engage.
| Secret | What it is |
|---|---|
APPLE_CERTIFICATE_P12 | base64 of the exported Developer ID Application .p12 (cert + private key). |
APPLE_CERTIFICATE_PASSWORD | password you set when exporting the .p12. |
APPLE_API_KEY_ID | App Store Connect API Key ID. |
APPLE_API_ISSUER_ID | App Store Connect API Issuer ID (UUID). |
APPLE_API_KEY | base64 of the App Store Connect .p8 private key. |
Producing the credential files
Drop these into a working directory (default ~/omp-signing):
| File | How |
|---|---|
*.p12 | Keychain Access β right-click your Developer ID Application: β¦ identity (the entry that expands to a cert with a private key) β Exportβ¦ β save as .p12 and set a password. |
p12-password.txt | the password you just set on the .p12. |
AuthKey_<KEYID>.p8 | App Store Connect β Users and Access β Integrations β App Store Connect API β create a key (Account Holder role also allows API cert creation; Developer is enough for notarization) β download once (non-recoverable). |
issuer-id.txt | the Issuer ID (UUID) shown above the keys table. |
key-id.txt | optional β the Key ID; otherwise read from the .p8 filename. |
The App Store Connect API key is the one credential that cannot be minted
from a CLI β it is the bootstrap credential for the API itself, and the .p8
downloads exactly once. Everything else is local.
Uploading (no value leaves disk)
scripts/ci-macos-upload-secrets.sh validates the files (opens the .p12 with
your password, sanity-checks the .p8) and pipes each value to gh secret set
over stdin β no secret is ever printed to the terminal, argv, or shell history:
scripts/ci-macos-upload-secrets.sh ~/omp-signing --dry-run # validate first
scripts/ci-macos-upload-secrets.sh ~/omp-signing # upload all five
gh secret list --repo can1357/oh-my-pi # confirmRe-run it whenever the certificate is renewed.
Finding your signing identity / Team ID (sanity check)
security find-identity -v -p codesigning
# e.g. "Developer ID Application: Your Name (TEAMID1234)"The script selects the first Developer ID Application identity automatically;
you do not need to store the identity string or Team ID as a secret.
Local dry run
You can exercise the full sign+notarize path locally (real cert + API key) by exporting the five env vars and running:
RELEASE_TARGETS=darwin-arm64 bun run ci:release:build-binaries
APPLE_CERTIFICATE_P12=β¦ APPLE_CERTIFICATE_PASSWORD=β¦ \
APPLE_API_KEY_ID=β¦ APPLE_API_ISSUER_ID=β¦ APPLE_API_KEY=β¦ \
bash scripts/ci-macos-sign.sh packages/coding-agent/binaries/omp-darwin-arm64