Comment vérifier un helper SSH avant son lancement
Apprenez à vérifier un helper SSH sur macOS : emplacement dans le bundle, signature, propriétaire, identité du fichier et exigence au lancement.

Une passerelle locale doit traiter son helper SSH comme du code privilégié, même si ce helper ne contient aucun coffre et ne conserve aucun état. Il reçoit une commande, hérite de descripteurs de fichiers et de l'environnement, et peut devenir le processus qui communique avec l'hôte distant. Si un attaquant peut remplacer cet exécutable, toutes les approbations accordées en amont perdent leur effet.
Une conception sûre remplit deux fonctions distinctes. Les contrôles préalables détectent un bundle endommagé ou mal installé avant qu'une requête atteigne l'exécuteur. Une exigence de code appliquée au lancement demande à macOS de refuser l'image réelle du processus si son identité de signature ne correspond pas. Confondre ces fonctions crée la faille classique où l'application vérifie un fichier et en exécute un autre.
Déduisez le helper du bundle en cours d'exécution
Trouvez le helper à partir du bundle de l'application réellement en cours d'exécution, jamais à partir de PATH, du répertoire courant, d'une préférence ou d'un chemin fourni par l'agent. L'emplacement attendu doit être une constante définie à la compilation. Une passerelle qui recherche sp-ssh a déjà laissé son environnement choisir du code exécutable.
Apple documente des emplacements standard pour le code imbriqué, notamment Contents/MacOS et Contents/Helpers. Choisissez-en un, n'y placez que du code et faites échouer la compilation si le helper arrive ailleurs. Signez d'abord le helper, puis l'application externe en dernier. Cet ordre permet à la signature externe de sceller la référence au code imbriqué.
Foundation peut localiser un exécutable auxiliaire, mais la décision de sécurité doit encore vérifier sa relation exacte avec le bundle principal. Résolvez les liens symboliques, normalisez les deux URL, puis comparez leurs composants plutôt que des préfixes textuels. Un test tel que candidate.path.hasPrefix(bundle.path) accepte des voisins comme /Applications/Good.app.backup et peut mal réagir à la casse ou à la normalisation. Le candidat doit être exactement l'unique URL attendue dans le bundle actif.
N'acceptez pas comme solution de secours un helper copié à côté de l'application. Cette facilité séduit en développement parce qu'elle masque un mauvais empaquetage. En production, elle déplace silencieusement la limite de confiance du contenu signé vers tout fichier présent sur un chemin voisin. Les builds de développement doivent utiliser une configuration explicite et échouer clairement si l'empaquetage est incorrect.
L'emplacement renseigne sur l'empaquetage, pas sur l'identité. Un attaquant capable de remplacer un fichier dans un bundle modifiable peut conserver le même chemin. Les contrôles suivants examinent donc l'objet ouvert et sa signature.
Ouvrez d'abord, puis inspectez l'identité du fichier
Ouvrez le candidat avec O_NOFOLLOW, gardez le descripteur ouvert et appelez fstat sur ce descripteur. L'ordre compte. Appeler lstat, vérifier son résultat, puis appeler open permet à un autre processus de remplacer l'entrée de répertoire entre les deux opérations.
Le Secure Coding Guide d'Apple recommande les opérations fondées sur des descripteurs pour cette raison. Il demande notamment de vérifier type, UID, GID, mode et nombre de liens après l'ouverture. Pour un helper exécutable, j'utilise un contrôle préalable de cette forme :
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
int inspect_helper(const char *path, uid_t expected_uid, struct stat *snapshot) {
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0) return -1;
struct stat st;
if (fstat(fd, &st) != 0 ||
!S_ISREG(st.st_mode) ||
st.st_uid != expected_uid ||
(st.st_mode & (S_IWGRP | S_IWOTH)) != 0 ||
(st.st_mode & S_IXUSR) == 0 ||
st.st_nlink != 1) {
int saved = errno ? errno : EPERM;
close(fd);
errno = saved;
return -1;
}
*snapshot = st;
return fd;
}
expected_uid vient du modèle d'installation, pas du fichier. Une installation administrée par le système peut exiger root comme propriétaire. Une application installée par utilisateur peut légitimement appartenir à cet utilisateur. N'imposez pas l'UID 0 uniquement parce que root semble digne de confiance. Apple avertit aussi qu'un chemin peut traverser un autre système de fichiers monté, où la propriété seule en dit moins qu'on ne le suppose souvent.
La règle du nombre de liens mérite une décision consciente. Un exécutable ordinaire inclus dans un bundle ne possède normalement qu'un lien physique, donc refuser toute autre valeur est raisonnable. Si votre empaquetage crée volontairement des liens physiques, documentez-le et testez la valeur attendue. Supprimer le contrôle parce qu'un build vous a surpris laisse un autre nom par lequel le même inode peut être modifié.
Conservez le descripteur et l'instantané stat jusqu'à la fin du lancement. Ils ne rendent pas atomique un lancement fondé sur un chemin, mais ils permettent de détecter un remplacement, de consigner le périphérique et l'inode, et d'expliquer un refus sans rouvrir un nom contrôlé par l'attaquant.
Vérifiez chaque répertoire modifiable de la chaîne
Un mode parfait sur le helper ne le protège pas si son répertoire parent peut être renommé ou modifié. L'attaquant n'a pas besoin de toucher aux octets de l'exécutable s'il peut remplacer l'entrée de répertoire qui les désigne.
Remontez du parent du helper à la racine du bundle avec des descripteurs de répertoires. Pour chaque composant, refusez les liens symboliques, confirmez qu'il s'agit d'un répertoire, notez son périphérique et son inode, puis appliquez les règles de propriété et d'écriture de votre modèle d'installation. openat et fstatat avec un descripteur parent sont préférables à des résolutions répétées de chaînes absolues. Le parcours doit s'arrêter à la racine déjà vérifiée, pas à un chemin qui porte simplement un suffixe familier.
Les installations par utilisateur exigent ici un discours honnête. Si l'utilisateur qui exécute la passerelle possède un bundle modifiable, un autre processus de ce compte peut remplacer ses fichiers. Les contrôles de mode et de propriétaire révèlent une exposition accidentelle à d'autres comptes, mais ne protègent pas contre la compromission complète du compte courant. L'identité de signature reste utile, car un attaquant ne satisfait pas votre exigence désignée en ajoutant simplement une signature ad hoc.
Comparez aussi les identifiants de périphérique si votre politique exige un bundle local sur un seul volume. Apple rappelle qu'un chemin peut franchir un point de montage. Refuser une frontière inattendue est utile, sans prouver que le volume est fiable. Cela prouve uniquement que l'objet n'est pas là où le contrat d'empaquetage l'annonçait.
Ces contrôles détectent des défauts de déploiement très ordinaires : un programme de mise à jour qui laisse un répertoire temporaire modifiable par le groupe, un helper restauré hors de l'application signée ou un lien symbolique créé par un script de développement. Ils doivent arrêter l'exécuteur avant qu'il reçoive une commande. Continuer après un avertissement transforme une erreur d'empaquetage en logique de sélection d'exécutable.
Vérifiez l'identité, pas seulement la validité
Une signature valide indique si le code reste cohérent avec cette signature. Elle ne dit pas que votre équipe l'a signé ni que l'exécutable est votre helper. Sur macOS, du code peut être signé ad hoc, et un autre développeur peut produire du code dont la signature est parfaitement valide.
Utilisez Code Signing Services avec une exigence explicite qui nomme l'identifiant de signature et l'équipe attendus pour le canal de distribution. Apple TN3127 clarifie la distinction : l'identifiant est un nom choisi par le signataire, l'identité de signature contient certificat et clé privée, et l'exigence désignée exprime ce qui compte comme le même code entre les versions. Comparer la chaîne Authority affichée est un raccourci de diagnostic, pas une frontière de sécurité.
Créez un SecStaticCode pour l'URL absolue du helper, compilez ou chargez l'exigence, puis appelez SecStaticCodeCheckValidityWithErrors. Ajoutez kSecCSStrictValidate et kSecCSCheckAllArchitectures. Apple indique que le comportement par défaut peut ne valider que l'architecture native d'un binaire universel. Tout vérifier évite qu'une architecture non testée porte une signature différente ou cassée.
N'utilisez pas kSecCSBasicValidateOnly ici. Ce drapeau saute la validation de l'exécutable principal et des ressources, ce qui annule le contrôle d'intégrité. Ne vous fiez pas non plus à l'exigence désignée du helper sans la comparer à une exigence contrôlée par la passerelle. Se décrire soi-même n'est pas une autorisation.
L'application externe mérite aussi une validation. Apple prévoit des emplacements de code imbriqué afin que le système de signature traite les helpers comme du code. Une version correctement signée possède une chaîne d'intention : le helper satisfait sa propre exigence attendue et le sceau de l'application enregistre le composant imbriqué. Vérifier les deux détecte un helper valide transplanté dans un bundle endommagé.
Conservez l'erreur CFError détaillée en interne et mappez-la vers quelques catégories de refus : signature absente, exigence non satisfaite, ressource invalide, architecture non prise en charge ou fichier modifié. Ne transformez jamais une erreur en nouvelle tentative sur un autre chemin. Un échec de signature signifie que l'exécuteur n'est pas disponible.
Un contrôle statique ne ferme pas la course
La validation statique n'est valable que tant que le fichier ne change pas. Apple le dit directement dans la documentation de SecStaticCodeCheckValidity et cite les systèmes de fichiers dynamiques de réseau, union et FUSE. L'avertissement vaut aussi pour une entrée de répertoire ordinaire remplaçable : après la validation, un processus peut renommer un autre exécutable sur le chemin avant que posix_spawn le résolve.
L'échec tient en quatre mouvements :
- La passerelle résout
/Applications/Example.app/Contents/Helpers/runneret valide le fichier A. - Un processus concurrent renomme le fichier B sur ce chemin.
- La passerelle demande à
Processouposix_spawnd'exécuter le chemin. - Le noyau ouvre B, car l'appel reçoit un nom et non le descripteur conservé pour A.
Comparer stat avant et après la validation réduit la fenêtre et détecte de nombreuses tentatives, sans rendre les étapes deux et trois indivisibles. Calculer son propre hash a la même limite et répète le travail de la signature. Un fichier de verrouillage ne coordonne que les processus qui acceptent de le respecter. Un attaquant ne le fera pas.
Un conseil fréquent consiste à valider une fois au démarrage et à mettre le succès en cache. Il plaît parce que la vérification a un coût et que le code du bundle paraît immuable pendant un usage normal. Il est faux pour une passerelle. Mises à jour, restaurations, changements de volume et remplacements volontaires peuvent se produire pendant qu'une application de barre de menus reste ouverte. Mettez en cache l'objet d'exigence compilé si nécessaire, comme le suggère Apple, mais réévaluez l'exécutable à chaque lancement.
Un descripteur ouvert reste utile. Gardez-le pendant la validation, reprenez un fstat juste avant le lancement et refusez tout changement de périphérique, inode, taille, date de modification ou changement de métadonnées. Prenez un troisième instantané après la création pour la télémétrie. Cela améliore le diagnostic et augmente l'effort d'attaque, mais l'affirmation doit rester exacte : seule une exigence appliquée par le système au lancement lie l'identité à la création du processus.
Liez l'exigence à la création du processus
Sur les systèmes qui proposent LightweightCodeRequirements, définissez Process.launchRequirement avant d'appeler run. Apple précise que si l'exécutable ne satisfait pas le LaunchCodeRequirement, le système ne lance pas le processus et crée un rapport de panne. Le contrôle décisif intervient dans la création, lorsque le chemin sélectionné ne peut plus dériver de la décision.
La configuration Swift principale est courte :
import Foundation
import LightweightCodeRequirements
func configuredProcess(helper: URL, team: String, identifier: String) throws -> Process {
let requirement = try LaunchCodeRequirement.allOf {
ValidationCategory(.developerID)
TeamIdentifier(team)
SigningIdentifier(identifier)
}
let process = Process()
process.executableURL = helper
process.launchRequirement = requirement
return process
}
L'équipe et l'identifiant doivent venir de la configuration de publication compilée dans la passerelle signée. Ne les chargez pas depuis des préférences placées près du helper. Si vous publiez par plusieurs canaux, créez et testez une exigence explicite pour chacun au lieu d'affaiblir une expression jusqu'à ce que tous les builds passent.
Appliquez l'exigence à un helper Mach-O, pas à un script shell. Apple indique que, pour un script avec shebang, l'exigence évalue l'interpréteur. Prouver que /bin/bash est du code Apple ne dit rien sur les octets du script à approuver. Placez la logique dans un exécutable signé, ou traitez le script comme des données scellées lues par du code fiable sans le lancer comme exécuteur privilégié.
Activez l'API selon sa disponibilité dans le SDK et gardez le contrôle statique pour le diagnostic. Sur d'anciennes cibles, POSIX_SPAWN_START_SUSPENDED peut créer l'enfant suspendu avant l'exécution en espace utilisateur. Obtenez alors un SecCode dynamique par PID, validez-le, puis reprenez ou tuez le processus. Ce repli est délicat : vérifiez chaque retour, évitez toute confusion de PID, fermez les descripteurs inattendus et n'envoyez aucune commande ni aucun secret avant le succès. Si votre modèle de menace refuse cette complexité, exigez un système qui prend en charge les exigences au lancement.
Lancez avec un contrat de processus étroit
Vérifier le binaire n'assainit pas ce que vous lui donnez. Construisez les arguments à partir de champs structurés, fixez un environnement explicite, choisissez le répertoire de travail et ne transmettez que les descripteurs nécessaires. N'invoquez jamais un shell pour composer une commande SSH.
Partez d'un environnement vide ou d'une liste autorisée. Les variables qui influencent le chargement dynamique, la découverte de configuration, la langue, les proxies ou le dossier personnel peuvent changer le comportement d'un programme signé sans changer son code. Hardened Runtime et Library Validation réduisent certaines attaques, mais ne rendent pas sûre n'importe quelle configuration héritée.
Traitez l'entrée et la sortie standard comme un protocole. Définissez des tailles maximales, refusez les champs en trop, fixez une échéance et distinguez une erreur de protocole d'un code de sortie SSH. Un helper sans état ne doit pas lire de clés dans l'environnement, les arguments, un fichier temporaire ou un chemin fourni par l'agent. Il doit recevoir la requête minimale sur un canal contrôlé et renvoyer le résultat minimal.
Fermez tous les descripteurs sans rapport. O_CLOEXEC aide pour les fichiers de précontrôle, mais auditez aussi le reste. Un enfant qui hérite du descripteur de la base du coffre, d'un listener IPC ou d'un journal obtient un accès que la vérification ne devait jamais accorder. Fixez des limites de ressources quand le contrat le permet et terminez tout le groupe de processus en cas d'annulation ou d'expiration.
Consignez les entrées de décision sans secrets : chemin relatif au bundle, version de l'exigence, instantanés de périphérique et d'inode, résultat, PID de l'enfant et raison de fin. La trace doit dire quel lancement a été autorisé. Un simple chemin textuel perd le fait qu'un nom a pu désigner plusieurs fichiers pendant un incident.
Placez la vérification dans l'autorisation
L'acceptation du helper appartient à la même transaction que l'autorisation de l'action et doit précéder toute capacité remise à l'enfant. Vérifier à l'ouverture de l'application est trop tôt. Vérifier après l'approbation humaine est trop tard si l'échec peut divulguer la commande, ouvrir un socket ou déclencher un repli.
Modélisez un lancement comme une machine à états aux frontières explicites et irréversibles. La passerelle reçoit et analyse l'action quand la requête n'a encore aucun accès aux identifiants. Elle confirme la disponibilité du coffre, résout et inspecte le helper, obtient l'autorisation humaine, puis lance avec l'exigence attachée. Elle ne crée le canal minimal de secret ou de connexion qu'après le succès. Tout échec antérieur détruit l'action en attente.
La place de l'approbation dépend de son sens. Si la carte demande si un agent peut exécuter une commande SSH précise, elle peut apparaître avant le lancement coûteux. La commande approuvée doit ensuite rester immuable et un échec de vérification doit consommer ou annuler l'approbation, jamais la réserver à un autre binaire. Si l'approbation exprime la confiance dans le processus exécutant, ne l'affichez qu'après avoir identifié le candidat. Les deux modèles sont possibles, mais la trace doit relier résumé de requête, décision, exigence et processus en une tentative.
N'exposez pas un secret au seul motif que la création a réussi. Préparez des pipes ou sockets avant si l'API l'exige, mais gardez leur extrémité sensible fermée ou bloquée. Attendez le succès de l'exigence, notez l'identité, puis envoyez la requête. Si l'enfant a besoin d'une opération de clé SSH, préférez une interface étroite de signature ou de connexion à la copie des octets privés dans sa mémoire. Moins il reçoit d'autorité, moins une erreur antérieure coûte cher.
L'annulation exige le même soin. Un utilisateur peut révoquer une session pendant la validation, ou l'agent se déconnecter entre l'approbation et le lancement. Revérifiez la génération d'autorisation juste avant de lancer, puis avant de libérer la requête. Si elle change, terminez l'enfant et fermez les canaux. Ce n'est pas une course du système de fichiers, mais c'est un autre intervalle entre contrôle et usage de la même décision.
Des lancements concurrents ne doivent pas partager de configuration mutable. Chaque tentative possède son URL, son exigence, ses arguments, son environnement, ses descripteurs, son échéance et son identifiant d'audit immuables. Un modèle global Process modifié par un thread pendant qu'un autre appelle run peut envoyer une demande au mauvais exécutable ou environnement même si les deux binaires sont valides. Synchronisez la courte transition qui consomme l'approbation et démarre le processus, pas toute la durée des connexions.
Un invariant utile veut qu'aucun octet contrôlé par l'enfant n'entre dans l'état privilégié avant l'application de l'identité. Le helper peut écrire dans un pipe dès son démarrage : laissez l'analyseur détaché jusqu'à l'acceptation, limitez l'exposition du tampon et traitez une sortie précoce comme une violation. N'utilisez pas non plus le texte d'erreur d'un enfant non vérifié pour construire un chemin privilégié, choisir un secret ou sélectionner un autre binaire.
Cet ordre produit un journal plus clair. Une tentative peut montrer request_received, candidate_preflight_passed, authorization_granted, launch_requirement_passed, request_released, puis son résultat. Les transitions absentes deviennent visibles. Une trace qui passe directement de l'approbation au code de sortie ne permet pas de savoir si l'exécuteur attendu a reçu la commande.
Vérifiez l'enfant réellement lancé
L'exigence au lancement doit être la barrière décisive, mais l'observation ultérieure détecte les erreurs d'intégration et fournit une identité stable. Une fois run réussi, recueillez le PID et obtenez un SecCode dynamique du processus. Vérifiez-le avec l'exigence correspondante avant de connecter l'analyseur ou de libérer des données sensibles.
La distinction d'Apple entre SecStaticCode et SecCode est utile. Un objet statique décrit le code sur disque et n'est pas intrinsèquement lié au code en exécution. Un objet dynamique représente le code chargé dans un processus. Les questions diffèrent : le contrôle statique explique l'intégrité du candidat installé, le contrôle dynamique confirme l'identité attribuée par macOS à l'enfant actuel.
Une recherche par PID seul présente des pièges. Les PID sont réutilisés et un enfant bref peut sortir entre run, la recherche et la validation. L'impossibilité d'obtenir ou de valider l'objet doit compter comme un échec. Quand une API IPC fournit un jeton d'audit, utilisez cette référence plus forte plutôt qu'un PID annoncé dans un message. Ne faites jamais confiance au PID, à l'identifiant ou au chemin déclarés par l'enfant.
Le contrôle après lancement ne doit pas devenir le seul contrôle sur une plateforme où l'enfant reçoit du temps CPU auparavant. Un substitut peut agir entre le spawn et l'inspection. Démarrer suspendu avant le code utilisateur réduit l'intervalle sur les anciens systèmes, mais l'implémentation ne doit reprendre que le PID validé et doit le tuer sur toute erreur. L'exigence de lancement est plus simple, car le système refuse l'écart avant l'exécution.
Après acceptation, gardez un handle du processus et liez chaque message à cette instance. Ne rouvrez pas un socket nommé et ne vous reconnectez pas à un endpoint qu'un autre processus pourrait occuper. À la sortie, fermez les canaux, invalidez l'action et exigez un nouveau lancement vérifié. Un premier enfant valide n'autorise pas un autre processus qui réutilise son PID ou son nom.
Consignez les preuves installées et actives. Pour l'objet installé : chemin relatif, périphérique, inode, taille, dates et résultat statique. Pour le processus : PID, révision de l'exigence, résultat au lancement, validation dynamique, heure de départ et statut final. Ces valeurs ne sont pas secrètes et distinguent version corrompue, collision de mise à jour, erreur de configuration et substitution sans exposer commandes ni identifiants.
L'identité du processus exige aussi une règle de durée. Si le helper exécute un autre programme, l'identité validée ne se transfère pas à la nouvelle image. Évitez un wrapper signé qui passe le contrôle puis appelle un ssh arbitraire via PATH. Si une transition exec fait partie du modèle, l'exécutable final doit avoir son propre chemin fixe et sa propre exigence, ou le helper fiable doit effectuer le protocole lui-même. La signature du lanceur ne prouve rien sur le programme choisi ensuite.
Surveillez aussi bibliothèques et configuration. L'exécutable principal peut satisfaire l'exigence tandis que des variables dangereuses ou des chemins modifiables changent son comportement. Signez les dépendances, activez les options Hardened Runtime adaptées, utilisez Library Validation quand c'est compatible et retirez les chemins de recherche de l'environnement. La configuration doit être une entrée validée par la passerelle, pas des fichiers découverts par l'enfant dans le dossier personnel.
Enfin, testez les affirmations de la télémétrie. Dans un test de substitution, l'instantané doit identifier A, l'exigence doit lancer A ou refuser B, et le contrôle dynamique doit concorder. Si le journal peut annoncer que A a réussi alors qu'un B non enregistré a tourné, le modèle de preuve suit encore le nom plutôt que le processus.
Refusez par défaut sans bloquer les mises à jour
Toute erreur de vérification doit arrêter cet exécuteur et laisser le reste de l'application dans un état compréhensible. Ne basculez pas vers le ssh système, ne cherchez pas ailleurs, ne retirez pas l'exigence et ne demandez pas à l'utilisateur d'approuver un helper non identifié. Une approbation ne répare pas l'identité d'un exécutable.
Les mises à jour ont besoin d'une transition séparée. Cessez d'accepter de nouvelles tâches SSH, laissez finir les enfants actifs ou terminez-les, installez le bundle signé complet par remplacement atomique, puis répétez tous les contrôles. Un helper et une application de versions différentes peuvent chacun être valides tout en rompant leur contrat de protocole. Ajoutez un échange de version après le lancement et refusez les différences avant d'envoyer l'action.
Décidez quels changements exigent une récupération visible. Un helper absent après une mise à jour partielle demande une réinstallation. Une équipe ou un identifiant différent peut signaler une altération et mérite un message plus ferme. Un mode différent peut venir d'un outil de sauvegarde. Gardez l'erreur exacte en interne et proposez une action concise qui n'apprend pas à contourner le contrôle.
Testez les remplacements, pas seulement les signatures valides. La suite de publication doit couvrir un lien symbolique au chemin du helper, un second lien physique, des parents modifiables par le groupe, un substitut signé ad hoc, un binaire correctement signé sous le mauvais identifiant, une architecture non native endommagée, le remplacement entre contrôle et lancement, et une mise à jour pendant l'exécution. Un point de test qui suspend après la validation rend la course reproductible.
Sallyport utilise un helper Go intégré et sans état pour son canal SSH, tandis que le cœur du coffre reste dans l'application signée de la barre de menus. Cette séparation tient les secrets hors du helper, sans rendre sa vérification facultative : la passerelle doit toujours prouver quel exécuteur démarre avant de lui remettre une action approuvée.
La règle d'acceptation doit tenir sur une ligne de revue : le fichier attendu dans le bundle actif passe les contrôles de descripteur et de répertoires, satisfait l'exigence de signature de publication, et la création du processus impose cette même identité. Si la plateforme ne peut pas imposer le dernier point, documentez la garantie plus faible et réduisez ce que l'enfant peut recevoir.
FAQ
Pourquoi la vérification du chemin du helper SSH ne suffit-elle pas ?
Un chemin est un nom, pas une identité stable. Un autre processus peut remplacer son entrée après le contrôle, donc la passerelle doit inspecter le fichier ouvert et imposer une exigence au lancement.
Une application macOS doit-elle chercher son helper dans PATH ?
Non. PATH laisse l'environnement choisir le code et fait passer un mauvais empaquetage pour une découverte réussie. Déduisez une URL exacte du bundle actif et refusez tout repli.
Quelles métadonnées de fichier faut-il vérifier ?
Après l'ouverture avec O_NOFOLLOW, utilisez fstat pour confirmer type, propriétaire, droits d'écriture et nombre de liens. Conservez périphérique et inode pour les comparaisons et la trace.
Une signature valide prouve-t-elle que le helper est le mien ?
Non. Elle prouve la cohérence avec une signature, et macOS peut exécuter du code d'autres développeurs ou signé ad hoc. Appliquez une exigence pour l'identifiant, l'équipe et la catégorie attendus.
Pourquoi valider toutes les architectures d'un helper universel ?
Apple indique que la validation statique ne vérifie souvent que l'architecture native. kSecCSCheckAllArchitectures inspecte chaque tranche, y compris celle qu'une autre machine pourrait exécuter.
Un descripteur ouvert supprime-t-il la course au remplacement ?
Il stabilise le fichier inspecté, mais Process et posix_spawn choisissent encore par chemin. Utilisez-le pour les instantanés, puis liez l'identité au processus avec une exigence au lancement.
Qu'apporte Process.launchRequirement ?
Il demande au système d'évaluer un LaunchCodeRequirement pendant le lancement. En cas d'échec, le processus ne s'exécute pas, ce qui ferme l'intervalle laissé par un contrôle statique.
Propriété et permissions restent-elles utiles avec la signature ?
Oui, elles détectent des installations dangereuses et réduisent les possibilités de remplacement. Elles complètent l'identité de signature et l'application au lancement sans les remplacer.
Faut-il mettre en cache une vérification réussie ?
Vous pouvez conserver l'exigence compilée si les mesures le justifient, mais validez à chaque lancement. Une application longue durée peut traverser une mise à jour, une restauration ou un remplacement.
Comment réagir à un échec de vérification ?
Refusez cet exécuteur, consignez la raison exacte et proposez une récupération adaptée, comme réinstaller une application endommagée. Ne cherchez jamais un autre binaire SSH et n'affaiblissez pas l'exigence.