5 min de lecture

L'identité d'un processus macOS dépasse le Team ID

Un modèle pratique d'identité des processus macOS avec Team ID, identifiant de signature, exigence désignée, chemins et builds non signés.

L'identité d'un processus macOS dépasse le Team ID

Un Team ID ne suffit pas pour décider quel processus macOS peut utiliser un secret. Il identifie une équipe de développement Apple, pas un exécutable précis. L'autoriser seul donne potentiellement le même accès à toutes les applications, tous les outils et tous les futurs binaires correctement signés par cette équipe.

Une identité défendable combine l'autorité de signature et l'identifiant de signature, conserve une exigence compatible avec les mises à jour légitimes et l'évalue sur le processus qui formule réellement la demande. Le chemin reste utile dans l'écran d'approbation et l'audit, mais ne doit pas décider. Les builds non signés ou signés ad hoc exigent un circuit distinct, clairement plus faible.

Une signature indique qui a signé et quel nom de programme ce signataire revendique. Elle ne prouve ni que le processus mérite un mot de passe, ni que sa demande est raisonnable, ni qu'un autre programme du même développeur doit recevoir le même droit. L'identité alimente l'autorisation, elle ne la remplace pas.

Le Team ID identifie le signataire, pas le programme

Le Team ID indique quelle équipe Apple détient l'identité de signature. Pour un certificat développeur émis par Apple, le langage d'exigences l'expose dans l'unité organisationnelle du certificat feuille. Les outils peuvent aussi afficher TeamIdentifier.

Inspectez l'exécutable exact, pas seulement le bundle externe :

codesign -dvvv /Applications/Example.app/Contents/MacOS/Example 2>&1

La sortie utile ressemble à ceci :

Executable=/Applications/Example.app/Contents/MacOS/Example
Identifier=dev.example.agent
Authority=Developer ID Application: Example Developer (A1B2C3D4E5)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
TeamIdentifier=A1B2C3D4E5

Un Team ID valide délimite le signataire et exclut les autres équipes. La documentation Apple SigningIdentifier précise que plusieurs signataires peuvent revendiquer le même identifiant. Pour du code tiers, une vérification sûre exige donc aussi TeamIdentifier et une catégorie de validation adaptée.

Mais une équipe peut signer une application, son auxiliaire privilégié, un élément d'ouverture de session, un outil en ligne de commande, un service XPC et un diagnostic interne. Tous partagent éventuellement le même Team ID. Un service de signature compromis peut encore produire un nouveau binaire dans ce périmètre. L'équipe ne distingue aucun de ces programmes.

Team ID == équipe approuvée accorde donc beaucoup trop pour un secret. Cela revient à ouvrir une porte à toute une entreprise parce que les badges ont le même émetteur. L'émetteur compte, tout comme le nom inscrit sur chaque badge.

Une politique peut volontairement faire confiance à toute l'équipe pour une ressource locale jetable. Il faut alors la nommer « confiance d'équipe », et non identité d'application, afin que son étendue reste visible.

L'identifiant de signature sépare les programmes

L'identifiant de signature apporte le nom du programme dans l'espace du signataire. codesign l'affiche sous Identifier. Il correspond souvent à l'identifiant du bundle, sans que macOS l'impose, et un exécutable en ligne de commande peut en porter un sans bundle.

La paire minimale devient :

team_id = A1B2C3D4E5
signing_identifier = dev.example.agent

Elle signifie « le programme dev.example.agent signé par l'équipe A1B2C3D4E5 ». Une autre équipe peut copier le nom mais pas satisfaire la contrainte d'équipe; un autre programme de l'équipe devrait employer un autre identifiant.

Le signataire contrôle toutefois son espace de noms et peut réutiliser un identifiant. Une configuration défectueuse peut donner celui de l'application à un auxiliaire, et un signataire compromis peut copier la valeur approuvée. La paire suppose donc que le signataire protège ses certificats et ses noms.

Ce compromis reste adapté aux mises à jour. Un hash est plus précis mais change à chaque version; l'empreinte d'un certificat casse lors de son renouvellement. Team ID et identifiant acceptent les futures versions du même programme sans accepter tout le catalogue.

Inspectez chaque exécutable qui se connecte. TN3127 montre qu'une application et son outil intégré partagent l'équipe mais doivent avoir des identifiants différents. Si l'outil appelle le service, son identité compte.

Comparez l'identifiant octet par octet. Apple indique qu'aucune normalisation Unicode n'a lieu. Ne le mettez pas en minuscules et ne le reconstruisez pas depuis CFBundleIdentifier.

L'exigence désignée conserve l'identité entre les versions

L'exigence désignée, ou DR, est l'expression macOS qui décide si un code est le même qu'auparavant. Elle combine le nom du programme et les contraintes sur le signataire.

TN3127 l'illustre par une mise à jour : la version 1.3 doit satisfaire l'identité enregistrée pour 1.2 malgré des octets différents, tandis qu'un autre produit doit échouer. C'est le comportement attendu d'une autorisation durable.

Affichez la DR ainsi :

codesign -d -r- /Applications/Example.app/Contents/MacOS/Example 2>&1

Une signature Developer ID produit généralement cette forme :

Executable=/Applications/Example.app/Contents/MacOS/Example
designated => anchor apple generic and identifier "dev.example.agent" and certificate leaf[subject.OU] = "A1B2C3D4E5"

Ne parsez pas ce texte pour réécrire l'évaluateur Apple. TN3125 rappelle que les structures changent et recommande codesign ou Code Signing Services. Obtenez l'objet avec SecCodeCopyDesignatedRequirement, puis évaluez-le avec SecCodeCheckValidity ou l'API de processus actuelle.

La DR du code reste une revendication, pas une permission. Le signataire peut en fournir une et le système peut en synthétiser une. Votre service décide laquelle enregistrer et si son périmètre convient.

Pour un logiciel Developer ID ordinaire, enregistrez la DR validée et conservez Team ID et identifiant comme faits lisibles. Évaluez ensuite le processus vivant contre l'objet, sans comparer deux rendus textuels.

Un changement entre Mac App Store et Developer ID peut modifier la DR. TN3127 explique que les valeurs par défaut ne sont pas automatiquement compatibles. Traitez ce passage comme une migration d'identité et demandez une nouvelle approbation, sauf exigence de compatibilité explicitement conçue et testée.

Le chemin fournit du contexte, pas une preuve

Le chemin montre où macOS a trouvé l'image. Il aide l'opérateur, mais les fichiers se déplacent, la translocation change les emplacements et les gestionnaires installent des versions ailleurs. À l'inverse, remplacer un fichier dans un chemin autorisé hérite d'une permission fondée uniquement sur ce chemin.

Une liste de chemins rejette donc le même programme après un déplacement et accepte un autre fichier après remplacement. Propriétaire et permissions réduisent certains risques sans créer une identité cryptographique.

Gardez le chemin pour :

  • Montrer quelle installation a demandé l'accès.
  • Conserver du contexte d'enquête.
  • Ajouter une contrainte d'emplacement après la validation de signature.

Dans un environnement géré, vous pouvez exiger à la fois une DR et un répertoire appartenant à root. Le chemin restreint alors un programme déjà identifié; il ne répare jamais une signature absente.

Observez le chemin lié au processus vivant. Ne croyez pas argv[0], le répertoire courant, une valeur de requête ou une variable d'environnement. Même un chemin canonique peut subir une course entre inspection et utilisation.

Conservez éventuellement chemin observé et chemin résolu dans l'audit, mais pas dans l'identité durable. Placez toute règle dans location_constraint pour la distinguer de l'identité.

Évaluez l'appelant vivant plutôt qu'un chemin

Gardez les secrets hors de l'agent
Le coffre exécute l'action sans exposer les clés API ou SSH au processus.

La décision doit se lier au processus connecté. Inspecter un fichier lors de l'installation laisse une fenêtre de remplacement, et relire le chemin au moment de la demande peut examiner un autre fichier que l'image déjà exécutée.

Code Signing Services distingue le code statique du code dynamique. Apple documente SecCodeCopyGuestWithAttributes pour obtenir un objet de processus et SecCodeCheckValidityWithProcessRequirement pour l'évaluer. Employez l'API prise en charge sur votre cible, sans analyser codesign en production.

Le flux fiable est le suivant :

  1. Tirez l'identité du canal IPC fourni par le noyau, par exemple son jeton d'audit, jamais d'un PID dans le corps.
  2. Résolvez le processus vivant et validez sa signature.
  3. Évaluez l'exigence enregistrée sur ce même objet.
  4. Journalisez chemin, PID, identité de démarrage disponible, faits de signature et résultat.
  5. Liez l'approbation à la connexion ou au processus et supprimez-la à sa fin.

Un PID seul est réutilisable. Un jeton d'audit contient davantage d'identité et la vérification liée à la connexion réduit la course. XPC et les sockets Unix ont des API différentes, mais le sujet doit toujours venir de la vision du noyau.

Validez avant d'afficher. Une carte doit distinguer une « signature Developer ID valide » d'un simple texte d'identifiant. En cas d'échec, ne revenez pas à une correspondance de chemin.

Si un agent signé lance /bin/sh et que le shell se connecte, le shell est le pair. Remonter vers le parent peut prêter son autorité à un enfant substitué. Pour autoriser le lanceur, transmettez une capacité limitée sur un canal contrôlé.

L'approbation de session doit expirer à la sortie du processus. Une identité reconnue peut simplifier une future approbation, mais ne doit pas créer une permission permanente pour tout processus correspondant.

Les builds non signés ont besoin d'une autre politique

Le code non signé n'a pas de DR. Une signature ad hoc produit une DR liée à cette version et une recompilation la change. TN3127 indique que macOS ne peut suivre durablement ces deux catégories.

Pour des secrets de production, refusez un appelant sans signature valide et stable. Les développeurs peuvent signer localement avec Apple Development ou une identité privée gérée par l'organisation.

Si le développement exige un mode faible, rendez-le volontaire et visible. Liez approbation de développement à la vie du processus et au hash exact, affichez non signé ou ad hoc, excluez les secrets de production et redemandez après chaque build.

N'utilisez pas :

  • Un chemin du dossier personnel, remplaçable par l'utilisateur.
  • Un nom lu dans des métadonnées non signées.
  • La signature du parent quand l'enfant se connecte.
  • Une autorisation générale du terminal.
  • Un hash comme identité permanente malgré les recompilations.

Le hash peut limiter une exception temporaire : il signifie « ces octets pour cette exécution », pas « le même programme après mise à jour ».

Traitez un Team ID absent comme une catégorie, pas comme une valeur qui contourne la comparaison. Code Apple, signature indépendante, ad hoc et absence de signature exigent des tests distincts.

Enregistrez une identité dont la portée reste explicite

Vérifiez sans ouvrir le coffre
`sp audit verify` contrôle hors ligne la chaîne de hash sur le texte chiffré.

Le dossier durable doit contenir exigence évaluée, faits lisibles, catégorie, contrainte d'emplacement et portée. Il doit dire si l'approbation vise une exécution, de futures versions ou toute l'équipe.

Exemple, avec un blob d'exigence fourni par Code Signing Services :

{
  "schema": 1,
  "code_category": "developer_id",
  "team_id": "A1B2C3D4E5",
  "signing_identifier": "dev.example.agent",
  "designated_requirement": "BASE64_PLATFORM_REQUIREMENT",
  "approval_scope": "matching_identity_per_session",
  "location_constraint": null,
  "observed_path": "/Applications/Example.app/Contents/MacOS/Example"
}

L'algorithme classe et valide l'appelant, évalue l'exigence, vérifie la cohérence des faits, applique l'emplacement puis la portée et les approbations du secret. Une incohérence signale une corruption ou un bogue, jamais un motif d'élargissement.

Son échec doit être banal et explicite :

caller = peer_from_kernel(connection)
code = dynamic_code(caller)
result = validate(code)

if result.category not in accepted_categories:
    deny("unsupported code category")

identity = evaluate(stored_requirement, code)
if identity != satisfied:
    deny("process identity changed")

if facts(code) != stored_audit_facts:
    deny("identity record inconsistent")

if location_constraint and not location_allowed(code, location_constraint):
    deny("approved program ran from an unapproved location")

authorize_for(connection_lifetime, requested_secret)

Il n'accepte ni Team ID seul, ni chemin avant signature, ni ancêtre plus commode, ni conversion silencieuse d'un appelant non signé. Chaque refus explique sa cause.

Traitez transfert d'équipe, nouvel identifiant, nouveau canal et passage d'ad hoc à Developer ID comme des migrations approuvées. Gardez ancien et nouveau dossiers dans l'audit.

Testez deux identifiants sous la même équipe, le même identifiant sous une autre équipe, un déplacement, un remplacement après lancement et une recompilation ad hoc. Seule l'identité voulue doit passer.

L'identité ne décide pas si l'action est permise

Coupez l'accès avec le processus
L'approbation de session disparaît dès que l'exécution de l'agent se termine.

Une correspondance correcte identifie le sujet, sans lui donner tous les secrets, toutes les destinations ni une durée illimitée. Séparez identité, approbation de session et approbation d'action.

Sinon, une permission pour un jeton de développement peut devenir plus tard un accès à toutes les informations d'authentification. Ni Team ID, ni identifiant, ni DR ne portent la limite de ressource initiale.

Écrivez sujet, action, ressource, conditions et durée :

subject: requirement R42 satisfied by this live process
action: perform an HTTP request with injected bearer credentials
resource: issue-tracker-development
conditions: vault unlocked and session approved
lifetime: this connection, with per-call approval if the secret requires it

Le sujet référence le processus; les autres champs viennent de l'opération et de ses contrôles. Un agent peut ainsi utiliser HTTP et SSH sans que sa reconnaissance autorise automatiquement les deux.

Un entitlement signé n'est pertinent que si la politique lui donne un sens précis. Sa présence ne rend pas un programme sûr, et son absence n'affaiblit pas la paire Team ID et identifiant.

Notarisation et Gatekeeper répondent à d'autres questions. spctl -a -vv -t exec aide à diagnostiquer l'acceptation d'un fichier, sans remplacer l'exigence enregistrée.

Révoquer une session doit fermer la connexion; révoquer une identité doit imposer une future approbation; révoquer une ressource doit préserver les autres. Un booléen trusted ne peut exprimer ces choix.

L'audit peut alors établir quel programme signé a tourné, qui a approuvé cette exécution et quelle opération elle a reçue. Une DR parfaite ne répond pas seule à la question de l'autorité accordée.

L'écran d'approbation doit dire ce qui est prouvé

Affichez signataire ou équipe, identifiant, catégorie, chemin et durée, car un humain ne relit pas un blob. Un nom convivial, issu de métadonnées modifiables, ne doit rester qu'une étiquette secondaire.

La première demande d'un nouveau processus agent dans Sallyport affiche une carte menée par son autorité de signature, puis lie l'approbation à l'exécution jusqu'à sa fin. Le verrou du coffre et l'approbation par appel restent des contrôles séparés.

L'audit conserve catégorie, Team ID, identifiant, référence d'exigence, chemin, processus, session, décision, motif et portée. Une enquête future ne doit pas dépendre du fichier présent aujourd'hui dans l'ancien chemin.

N'appelez pas le résultat processus fiable. La preuve est plus étroite : ce processus vivant a satisfait une exigence et une personne ou une politique lui a accordé une action définie.

Le Team ID nomme le signataire, l'identifiant nomme son programme, la DR préserve la continuité et l'objet dynamique lie la preuve à l'appelant. Si une pièce manque, réduisez l'accès ou redemandez. L'incertitude ne doit jamais devenir une permission permanente.

FAQ

Deux applications macOS peuvent-elles partager un Team ID ?

Oui. Tous les programmes signés par la même équipe peuvent le partager. Ajoutez l'identifiant ou une exigence désignée pour les distinguer.

L'identifiant du bundle est-il celui de la signature ?

Souvent, mais macOS ne l'impose pas. Lisez la valeur dans la signature validée, car le signataire la choisit.

Faut-il garder la DR ou Team ID et identifiant ?

Gardez l'exigence évaluable et les deux champs comme faits lisibles. Évaluez-la avec Code Signing Services, sans comparer son texte.

Une DR prouve-t-elle qu'un processus est sûr ?

Non. Elle prouve seulement une identité. Secrets, actions, durée et approbation restent des décisions séparées.

Le chemin peut-il participer à l'autorisation ?

Oui, comme contrainte supplémentaire après la signature. Seul, il reste remplaçable et instable.

Comment identifier le pair d'une connexion IPC ?

Partez des données du noyau, comme le jeton d'audit, puis validez le code vivant. Ne croyez pas les champs envoyés par le client.

Que devient l'accès après une mise à jour ?

Une DR bien choisie accepte une mise à jour légitime. Un changement hors de son périmètre exige migration ou nouvelle approbation.

Peut-on autoriser durablement un build non signé ?

C'est possible, mais le chemin ne prouve aucune continuité. Préférez une approbation limitée au processus et au build, hors production.

Un enfant doit-il hériter de l'identité du parent ?

Pas automatiquement. L'enfant connecté est le pair. Transmettez une capacité limitée par un canal contrôlé si cette délégation est voulue.

Un hash est-il meilleur qu'un Team ID ?

Il identifie exactement un build et convient à une exception temporaire. Il ne survit pas aux mises à jour, contrairement à une identité signée.

Sallyport

Sallyport exécute les appels d'API et les commandes SSH à la place de votre agent IA. Les clés restent dans un coffre-fort local sur votre Mac ; vous approuvez chaque exécution et chaque action est consignée dans un journal scellé.

© 2026 Sallyport · Open source sous Apache-2.0 · Oleg Sotnikov