Skip to content
C.W.K.
Stream
Lesson 04 of 06 · published

To macOS, Your App Is Its Signature

~15 min · platform-map, code-signing, designated-requirement, tcc

Level 0Bundle Opener
0 XP0/81 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete
"You granted Accessibility to an app yesterday. Today macOS asks again. Nothing is broken — you shipped a different app."

Every Binary Is Signed, Whether You Asked or Not

On Apple silicon, native code must carry a code signature to run at all. If you never mention signing, the linker signs your binary anyway — an ad-hoc signature with no certificate behind it. Run codesign -dv on a fresh swift build product and you will see Signature=adhoc. The program runs. Everything looks fine.

The trap is what an ad-hoc signature is worth as an identity. A signature carries a designated requirement: the rule a future copy of the code must satisfy to count as "the same app". For ad-hoc code that rule is a hash of the exact bytes — cdhash H"e3e8…". Rebuild, and the bytes change, the hash changes, and the requirement names a program that no longer exists.

Who Uses That Requirement

  • TCC — the privacy system behind Accessibility, Microphone, Screen Recording, Input Monitoring, Calendars. A grant is stored against the app's code requirement.
  • The Keychain — an item's access list trusts the requirement of the app that created it.
  • Gatekeeper and notarization — whether a downloaded app may open at all.
  • Launch constraints, App Groups, provisioning on iOS — all keyed to a signing identity and a team.

So an ad-hoc menu-bar utility that needs Accessibility behaves like this: you grant it, it works, you rebuild, and macOS treats the new build as a stranger. A family dictation app re-prompted for Accessibility on every single rebuild until it was signed with a stable certificate. After that, one grant lasted forever.

Three Shapes of Requirement

Signed withDesignated requirement looks likeSurvives a rebuild?
Nothing (linker ad-hoc)cdhash H"…"No
A self-signed certificateidentifier "com.example.spark" and certificate leaf = H"…"Yes, on every Mac that signs with that same certificate
An Apple-issued certificateidentifier "com.example.spark" and anchor apple generic and … certificate leaf[subject.OU] = EXAMPLE_TEAMYes, for that team

The identifier plus a certificate is what makes an app itself across builds. Track 6 goes all the way down: the self-signed identity this family uses on the Mac, how TCC stores its three states, why a stable requirement still does not prove a grant exists, and the roads (hardened runtime, notarization, sandbox) the family deliberately does not take.

Code

Read a signature and its designated requirement·bash
swift build
BIN="$(swift build --show-bin-path)/Spark"
codesign -dv "$BIN" 2>&1 | grep -E 'Signature|flags'
# CodeDirectory v=20400 ... flags=0x2(adhoc) ...
# Signature=adhoc
codesign -dr - "$BIN"
# # designated => cdhash H"e3e8194102faaee2a3b1946f1cc4be7e26860679"

# Rebuild after any source change and run the last command again: the hash is different.
# Now read a signed app you trust:
codesign -dr - /Applications/SomeSignedApp.app

External links

Exercise

In your Spark package, run swift build and record the designated requirement with codesign -dr -. Change one string in the source, rebuild, and record it again. Then run the same command on two apps in /Applications that you have granted a privacy permission to. For each, write whether its requirement would survive a rebuild and which part of the requirement makes that true.
Hint
Look for the word cdhash versus the words certificate leaf. A requirement made only of a hash cannot survive new bytes; a requirement made of an identifier and a certificate can.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.