"Self-signed local identity: commit it. Trusted Apple certificate: never."
The Problem It Solves
An ad-hoc signed app has a requirement made of its own hash, so every rebuild is a new app to TCC and the Keychain: Accessibility prompts again, and "Always Allow" on a Keychain item never sticks. The family's answer for Mac apps that never leave its own machines is one self-signed code-signing identity, shared by every native app, launcher and permission-bearing helper, imported into the login keychain of every Mac that builds. Signed with it, an app's designated requirement becomes identifier "…" and certificate leaf = H"…" — identical across rebuilds, and identical across every Mac that signs with the same certificate.
Why Not an Apple Certificate?
An Apple Developer ID certificate would also give a stable requirement, and it is what you need to distribute to other people (the last lesson of this track). But a trusted, Gatekeeper-accepted, notarization-capable certificate is a credential: if it leaks, someone can ship software that macOS treats as signed by you. A self-signed identity opens nothing beyond the Macs that trust it: Gatekeeper rejects it, and it unlocks no account and no server. On those Macs, though, it is exactly what TCC grants and Keychain access lists name, so whoever holds its private key can sign a binary with the same identifier and inherit them. Its blast radius is the private repository plus a way to run code on these Macs, and anyone with both already has what those grants protect. That is why the family keeps its self-signed identity in its private repository, and why a trusted certificate must never be committed anywhere.
The Costs, Stated Plainly
- Gatekeeper does not trust it. The apps run on Macs where the certificate is installed and where you launch them yourself — not on a stranger's Mac.
- Each Mac's keychain asks once, the first time
codesignuses the key: "codesign wants to use key …". Click Always Allow once per Mac. - After moving an app from ad-hoc to the stable identity, reset its old grants once (
tccutil reset Accessibility com.example.spark), because grants pinned to the old cdhash will not match the new requirement. Grant once more, and it lasts. - Regenerating the certificate changes the leaf hash, so every Mac re-grants once and every app is rebuilt.
The Detection Trap
A build script that checks for the identity with security find-identity -v -p codesigning will conclude it is missing. -v filters to valid identities — ones that chain to a trusted root — and a self-signed identity is reported as CSSMERR_TP_NOT_TRUSTED, so it is hidden. Leave out -v. This one trap kept a family dictation app on ad-hoc signing, re-prompting on every build, until it was found.