cheat sheet
codesign
End-to-end macOS distribution pipeline — sign binaries and app bundles with codesign, notarize with notarytool, staple tickets with stapler, and verify Gatekeeper acceptance with spctl.
codesign — macOS Code Signing, Notarization & Stapling
What it is
codesign is the Apple-supplied command-line tool for embedding cryptographic signatures into Mach-O binaries, application bundles (.app), kernel extensions, command-line tools, and installer packages on macOS. It ships at /usr/bin/codesign as part of the Command Line Tools. A code signature binds an identity (a certificate issued by Apple) to the bytes of an executable, declares the entitlements the executable may exercise, and locks the executable into the hardened runtime when so requested. Modern macOS distribution is a four-step pipeline: sign (codesign), notarize (xcrun notarytool), staple (xcrun stapler), verify (codesign --verify + spctl --assess). Skipping notarization is what triggers the "App can't be opened because Apple cannot check it for malicious software" dialog on macOS 10.15+; skipping stapling means a notarized app still hits the network on first launch to fetch its notarization ticket. The conceptual peer on Windows is Authenticode (signtool.exe); on Linux there is no direct equivalent — the closest is gpg --detach-sign plus a distribution-curated keyring.
Install
codesign, notarytool, stapler, and spctl are all bundled with Xcode and the Command Line Tools. If codesign --version reports command not found, install the tools.
# Install Command Line Tools (no full Xcode required)
xcode-select --install
# Or, if Xcode is already installed, point xcode-select at it
sudo xcode-select -s /Applications/Xcode.app
# Verify
codesign --version
xcrun notarytool --version
xcrun stapler --help | head -1
spctl --version
Output:
0.7.0
0.42
OVERVIEW: Staple a notarization ticket to a code-signed file.
spctl version 1.3.0 (1.3.0)
Identities and certificates
Code signing requires a certificate with a private key in the user's keychain. The two relevant Apple certificate kinds for distribution outside the Mac App Store are Developer ID Application (for .app/binary signing of redistributable software) and Developer ID Installer (for .pkg packages). Both are issued by Apple via the Developer Program; the certificate's Common Name is what codesign --sign accepts as the identity argument.
# List signing identities visible to codesign
security find-identity -v -p codesigning
# Just the names
security find-identity -v -p codesigning | awk -F'"' '/"/{print $2}'
Output:
1) 1A2B3C4D5E6F7890ABCDEF0123456789ABCDEF01 "Developer ID Application: Alice Dev (TEAMID12)"
2) 0F1E2D3C4B5A69788776655443322110FFEEDDCC "Developer ID Installer: Alice Dev (TEAMID12)"
3) 9988776655443322110011223344556677889900 "Apple Development: alice@example.com (XXXXXXXXXX)"
3 valid identities found
Identities can be referenced three ways: by full Common Name ("Developer ID Application: Alice Dev (TEAMID12)"), by SHA-1 fingerprint (1A2B3C4D5E6F...), or by the team ID parenthesized form. Scripts pin the fingerprint to avoid breaking when a certificate is renewed and the CN changes year-to-year.
Syntax
codesign [--sign IDENTITY] [--options OPT,...] [--entitlements PLIST] \
[--timestamp] [--deep] [--force] [--verbose=LEVEL] \
PATH...
codesign --verify [--strict] [--deep] [--verbose=LEVEL] PATH
codesign --display [-d] [-r-] [--verbose=LEVEL] PATH
codesign --remove-signature PATH
Output: (none — exits 0 on success)
Essential options
| Option | Meaning |
|---|---|
-s / --sign IDENTITY | Sign with this identity (CN or fingerprint) |
--options runtime[,library] | Enable hardened runtime (required for notarization) |
--entitlements FILE.plist | Attach the entitlements file (XML plist) |
--timestamp | Include a secure timestamp from Apple's timestamp server (required for notarization) |
--deep | Recursively sign nested code (legacy; prefer signing each bundle explicitly) |
-f / --force | Replace any existing signature |
--preserve-metadata=... | Keep entitlements/identifier/etc. from prior signature |
--identifier ID | Override the bundle/program identifier |
--prefix PREFIX | Set the identifier prefix |
--requirements - / --requirements TEXT | Designated requirement |
--keychain KEYCHAIN | Use a specific keychain |
-v / --verbose=LEVEL | Verbosity (1–4) |
-d / --display | Show signature info |
-r- | Print the designated requirement |
--verify | Verify the signature |
--strict | Strictest verification mode |
--remove-signature | Strip the signature |
Signing a single binary
The minimal case: a standalone Mach-O command-line tool. Sign with a Developer ID Application identity, attach the secure timestamp, and enable the hardened runtime. Verify with -dvv to dump the signed properties.
# Sign
codesign --sign "Developer ID Application: Alice Dev (TEAMID12)" \
--options runtime \
--timestamp \
./mytool
# Inspect the signature
codesign -dvv ./mytool
Output (codesign -dvv ./mytool):
Executable=/Users/alice/Projects/mytool/mytool
Identifier=mytool
Format=Mach-O thin (arm64)
CodeDirectory v=20500 size=531 flags=0x10000(runtime) hashes=11+2 location=embedded
Signature size=9056
Authority=Developer ID Application: Alice Dev (TEAMID12)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=May 25, 2026 at 14:02:11
Info.plist=not bound
TeamIdentifier=TEAMID12
Runtime Version=14.0.0
Sealed Resources=none
Internal requirements count=1 size=180
Strict verification — the form Gatekeeper itself runs — should also pass:
codesign --verify --strict --verbose=2 ./mytool
Output:
./mytool: valid on disk
./mytool: satisfies its Designated Requirement
Signing an .app bundle
App bundles contain nested frameworks, helper tools, and resources. Each nested code item must be signed before the enclosing bundle is signed — the outer signature seals hashes of the inner signatures. Modern best practice is to sign each frame/helper explicitly with its own identifier and entitlements rather than relying on --deep (which is now deprecated for distribution because it skips entitlements files for nested code).
# A typical .app layout
# MyApp.app/
# Contents/
# Info.plist
# MacOS/MyApp
# Frameworks/Sparkle.framework
# Frameworks/MyLib.framework
# PlugIns/Helper.appex
# Resources/...
# Sign inside-out: nested frameworks first, then plugins, then the app
codesign --sign "Developer ID Application: Alice Dev (TEAMID12)" \
--options runtime --timestamp --force \
"MyApp.app/Contents/Frameworks/MyLib.framework/Versions/A/MyLib"
codesign --sign "Developer ID Application: Alice Dev (TEAMID12)" \
--options runtime --timestamp --force \
"MyApp.app/Contents/Frameworks/MyLib.framework"
codesign --sign "Developer ID Application: Alice Dev (TEAMID12)" \
--options runtime --timestamp --force \
--entitlements helper.entitlements.plist \
"MyApp.app/Contents/PlugIns/Helper.appex"
# Outer bundle last, with its own entitlements
codesign --sign "Developer ID Application: Alice Dev (TEAMID12)" \
--options runtime --timestamp --force \
--entitlements MyApp.entitlements.plist \
"MyApp.app"
# Verify the whole tree
codesign --verify --deep --strict --verbose=2 "MyApp.app"
Output:
MyApp.app: valid on disk
MyApp.app: satisfies its Designated Requirement
Loops for nested code
A common pattern for apps with many nested frameworks: walk the tree and sign each binary, sorting deepest first so children are sealed before parents.
APP=MyApp.app
IDENTITY="Developer ID Application: Alice Dev (TEAMID12)"
find -s "$APP" \( -name "*.dylib" -o -name "*.framework" -o -name "*.bundle" \) \
| sort -r \
| while read -r f; do
codesign --sign "$IDENTITY" --options runtime --timestamp --force "$f"
done
codesign --sign "$IDENTITY" --options runtime --timestamp --force \
--entitlements MyApp.entitlements.plist "$APP"
Output: (none — exits 0 on success)
The hardened runtime
The hardened runtime is a set of opt-in protections enabled by --options runtime. It restricts what a signed binary can do at execution: no unsigned writable memory pages (W^X), no dlopen of unsigned libraries (without a specific entitlement), no debugger attachment (without com.apple.security.get-task-allow), and no dyld environment variables. As of macOS 10.15, notarization requires the hardened runtime — notarytool rejects submissions without it. Once enabled, certain behaviours your app may rely on (loading random plugins, running scripts via interpreter shims) need explicit entitlement exceptions.
# Inspect runtime flags on a binary
codesign -dvv ./mytool 2>&1 | grep -i flags
Output:
CodeDirectory v=20500 size=531 flags=0x10000(runtime) hashes=11+2
A flags value containing runtime (0x10000) means the hardened runtime is on. Other interesting flags: library-validation (0x2000), kill (0x200).
Entitlements
Entitlements are a property list (*.plist) of key-value pairs that grant a signed binary specific privileges. The classic categories are capabilities (com.apple.security.network.client, com.apple.security.files.user-selected.read-write), hardened-runtime exceptions (com.apple.security.cs.allow-jit, com.apple.security.cs.disable-library-validation), and App Sandbox keys (only meaningful for App Store apps). The plist is embedded into the signature; you cannot change entitlements after the fact without re-signing.
<!-- MyApp.entitlements.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<false/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<false/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
# Embed during sign
codesign --sign "Developer ID Application: Alice Dev (TEAMID12)" \
--options runtime --timestamp \
--entitlements MyApp.entitlements.plist \
MyApp.app
# Inspect embedded entitlements
codesign -d --entitlements - MyApp.app
# Or as plain XML
codesign -d --entitlements :- MyApp.app
Output:
[Dict]
[Key] com.apple.security.cs.allow-jit
[Value]
[Bool] true
[Key] com.apple.security.network.client
[Value]
[Bool] true
[Key] com.apple.security.files.user-selected.read-write
[Value]
[Bool] true
Notarization with notarytool
Notarization is Apple's malware scan and signing service for redistributable macOS software. You upload a signed .zip/.dmg/.pkg to Apple's service; they scan it and, if clean, issue a ticket that says "this binary's hash has been notarized". The modern client is xcrun notarytool (since Xcode 13); the older altool --notarize-app is deprecated. Authentication uses either an Apple ID + app-specific password + team ID, or — recommended for CI — an App Store Connect API key.
Authenticating once into the keychain
Store credentials in the login keychain under a profile name so subsequent invocations don't need to repeat them.
# Method A: Apple ID + app-specific password (interactive)
xcrun notarytool store-credentials "AC_PASSWORD" \
--apple-id alice@example.com \
--team-id TEAMID12 \
--password app-specific-password-goes-here
# Method B: App Store Connect API key (CI-friendly)
xcrun notarytool store-credentials "AC_API" \
--key ~/Keys/AuthKey_ABCD1234.p8 \
--key-id ABCD1234 \
--issuer 12345678-1234-1234-1234-123456789012
Output:
This process stores your credentials securely in the Keychain. You reference these credentials later using a profile name.
Validating your credentials...
Success. Credentials validated.
Credentials saved to Keychain.
To use them, specify `--keychain-profile "AC_PASSWORD"`
Submitting for notarization
submit --wait blocks until the service returns a verdict. The --output-format json flag is the script-friendly form. If the verdict is anything other than Accepted, the log subcommand fetches the JSON breakdown — read this carefully, the error code field tells you which binary or which signature attribute caused the rejection.
# 1. Package the signed app as a zip (notarytool only accepts zip/dmg/pkg)
ditto -c -k --keepParent MyApp.app MyApp.zip
# 2. Submit and wait for verdict
xcrun notarytool submit MyApp.zip \
--keychain-profile "AC_PASSWORD" \
--wait
# 3. Inspect submission log if it failed
xcrun notarytool log <SUBMISSION_UUID> --keychain-profile "AC_PASSWORD"
Output:
Conducting pre-submission checks for MyApp.zip and initiating connection to the Apple notary service...
Submission ID received
id: 9f8e7d6c-5b4a-3210-fedc-ba9876543210
Successfully uploaded file
id: 9f8e7d6c-5b4a-3210-fedc-ba9876543210
path: /Users/alice/Projects/MyApp.zip
Waiting for processing to complete.
Current status: Accepted...........
Processing complete
id: 9f8e7d6c-5b4a-3210-fedc-ba9876543210
status: Accepted
A failing run instead returns status: Invalid and you fetch the log:
xcrun notarytool log 9f8e7d6c-... --keychain-profile "AC_PASSWORD" \
| jq '.issues[] | {severity, message, path}'
Output:
{"severity":"error","message":"The binary is not signed with a valid Developer ID certificate.","path":"MyApp.zip/MyApp.app/Contents/MacOS/MyApp"}
{"severity":"error","message":"The signature does not include a secure timestamp.","path":"MyApp.zip/MyApp.app/Contents/MacOS/MyApp"}
{"severity":"error","message":"The executable does not have the hardened runtime enabled.","path":"MyApp.zip/MyApp.app/Contents/MacOS/MyApp"}
Polling without --wait
For long-running CI where you don't want to hold a worker:
xcrun notarytool submit MyApp.zip --keychain-profile "AC_PASSWORD"
# ... record the submission id ...
xcrun notarytool info <UUID> --keychain-profile "AC_PASSWORD"
xcrun notarytool history --keychain-profile "AC_PASSWORD"
Output:
id: 9f8e7d6c-...
createdDate: 2026-05-25T14:02:11.000Z
status: In Progress
name: MyApp.zip
Stapling with stapler
Notarization produces a ticket that the running Mac fetches from Apple over the network on first launch. Stapling attaches that ticket directly to the artifact so it works offline (or behind a firewall). Stapling is mandatory for .dmg distribution: the ticket lives on the DMG itself; without it, users running offline will see the malware warning even though the DMG is notarized.
# Staple an app (after notarization succeeded)
xcrun stapler staple MyApp.app
# Staple a DMG
xcrun stapler staple MyApp.dmg
# Validate that the ticket is attached and matches
xcrun stapler validate MyApp.app
xcrun stapler validate -v MyApp.dmg
Output:
Processing: /Users/alice/Projects/MyApp.app
Processing: /Users/alice/Projects/MyApp.app
The staple and validate action worked!
Processing: /Users/alice/Projects/MyApp.dmg
The validate action worked!
xcrun stapler validate exits non-zero if the ticket is missing or doesn't match the artifact — wire that into your CI as the canonical "ready to ship" check.
Gatekeeper assessment with spctl
Gatekeeper is the macOS policy daemon that decides whether the system will launch a downloaded binary. spctl --assess runs the same check Gatekeeper would run on first launch; if it passes you can confidently distribute the artifact. The --type exec form is for binaries; --type install is for installer packages; the default for .app bundles is exec.
spctl --assess --type exec --verbose=4 MyApp.app
spctl --assess --type install --verbose=4 MyApp.pkg
# As of macOS 14.4, Gatekeeper requires notarization for downloaded code
spctl --status # is Gatekeeper enabled?
Output:
MyApp.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: Alice Dev (TEAMID12)
MyApp.pkg: accepted
source=Notarized Developer ID
origin=Developer ID Installer: Alice Dev (TEAMID12)
assessments enabled
A failing assessment is the most common pre-ship diagnostic — if spctl rejects the artifact, no amount of codesign --verify passing will save you on a user's Mac.
MyApp.app: rejected (the code is valid but does not seem to be an app)
source=Unnotarized Developer ID
source=Unnotarized Developer ID means the binary is correctly signed but has not been notarized — re-run notarytool submit and stapler staple.
Designated requirements
A designated requirement is a small expression (a "csreq") embedded in the signature that says "the code I will accept as compatible with me has these properties" — same team ID, same identifier, same anchor. Apple ships sensible defaults; you usually only inspect them. Read them with codesign -d -r-.
# Show the designated requirement as text
codesign -d -r- MyApp.app
# Decode an existing requirement from binary form
csreq -r- -t < req.bin
# Author one from scratch (rarely needed)
csreq -r 'identifier "com.example.myapp" and anchor apple generic and certificate leaf[subject.OU] = "TEAMID12"' -b req.bin
Output:
Executable=/Users/alice/Projects/MyApp.app/Contents/MacOS/MyApp
designated => identifier "com.example.myapp" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "TEAMID12"
Removing a signature
--remove-signature strips a code signature, leaving an unsigned binary. This is occasionally useful when re-signing a third-party binary with your own identity for internal redistribution, or when an embedded signature is preventing modification of a binary you control.
codesign --remove-signature ./mytool
codesign -dvv ./mytool 2>&1 | grep -i signature
Output:
./mytool: code object is not signed at all
The full pipeline
Putting it together — sign every nested binary, sign the outer app, package as a DMG (or zip), notarize, staple both the DMG and the app, verify everything one more time:
APP=MyApp.app
DMG=MyApp.dmg
ID="Developer ID Application: Alice Dev (TEAMID12)"
ENT=MyApp.entitlements.plist
PROFILE="AC_PASSWORD"
# 1. Sign nested code (deepest first)
find -s "$APP" \( -name "*.dylib" -o -name "*.framework" -o -name "*.bundle" \) \
| sort -r \
| while read -r f; do
codesign --sign "$ID" --options runtime --timestamp --force "$f"
done
# 2. Sign outer app
codesign --sign "$ID" --options runtime --timestamp --force \
--entitlements "$ENT" "$APP"
# 3. Verify locally before submitting
codesign --verify --deep --strict --verbose=2 "$APP"
spctl --assess --type exec --verbose=4 "$APP" || \
echo "Note: spctl will say 'rejected' until notarization+stapling complete"
# 4. Package
hdiutil create -volname MyApp -srcfolder "$APP" -ov -format UDZO "$DMG"
# 5. Sign the DMG itself
codesign --sign "$ID" --timestamp "$DMG"
# 6. Notarize
xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait
# 7. Staple both DMG and inner app
xcrun stapler staple "$DMG"
xcrun stapler staple "$APP" # if shipping the app on its own too
# 8. Final verification
xcrun stapler validate "$DMG"
spctl --assess --type install --verbose=4 "$DMG" # for installer pkgs
spctl --assess --type exec --verbose=4 "$APP"
Output (final spctl):
MyApp.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: Alice Dev (TEAMID12)
codesign vs other signing tools
| Tool | Scope | When to reach for it |
|---|---|---|
codesign | Mach-O, .app, frameworks, bundles | Every macOS signing task |
productsign | .pkg installer packages | Signing Distribution .pkg for installer tool |
pkgbuild / productbuild | Build .pkg files | Before productsign |
xcrun notarytool | Apple notary service | After every signing pass for distribution |
xcrun stapler | Attach notary ticket | After successful notarization |
spctl | Gatekeeper assessment | Pre-ship verification |
csreq | Read/write designated requirements | When customising signing requirements |
xattr -d com.apple.quarantine | Remove quarantine bit | When sideloading binaries you trust |
signtool.exe (Windows) | Authenticode | The Windows analogue |
gpg --detach-sign (Linux) | Detached signature | Loosely equivalent for tarballs / releases |
Common pitfalls
- Forgetting
--timestamp— notarization will reject any signature without a secure timestamp. The flag is not the default; you must pass it explicitly on everycodesigninvocation in the pipeline. - Using
--deepinstead of explicit per-binary signing —--deepskips per-bundle entitlements and is deprecated for distribution. Sign each nested framework/helper individually with its own identity and entitlements file. - Hardened runtime not enabled (
--options runtime) — notarization will reject the binary withThe executable does not have the hardened runtime enabled.This is the single most common cause of failed first notarization runs. - App-specific password used directly each invocation — leaks the password into shell history and ps. Always run
notarytool store-credentialsonce and use--keychain-profile. - Notarizing without stapling the DMG — the app inside is notarized, but the DMG itself isn't. Users opening the DMG offline see the unsigned warning. Staple both.
codesign --verifypasses butspctl --assessfails —codesignchecks structural validity;spctlchecks Gatekeeper policy (notarization status, certificate trust). The first does not imply the second.- Signing without
--forcere-signs in place but does not strip a broken existing signature — when iterating, always pass--force(or--remove-signaturefirst) to ensure the new signature replaces the old. - Editing files inside the bundle after signing — any change to
Resources/,Info.plist, or executable bytes invalidates the signature. Re-sign after every modification. Tools likeplistutilare a common silent breaker. - Wrong certificate kind for the artifact —
.appand binaries needDeveloper ID Application;.pkginstallers needDeveloper ID Installer. Using the wrong one will fail Gatekeeper assessment even after notarization. - Stapling a
.zip— you can notarize a.zip(with the.appinside), but you cannot staple a.zip. Staple the inner.app, re-zip, and ship that. - Entitlements file with stray top-level keys —
codesignaccepts malformed plists silently in older versions. Always runplutil -lint MyApp.entitlements.plistbefore signing. - Forgetting that secure timestamps require Apple's timestamp server reachable — air-gapped or restricted-egress build machines need a proxy or pre-signed timestamps.
--timestamp=noneexists but disqualifies you from notarization.
Sources
References consulted while writing this article. Links open in a new tab.
- Apple Developer — codesign(1) man page — Authoritative flag list used while writing the options reference.
- SS64 — codesign — Cross-version notes.
Real-world recipes
Bootstrap script: sign + notarize + staple a single-binary CLI
A redistributable command-line tool — the simplest possible pipeline that still produces a Gatekeeper-accepted result on macOS 14+.
#!/usr/bin/env bash
set -euo pipefail
BINARY=mytool
ID="Developer ID Application: Alice Dev (TEAMID12)"
PROFILE="AC_PASSWORD"
# 1. Sign
codesign --sign "$ID" --options runtime --timestamp --force "$BINARY"
# 2. Wrap in a zip for notarytool
ditto -c -k --keepParent "$BINARY" "$BINARY.zip"
# 3. Notarize
xcrun notarytool submit "$BINARY.zip" --keychain-profile "$PROFILE" --wait
# 4. Notarytool stamps the ticket against the binary's hash, but command-line
# tools cannot be stapled directly (stapling needs a wrapping bundle/DMG/pkg).
# For a CLI ship the .zip — Gatekeeper will fetch the ticket on first run.
# 5. Verify
codesign --verify --strict --verbose=2 "$BINARY"
echo "Done. Distribute $BINARY.zip"
Output:
Processing complete
status: Accepted
mytool: valid on disk
mytool: satisfies its Designated Requirement
Done. Distribute mytool.zip
CI snippet: notarize and bail on the first failed log issue
#!/usr/bin/env bash
set -euo pipefail
ARTIFACT="MyApp.dmg"
PROFILE="AC_API" # App Store Connect API key profile
submission=$(xcrun notarytool submit "$ARTIFACT" \
--keychain-profile "$PROFILE" \
--output-format json --wait)
status=$(jq -r '.status' <<<"$submission")
uuid=$(jq -r '.id' <<<"$submission")
if [[ "$status" != "Accepted" ]]; then
echo "Notarization failed with status: $status"
xcrun notarytool log "$uuid" --keychain-profile "$PROFILE" | jq .
exit 1
fi
xcrun stapler staple "$ARTIFACT"
xcrun stapler validate "$ARTIFACT"
echo "$ARTIFACT is shippable."
Output:
MyApp.dmg is shippable.
Remove quarantine from a self-signed internal tool
For an internal binary that is signed with your own (untrusted) certificate, distributing it via download will attach com.apple.quarantine xattrs that Gatekeeper checks. Strip the xattr after install on each target machine.
xattr -dr com.apple.quarantine /Applications/InternalTool.app
spctl --assess --type exec /Applications/InternalTool.app
Output:
/Applications/InternalTool.app: rejected (the code is valid but does not seem to be an app)
source=Unsigned Origin
For internal-only distribution you can also disable Gatekeeper for ad-hoc-signed code with spctl --add plus the binary path — but the modern and safer answer is to enroll the binary in your MDM-pushed signed Developer ID flow.
Compare two signed binaries
When debugging "works on my machine, fails on theirs", a fast first check is whether the two copies of the binary have identical signatures.
codesign -dvv ./build-a/MyApp.app 2>&1 | grep -E "Identifier|CodeDirectory|Authority|TeamIdentifier|Timestamp"
codesign -dvv ./build-b/MyApp.app 2>&1 | grep -E "Identifier|CodeDirectory|Authority|TeamIdentifier|Timestamp"
# Or hash-compare the CDHash directly
codesign -dvv ./build-a/MyApp.app 2>&1 | grep CDHash
codesign -dvv ./build-b/MyApp.app 2>&1 | grep CDHash
Output:
Identifier=com.example.myapp
CodeDirectory v=20500 size=531 flags=0x10000(runtime) hashes=11+2
Authority=Developer ID Application: Alice Dev (TEAMID12)
TeamIdentifier=TEAMID12
Timestamp=May 25, 2026 at 14:02:11
CDHash=8a1f2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f90
CDHash=8a1f2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f90
Strip and re-sign a downloaded third-party tool with your identity
When wrapping a public binary into your own distribution, you must strip its signature and re-sign — otherwise notarization fails because the Team ID won't match yours.
codesign --remove-signature ./vendor/tool
codesign --sign "Developer ID Application: Alice Dev (TEAMID12)" \
--options runtime --timestamp \
./vendor/tool
codesign --verify --strict --verbose=2 ./vendor/tool
Output:
./vendor/tool: valid on disk
./vendor/tool: satisfies its Designated Requirement
Inspect entitlements of any signed binary
When debugging "feature X works in build A but not in build B", check whether the relevant entitlement is present.
codesign -d --entitlements :- /Applications/SomeApp.app \
| plutil -p - # pretty-print the embedded plist
Output:
{
"com.apple.security.app-sandbox" => 1
"com.apple.security.network.client" => 1
"com.apple.security.network.server" => 0
"com.apple.security.files.user-selected.read-write" => 1
}
Audit every signed binary in /Applications
A one-liner that flags any installed app that fails strict signature verification — useful during incident response.
for app in /Applications/*.app; do
result=$(codesign --verify --strict --no-strict --deep "$app" 2>&1)
if [[ $? -ne 0 ]]; then
echo "FAIL: $app"
echo " $result"
fi
done
Output:
FAIL: /Applications/OldShareware.app
/Applications/OldShareware.app: a sealed resource is missing or invalid