Il est deux heures du matin. Votre app compile. Vous la signez. Vous l’empaquetez dans un DMG. Vous exécutez notarytool submit. Apple dit “In Progress”. Vous attendez 5 minutes. 10. 20. Une heure. Deux heures. La soumission reste “In Progress”. Vous allez vous coucher. Le lendemain matin : Invalid.
Sans plus d’explication que “The signature of the binary is invalid”. Pour les deux architectures. Merci, Apple. Très utile.
La notarisation est l’un de ces processus qui fonctionne parfaitement… jusqu’à ce que ça ne fonctionne plus. Et quand ça échoue, vous vous retrouvez avec un .dmg que Gatekeeper ne va pas laisser ouvrir et une erreur qui ne vous dit rien. Après m’être battu avec ça pendant quelques jours avec Tokamak (mon app de menu bar pour surveiller le quota de Claude), j’ai décidé de documenter tout ce que j’avais appris et d’écrire un linter pour ne plus jamais repasser par là.
Ce qu’est la notarisation (en termes clairs)
Imaginez que le Mac App Store soit un centre commercial avec un agent de sécurité. Mais vous ne voulez pas vendre dans le centre commercial — vous voulez distribuer votre app directement, avec votre propre DMG. Un stand de rue.
Apple dit : “D’accord, vous pouvez. Mais d’abord passez par le videur.”
Ce videur, c’est la notarisation. C’est un service automatisé d’Apple qui scanne votre app signée, vérifie qu’elle ne contient pas de malware connu, et si tout va bien, vous donne un ticket. Vous agrafez ce ticket à votre DMG (stapler staple) et dès lors, quand un utilisateur le télécharge et essaie de l’ouvrir, Gatekeeper voit le ticket et dit “allez-y”.
Sans ce ticket, l’utilisateur voit ceci :
“Tokamak.app” ne peut pas être ouvert parce qu’Apple ne peut pas vérifier qu’il ne contient pas de logiciels malveillants.
Et votre app reste sur le carreau.
Pourquoi Apple fait ça
Il y a deux raisons. Une légitime. Une autre… enfin bon.
La légitime : protéger les utilisateurs. Avant la notarisation (introduite dans macOS 10.14.5, 2019), n’importe qui pouvait distribuer un .app signé avec Developer ID et macOS l’ouvrait sans broncher. Le code signing vérifiait l’identité du développeur, mais ne scannait pas le contenu. Si votre app signée incluait un keylogger, signé et tout, ça s’exécutait.
La notarisation ajoute une couche : Apple scanne le binaire à la recherche de malware connu et de comportements suspects avant qu’il n’arrive chez l’utilisateur. Ce n’est pas une révision humaine comme l’App Store — c’est un système automatisé. Mais c’est quelque chose.
L’autre raison : le contrôle. Apple veut que vous passiez par App Store Connect pour tout. La distribution directe avec Developer ID a toujours été le citoyen de seconde classe. La notarisation est un pas de plus sur ce chemin de “vous pouvez distribuer en dehors de l’App Store, mais on va vous rendre la tâche inconfortable”.
Ceci dit, la notarisation est obligatoire depuis macOS 10.15 pour toute app distribuée en dehors de l’App Store. Ce n’est pas optionnel. Votre app va notarisée ou ne s’ouvre pas. Point.
Les 7 erreurs qui vont vous faire perdre des heures
Après avoir collecté mes erreurs et celles des forums de développeurs, voici celles qui font le plus mal :
1. Il manque le --timestamp
C’est le classique. Votre codesign fonctionne parfaitement en local, Gatekeeper ne se plaint pas, et la notarisation renvoie “The signature of the binary is invalid.”
| |
Le secure timestamp prouve que la signature a été faite pendant que le certificat était valide. Sans lui, Apple ne fait pas confiance. C’est comme signer un contrat sans date — techniquement c’est valide, mais personne ne va l’accepter.
2. Certificat incorrect
Il y a trois certificats qui sonnent pareil et font des choses différentes :
| Certificat | Pour quoi |
|---|---|
| Apple Development | Builds de debug (développement local) |
| Apple Distribution | App Store et TestFlight |
| Developer ID Application | Distribution directe + notarisation |
Si vous signez avec “Apple Development” et essayez de notariser, Apple vous envoie paître. Vous avez besoin de Developer ID Application. Ça semble évident, mais quand vous vous y battez depuis trois heures, ça ne l’est plus.
3. Hardened Runtime désactivé
La notarisation exige que votre app utilise le Hardened Runtime. C’est une protection qui empêche votre app de faire des choses comme injecter du code dans d’autres processus ou désactiver les protections mémoire.
| |
Sans --options runtime, la signature est valide mais Apple la rejette. C’est comme aller à un entretien avec le CV parfait mais sans pantalon.
4. xcodebuild -exportArchive se plante
Si vous utilisez xcodebuild -exportArchive avec method: developer-id, préparez-vous : ça se plante. Indéfiniment. Sans output. Sans erreur. Juste… du silence.
Le problème c’est que exportArchive a besoin d’identifiants Apple ID pour contacter les serveurs de distribution, et s’il ne les a pas en cache (ou s’il y a de vieux comptes corrompus dans le Keychain), il reste en attente d’une authentification interactive qui n’arrive jamais.
La solution : contourner exportArchive et faire la signature manuellement.
| |
Bricolage, oui. Mais ça marche. Et ça ne se plante pas.
5. codesign demande l’accès au Keychain… en silence
La première fois que vous signez avec un nouveau certificat, codesign a besoin d’accéder à la clé privée stockée dans le Keychain. macOS affiche un popup demandant permission.
Le problème : si vous exécutez codesign depuis un IDE, un script ou un outil comme Claude Code, le popup peut ne pas apparaître. Ou apparaître derrière toutes les fenêtres. Et codesign reste planté en attendant votre réponse à un popup que vous ne voyez pas.
Solution : la première fois, exécutez codesign directement depuis le terminal. Cliquez “Toujours autoriser” sur le popup. À partir de là, les scripts fonctionneront sans demander.
6. La notarisation traîne… et traîne… et traîne
Apple dit que “most uploads are processed within minutes.” La réalité :
- Normal : 2-15 minutes
- Première fois avec nouveau Developer ID : jusqu’à 1 heure
- Quand Apple a des problèmes : des heures. Ou des jours. Sans avertissement.
En février 2026, il y a des rapports sur les forums Apple de soumissions coincées en “In Progress” pendant plus de 16 heures. Le service était techniquement “opérationnel”, mais beaucoup de soumissions ne se traitaient simplement pas.
Vous ne pouvez rien faire à part attendre et réessayer. Bienvenue dans l’expérience développeur d’Apple.
7. Extended attributes qui cassent la signature
macOS ajoute des extended attributes aux fichiers téléchargés (le fameux com.apple.quarantine). Si votre pipeline de build copie des fichiers qui ont ces attributs, la signature devient invalide.
| |
C’est un détail que personne ne vous dit jusqu’à ce que ça vous morde.
Le linter : 25 vérifications pour ne pas se retrouver à poil
Après avoir accumulé toutes ces erreurs, j’ai écrit un script qui vérifie 25 conditions de notarisation — tant statiques (votre code et configuration) que dynamiques (les artefacts de build).
Il s’exécute comme ça :
| |
Et produit quelque chose comme :
── CRITICAL ──
✓ PASS C1: export-direct passe --entitlements à codesign
✗ FAIL C2: Release CODE_SIGN_IDENTITY n'est pas Developer ID
✓ PASS C3: ENABLE_HARDENED_RUNTIME = YES en Release
── HIGH ──
✗ FAIL H1: export-direct N'exécute PAS xattr -cr
── MEDIUM ──
⚠ WARN M1: DMG n'est pas signé
✓ PASS M2: LSUIElement déclaré dans project.yml
⚠ WARN M4: Seul le DMG est agrafé, pas le .app
── CERTIFICATS ET KEYCHAIN ──
✓ PASS P1: Certificat Developer ID Application dans Keychain
✓ PASS P2: Profil notarytool 'tokamak-notary' configuré
── ARTEFACTS ──
✓ PASS P13: Binaire universel (x86_64 + arm64)
✓ PASS C2-V: Export signé avec Developer ID Application
✓ PASS C3-V: Hardened runtime actif dans signature de l'export
── RÉSUMÉ ──
Total: 25 vérifications
✓ 18 réussies
✗ 2 échouées
⚠ 3 avertissements
⊘ 2 ignorées (artefacts de build manquants)
✗ NE PAS NOTARISER — il y a 2 échecs à corriger d'abord.
Les vérifications se divisent en quatre niveaux :
CRITICAL — Si l’un échoue, la notarisation va échouer à coup sûr :
--entitlementsdanscodesign(si vous ne les passez pas, vous perdez sandbox et network)CODE_SIGN_IDENTITY= Developer ID en Release- Hardened Runtime activé
HIGH — Probablement va échouer ou causer des problèmes :
xattr -cravant de signer- Pas de dylibs de debug en Release
MEDIUM — Peut fonctionner, mais vous prenez des risques :
- DMG signé (pas obligatoire, mais recommandé)
LSApplicationCategoryTypepour App Store- Cohérence des versions entre
AppInfoetproject.yml
ARTEFACTS — Vérifie les builds générés :
- Signature valide avec Developer ID
- Hardened Runtime actif dans la signature
- Binaire universel (x86_64 + arm64)
- Pas de
.DS_Storeni symlinks dans le bundle - Structure correcte (
Contents/{MacOS,Resources,Info.plist})
Le script est statique pour les vérifications de configuration (n’a pas besoin de builds), et dynamique pour les artefacts (vous devez avoir fait make archive && make export-direct). Ainsi vous pouvez l’exécuter à tout moment comme preflight check.
Le flux complet
Pour que vous ayez une idée du processus de bout en bout :
flowchart TD
subgraph build[" 🔨 Build "]
direction TB
A["xcodegen generate"] --> B["xcodebuild archive<br/>(Release, signé)"]
end
subgraph sign[" ✍️ Signature "]
direction TB
C["Copier .app de l'archive"] --> D["xattr -cr<br/>(nettoyer attributes)"]
D --> E["codesign --force<br/>--options runtime<br/>--timestamp<br/>--sign Developer ID"]
end
subgraph package[" 📦 Empaquetage "]
direction TB
F["hdiutil create<br/>(DMG format UDZO)"]
end
subgraph notarize[" 🍎 Notarisation "]
direction TB
G["notarytool submit<br/>--wait"] --> H{¿Accepted?}
H -->|Oui| I["stapler staple<br/>(agrafer ticket)"]
H -->|Non| J["notarytool log<br/>(voir erreurs)"]
J --> K["Corriger et<br/>re-signer"]
end
subgraph lint[" 🔍 Preflight "]
direction TB
L["lint-notarize.sh<br/>(25 vérifications)"]
end
build --> sign
sign --> lint
lint -->|✓ Tout OK| package
lint -->|✗ Échecs| K
package --> notarize
K --> sign
I --> M["✅ DMG prêt<br/>pour distribution"]
Et en commandes :
| |
Leçons apprises à 3h du matin
--timestamptoujours. Toujours. Pas d’excuse.- La première signature, en terminal. Pour que le popup du Keychain sorte.
- N’utilisez pas
exportArchivepour Developer ID. Copie +codesignmanuel. - Soyez patient avec Apple. La notarisation peut prendre des minutes ou des heures. Il n’y a pas de SLA.
- Un linter vaut mieux que cent tentatives. Les 30 secondes que prend le script vous font économiser des heures de soumissions ratées.
La notarisation c’est comme l’examen de conduite : pénible, bureaucratique, et probablement nécessaire. Mais une fois que vous maîtrisez le processus, ça devient juste une étape de plus du pipeline. Une étape qui ne vous réveille plus à 3h du matin en vous demandant pourquoi Apple dit “Invalid” sans daigner expliquer la raison.
Le script complet est dans le repo de Tokamak.