← Tous les articles

macOS 27 refuse l'accès aux conteneurs d'une autre équipe sans rien demander

Sur macOS, containerURL(forSecurityApplicationGroupIdentifier:) renvoie une URL d’apparence valide pour un groupe d’applications sur lequel vous ne disposez d’aucune autorisation. Apple le documente sans détour : sous iOS, la méthode renvoie nil pour un identifiant non valide, tandis que sous macOS « une URL de la forme attendue est toujours renvoyée, même si le groupe d’applications n’est pas valide ».4 Dans macOS 27, ce comportement entre en collision avec une nouvelle restriction.

macOS 27 a cessé d’interroger l’utilisateur avant de refuser l’accès aux conteneurs d’une autre équipe. Lire des fichiers dans le conteneur de données ou le conteneur de groupe d’applications d’une autre équipe de développement déclenchait auparavant une demande d’autorisation. L’opération échoue désormais par défaut, et n’est récupérable que si l’utilisateur déniche l’entrée correspondante dans Confidentialité et sécurité.1

Ces deux faits se combinent en un échec dépourvu de tout signal à l’endroit où il se produit. Aucune boîte de dialogue n’apparaît. La méthode renvoie une URL plutôt que nil. Le chemin ressemble exactement à celui que vous attendiez. Le refus ne fait surface que plus tard, au moment de l’opération sur le fichier, où il se lit comme un fichier manquant.

L’essentiel

macOS 27 supprime la demande d’autorisation utilisateur pour l’accès aux conteneurs de données et aux conteneurs de groupe d’applications d’autres équipes, et refuse cet accès par défaut, le contrôle utilisateur étant déplacé vers les réglages Confidentialité et sécurité.1 La modification est classée sous System Integrity Protection comme une nouveauté, non comme une correction de bug. Les recommandations d’Apple sur les conteneurs de groupe d’applications décrivent encore le comportement de demande d’autorisation que macOS 27 a supprimé. Comme l’API macOS renvoie une URL bien formée même pour des groupes auxquels vous n’avez pas accès, le refus apparaît au moment de la lecture et non lors de l’appel à l’API. L’accès au sein d’une même équipe n’est pas affecté : la frontière, c’est le Team ID.

Ce qui a changé

Les notes de version de macOS 27 contiennent une seule phrase sous System Integrity Protection :1

« L’accès aux fichiers situés dans les conteneurs de données et les conteneurs de groupe d’applications d’autres équipes de développement ne demande plus l’autorisation de l’utilisateur ; de tels accès sont refusés par défaut et peuvent être gérés par l’utilisateur dans les réglages Confidentialité et sécurité. »

Radar 161835690. Deux propositions, deux changements distincts.

La première supprime une demande d’autorisation. La seconde érige le refus en comportement par défaut. Les articles consacrés à ce changement mettent généralement le refus en avant, c’est-à-dire la moitié la moins intéressante. Un durcissement des valeurs par défaut, c’est la routine. Une demande d’autorisation qui disparaît, en revanche, change la forme de l’échec que vos utilisateurs subissent, et la forme du rapport de bug que vous recevez.

Avant, l’utilisateur voyait une boîte de dialogue et prenait une décision. S’il refusait, votre application savait qu’un humain avait tranché. Désormais, personne n’est consulté. L’accès n’a tout simplement pas lieu, et le seul moyen de l’activer passe par un panneau de réglages que l’utilisateur n’a aucune raison de visiter, à moins qu’on ne le lui indique.

Le comportement antérieur que la documentation d’Apple décrit toujours

La restriction vient se superposer à une protection apparue deux versions plus tôt. Le guide d’Apple sur les conteneurs de groupe d’applications indique :2

« Dans macOS 15 et versions ultérieures, les conteneurs de groupe d’applications offrent [System Integrity Protection] pour les fichiers locaux de votre application, même si celle-ci ne dispose pas de la capacité App Sandbox. Ces conteneurs de groupe d’applications limitent l’accès des applications qui ne font pas partie du groupe. Les applications extérieures au groupe qui tentent d’accéder à des emplacements situés dans un groupe d’applications ou un conteneur de données d’application déclenchent une demande d’autorisation auprès de l’utilisateur. »

Cette page décrit la demande d’autorisation au présent. À l’heure où ces lignes sont écrites, Apple ne l’a pas mise à jour pour macOS 27.

Conséquence pratique : un développeur qui se heurte au problème, consulte la documentation et tombe sur la page officielle s’entend dire qu’une boîte de dialogue d’autorisation va apparaître. Elle n’apparaîtra pas. Il en conclura alors, non sans logique, que la demande d’autorisation est cassée ou que ses entitlements sont mal configurés — et cherchera au mauvais endroit.

Mieux vaut vérifier vous-même l’état de cette page plutôt que de vous fier à la date de cet article. La documentation finit par rattraper son retard.

Pourquoi l’échec arrive si tard

Le comportement de l’API est ce qui transforme un changement de politique en problème de débogage.

La référence d’Apple pour containerURL(forSecurityApplicationGroupIdentifier:) est explicite sur la différence entre plateformes :4

« Une URL indiquant l’emplacement du répertoire partagé du groupe dans le système de fichiers. Sous iOS, la valeur est nil lorsque l’identifiant de groupe n’est pas valide. Sous macOS, une URL de la forme attendue est toujours renvoyée, même si le groupe d’applications n’est pas valide ; veillez donc à vérifier que vous pouvez accéder au répertoire sous-jacent avant de tenter de l’utiliser. »

La section « discussion » répète l’avertissement : appeler la méthode avec un identifiant de groupe pour lequel vous ne détenez aucune autorisation renvoie malgré tout une URL de la forme attendue, mais le répertoire n’existe pas et une application sandboxée ne peut pas le créer.4

Le réflexe défensif habituel ne sert donc à rien ici :

// This guard passes on macOS regardless of entitlement.
guard let url = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: "ABCDE12345.com.example.shared"
) else {
    return  // never taken on macOS
}

L’URL est bien formée. Elle pointe vers ~/Library/Group Containers/<team>.<group>, c’est-à-dire précisément là où ce conteneur résiderait. Tout se lit comme une réussite, jusqu’à ce qu’une opération sur un fichier soit lancée.

Le correctif consiste à tenter la lecture et à traiter l’échec, plutôt qu’à demander si elle réussirait :

let fm = FileManager.default
guard let url = fm.containerURL(
    forSecurityApplicationGroupIdentifier: groupID
) else { return }  // macOS never takes this branch

do {
    _ = try fm.contentsOfDirectory(atPath: url.path)
    // Reached the container.
} catch {
    // On macOS 27 a cross-team denial lands here.
    // No prompt fired. The user was never asked.
    presentContainerUnavailable(error)
}

Résistez à la tentation de recourir à isReadableFile(atPath:) comme vérification préalable. Apple déconseille toute cette catégorie de tests :3

« Il est déconseillé de conditionner un comportement à l’état actuel du système de fichiers ou d’un fichier particulier. Cela peut provoquer des comportements étranges ou des situations de concurrence. Il vaut bien mieux tenter une opération (charger un fichier ou créer un répertoire, par exemple), vérifier les erreurs et les traiter proprement, plutôt que d’essayer de déterminer à l’avance si l’opération va réussir. »

Une seconde raison tient à ce changement précis. La même page note que isReadableFile(atPath:) « utilise l’identifiant d’utilisateur réel et l’identifiant de groupe réel » pour déterminer si un fichier est lisible.3 Il s’agit d’une évaluation de permissions POSIX. Or le refus d’accès à un conteneur d’une autre équipe est une décision de politique qui se superpose à POSIX : rien ne garantit qu’un contrôle des bits de permission la reflète. Une vérification préalable qui renvoie true avant une lecture qui échoue quand même est pire que pas de vérification du tout, car elle éloigne encore la surprise de sa cause.

Ceux qui ont lu canOpenURL est déprécié dans iOS 27 reconnaîtront la figure. Apple ne cesse de retirer la possibilité de demander à l’avance, en ne laissant que celle d’essayer. Tenter-puis-traiter devient la réponse générale, et non un contournement propre à une API isolée.

Notez l’asymétrie entre les plateformes. iOS renvoie nil et échoue honnêtement au point d’appel. macOS renvoie une URL et diffère l’échec. Le changement de macOS 27 supprime la demande d’autorisation, c’est-à-dire l’unique signal qui subsistait sur celle des deux plateformes qui était déjà la plus silencieuse.

Un dernier piège : Apple recommande de toujours utiliser l’URL renvoyée par cette méthode plutôt que de construire ~/Library/Group Containers/... à la main, car cet emplacement pourrait changer dans de futures versions.4 Quiconque a codé ce chemin en dur n’a aucun appel de méthode à contrôler, ni le moindre endroit où placer le test de lisibilité.

Vraisemblablement pas conditionné au SDK

Une question vient naturellement : rester sur un SDK plus ancien permet-il de repousser le changement ? Les notes de version ne le disent pas.

Ce qu’elles révèlent, en revanche, c’est une constante. Les notes de macOS 27 emploient un vocabulaire explicite de conditionnement au SDK dans 11 entrées distinctes : « Dans les applications compilées avec le SDK de macOS 27.0 », « Dans les applications compilées avec les SDK 27.0 », « Lorsque la cible de déploiement minimale de votre projet est inférieure à 27.0 ».1 Apple assortit ces changements d’une réserve chaque fois que la réserve s’applique.

L’entrée relative aux conteneurs n’en comporte aucune. Si l’on retient l’hypothèse raisonnable que cette absence est délibérée, la restriction relève d’une politique au niveau du système d’exploitation, applicable à tout binaire s’exécutant sur macOS 27, quel que soit le SDK qui l’a compilé.

Considérez cela comme une inférence solide, non comme un fait établi, car Apple ne l’a pas dit explicitement. La conséquence opérationnelle reste la même dans les deux cas : ne comptez pas sur un SDK plus ancien comme mesure d’atténuation. Partez du principe que l’accès va échouer.

Qui est réellement touché

L’accès au sein d’une même équipe n’est pas concerné. Les règles d’Apple font de la frontière du Team ID un élément structurel et non accessoire : « Des équipes de développement différentes ne peuvent pas utiliser le même groupe d’applications », alors qu’une même équipe peut partager un groupe entre ses propres applications et leurs processus auxiliaires.2

Les applications qui cessent de fonctionner sont celles qui franchissent cette ligne :

Les outils de migration et d’import. Tout ce qui lit le conteneur d’un concurrent ou d’un prédécesseur pour importer les données de l’utilisateur. C’est le cas le plus net, et il était déjà soumis à une demande d’autorisation que les utilisateurs approuvaient parfois.

Les utilitaires de sauvegarde et de synchronisation. Les outils qui énumèrent les conteneurs d’applications pour les sauvegarder ignorent désormais en silence tout ce qui se trouve hors de leur propre équipe.

Les applications compagnons qui ont changé de mains. Le cas le plus retors, parce que rien ne change dans le code. Deux applications sont distribuées sous un même Team ID et partagent un groupe sans le moindre problème. Une acquisition, une scission d’équipe ou un basculement vers un compte de développeur distinct les place sous des Team ID différents. L’identifiant du groupe d’applications a toujours l’air correct, l’URL se résout toujours, et les données cessent d’arriver.

Ce dernier cas mérite qu’on s’y attarde, car le calendrier joue contre vous. Les changements de Team ID sont pris en charge par la personne qui gère la signature et la distribution, et la dépendance à un conteneur partagé est généralement invisible depuis ce poste d’observation. Le code compile. Les deux applications sont distribuées. Aucune des deux suites de tests ne détecte quoi que ce soit, car les tests unitaires ne sollicitent pas un véritable conteneur inter-équipes et la CI exécute les deux cibles sous l’identité dont dispose la machine de build. L’échec apparaît en production, sur les machines des utilisateurs, sous la forme de données qui ont cessé de se synchroniser entre deux applications que l’utilisateur considère, à juste titre, comme un seul et même produit.

Si vous préparez une migration de Team ID, l’audit est mécanique : faites un grep sur chaque identifiant de groupe d’applications déclaré par vos cibles et, pour chacun, listez les bundles qui le lisent. Tout identifiant lu par des bundles qui se retrouveront sous des Team ID différents constitue une rupture. C’est une vérification de dix minutes avant l’opération, et un incident de support après.

Apple dresse la liste de ce qui peut participer à un groupe d’applications : les exécutables principaux au sein d’une structure de bundle, les extensions d’application, les App Clips et les XPC Services.2 Chacun est un endroit où le problème peut se manifester.

Diagnostiquer le problème

Deux vérifications méritent d’être menées avant de conclure à un bug dans votre code.

Confirmez que le système a bien validé vos entitlements. Apple documente une vérification à l’exécution de l’indicateur « entitlements validés » sur un processus en cours :2

sudo launchctl procinfo <pid>

Les profils de provisionnement créés avant l’existence de l’autorisation des groupes d’applications peuvent ne pas la porter, ce qui produit des échecs d’accès en tout point identiques au refus de macOS 27, mais d’origine différente. Xcode actualise les profils lorsque « Automatically manage signing » est activé et que le réglage de compilation REGISTER_APP_GROUPS vaut Yes.2

Sachez quel style d’identifiant vous utilisez. Les groupes préfixés par group. doivent figurer dans le profil de provisionnement de l’application. Les groupes de la forme <TeamID>.<group name> ne nécessitent aucun profil, puisque le système vérifie le préfixe d’identifiant d’équipe par rapport à l’identité de signature — mais cette forme est réservée à macOS et incompatible avec les Keychain Access Groups.2

Consultez ensuite Confidentialité et sécurité, puisque c’est là que la note de version situe désormais le contrôle utilisateur.1

Concevoir pour un refus que l’on ne peut pas soumettre à l’utilisateur

La demande d’autorisation faisait un travail de produit, pas seulement un travail de sécurité. Elle signalait à l’utilisateur qu’une décision existait, et elle signalait à votre application qu’une décision avait été prise. Ces deux missions vous incombent désormais.

Sondez à la frontière, une seule fois. Tentez une lecture peu coûteuse au moment où vous résolvez le conteneur pour la première fois, au lieu de découvrir le refus au milieu d’une boucle de synchronisation. Un échec au fond d’une file d’attente produit un résultat partiel et une erreur imputée au fichier qui se trouvait être le suivant. Une tentative unique au moment de la résolution vous offre un seul point de branchement et, contrairement à une vérification préalable des permissions, elle emprunte exactement le même chemin que vos lectures réelles.

Dites ce qui s’est passé, dans les termes de l’utilisateur. « Impossible de lire les données » appelle un ticket de support sur une perte de données. Le message juste nomme la frontière et le remède : ces données appartiennent à une application d’un autre développeur, macOS bloque cet accès par défaut, et le réglage se trouve dans Confidentialité et sécurité. Les utilisateurs ne peuvent pas agir sur un échec qu’ils ne parviennent pas à localiser.

Ne réessayez pas. Un refus est un état de politique, pas une erreur passagère. Les boucles de nouvelle tentative avec temporisation contre un conteneur bloqué vident la batterie et remplissent les journaux sans rien changer. Échouez une fois, exposez l’état et proposez une nouvelle vérification que l’utilisateur peut déclencher après avoir modifié le réglage.

Revérifiez à l’activation plutôt qu’en interrogeant en boucle. Quand l’utilisateur s’absente pour modifier un réglage puis revient, c’est le moment de retester l’accès. Interroger un chemin bloqué à intervalle régulier produit le même refus, à la cadence que vous aurez choisie.

Demandez-vous si vous avez seulement besoin de cette lecture inter-équipes. C’est la question qui dérange, et souvent la bonne réponse. Un outil de migration qui lit le conteneur d’un concurrent fait quelque chose que la plateforme restreint depuis trois versions : sandboxé dans macOS 15, soumis à autorisation, et désormais refusé. Un chemin d’export contrôlé par l’autre application, un import basé sur les documents via un sélecteur de fichiers piloté par l’utilisateur, ou un format d’échange documenté : tous survivent à ce changement. Les lectures de conteneurs par-delà une frontière de Team ID suivent une trajectoire, et cette trajectoire ne va que dans un sens.

Le sélecteur de fichiers mérite une mention particulière, car c’est l’issue de secours que la plateforme prévoit. Un utilisateur qui sélectionne un fichier accorde un accès explicite via le mécanisme de portée de sécurité, ce qui constitue un fondement d’autorisation plus solide que la demande qui vient de disparaître — et cela fonctionne dès aujourd’hui, sans modifier le moindre réglage.

À retenir

Pour les développeurs d’applications macOS : - Ne considérez jamais qu’un retour non nil de containerURL(forSecurityApplicationGroupIdentifier:) prouve l’accès sous macOS. Cette méthode renvoie toujours une URL. Tentez une lecture et traitez l’erreur. - Renoncez à isReadableFile(atPath:) comme vérification préalable. Apple déconseille de prédire l’issue des opérations sur le système de fichiers, et cette méthode évalue les permissions POSIX, qu’un refus décidé au niveau des politiques de sécurité ne modifie pas nécessairement. - Auditez tout chemin de code qui lit un conteneur appartenant à un autre Team ID. Sous macOS 27, il échoue sans rien demander à personne. - Si vous avez codé ~/Library/Group Containers/... en dur, vous n’avez aucun appel de méthode à protéger. Passez à l’API pour disposer d’un endroit où placer la vérification.

Pour les équipes qui distribuent des outils de migration ou de sauvegarde : - Les lectures inter-équipes ne s’obtiennent plus au prix d’une simple demande d’autorisation. Concevez en partant du refus comme état par défaut et indiquez aux utilisateurs où se trouve le réglage. - L’échec se présente comme une absence de données, pas comme une erreur. Ajoutez des messages explicites, faute de quoi les utilisateurs le signaleront comme une perte de données.

Pour quiconque change de Team ID : - Une acquisition ou une séparation de comptes rompt silencieusement le partage de groupe d’applications entre des applications qui partageaient jusque-là un préfixe de Team ID. Rien dans le code source ne le signale.

FAQ

Cela affecte-t-il les applications qui partagent un conteneur au sein d’un même compte de développeur ?

Non. La frontière, c’est le Team ID. Les règles d’Apple empêchent déjà des équipes différentes d’utiliser le même groupe d’applications, et une même équipe peut partager un groupe entre ses propres applications, extensions, App Clips et XPC Services.2

Mes utilisateurs verront-ils une demande d’autorisation qu’ils peuvent approuver ?

Pas sous macOS 27. La note de version indique que ces accès ne donnent plus lieu à une demande d’autorisation et sont refusés par défaut, la gestion étant déplacée vers les réglages Confidentialité et sécurité.1

Puis-je y échapper en compilant avec un SDK plus ancien ?

Probablement pas. Les notes de version assortissent 11 autres changements d’une mention explicite de conditionnement au SDK ; celui-ci n’en comporte aucune, ce qui suggère une politique au niveau du système d’exploitation s’appliquant à tous les binaires.1 Apple ne l’a pas affirmé noir sur blanc : traitez-le donc comme une inférence solide et partez du principe que l’accès échouera.

Comment distinguer ce cas d’un problème de provisionnement ?

Exécutez sudo launchctl procinfo <pid> et vérifiez si le système a bien posé l’indicateur « entitlements validés » sur votre processus. Des profils de provisionnement anciens peuvent être antérieurs à l’autorisation des groupes d’applications et produire un échec d’apparence similaire mais de cause sans rapport.2

Pourquoi la documentation décrit-elle encore une demande d’autorisation ?

Le guide d’Apple sur les conteneurs de groupe d’applications décrit le comportement de macOS 15 et n’avait pas été mis à jour pour le changement de macOS 27 à l’heure où ces lignes sont écrites.2 Vérifiez l’état actuel de cette page plutôt que de vous fier à la date de cet article.

Sources


  1. Apple, « macOS 27 Golden Gate Beta 4 Release Notes ». System Integrity Protection, New Features : « Accessing files in other developer teams’ app data containers and app group containers no longer prompts the user for authorization; such accesses are denied by default and can be managed by the user in Privacy & Security settings. » Radar 161835690. Également la source du vocabulaire de conditionnement au SDK employé dans 11 autres entrées et absent de celle-ci. Le HTML est rendu côté client ; la copie exploitable par une machine se trouve à developer.apple.com/tutorials/data/documentation/macos-release-notes/macos-27-release-notes.json

  2. Apple, « Accessing app group containers in your existing macOS app ». Source du comportement de demande d’autorisation sous macOS 15, de la règle selon laquelle des équipes de développement différentes ne peuvent pas partager un groupe d’applications, de la distinction entre les identifiants group. et <TeamID>.<group name>, de la vérification « entitlements validés » via sudo launchctl procinfo, du réglage de compilation REGISTER_APP_GROUPS et de la liste des participants éligibles à un conteneur. Consulté le 1er août 2026 ; la page décrit toujours la demande d’autorisation. 

  3. Apple, « isReadableFile(atPath:) ». Source de la recommandation contre la prédiction de l’état du système de fichiers : « It’s far better to attempt an operation (such as loading a file or creating a directory), check for errors, and handle those errors gracefully than it is to try to figure out ahead of time whether the operation will succeed. » Également la source de la remarque selon laquelle la méthode « uses the real user ID and group ID, as opposed to the effective user and group IDs, to determine if the file is readable », ce qui explique pourquoi une vérification au niveau POSIX n’est pas un substitut fiable à un refus décidé au niveau des politiques de sécurité. 

  4. Apple, « containerURL(forSecurityApplicationGroupIdentifier:) ». Valeur de retour : « In iOS, the value is nil when the group identifier is invalid. In macOS, a URL of the expected form is always returned, even if the app group is invalid, so be sure to test that you can access the underlying directory before attempting to use it. » Également la source de la recommandation contre la construction manuelle du chemin du conteneur. 

Articles connexes

Menu Item Images Disappear in macOS 27 and iPadOS 27

macOS 27 and iPadOS 27 hide menu item images by default, and what disappears depends on the SDK you linked against. Thre…

13 min de lecture

L'interpréteur de polices d'Apple est désormais en Swift, et 13 % plus rapide

L'équipe de sécurité d'Apple a réécrit l'interpréteur de hinting TrueType du C vers du Swift à sûreté mémoire, l'a rendu…

10 min de lecture

The Mac App Store Won't Let Window Managers Exist. I Shipped One Anyway.

App Sandbox forbids the API every window tiler needs, and Apple said ship outside the store. 941 Tiles ships inside it: …

8 min de lecture