← Todos los articulos

macOS 27 deniega el acceso a contenedores de otro equipo sin preguntar

En macOS, containerURL(forSecurityApplicationGroupIdentifier:) devuelve una URL de aspecto válido para un app group sobre el que no tienes ningún entitlement. Apple lo documenta sin rodeos: en iOS el método devuelve nil cuando el identificador no es válido, pero en macOS “siempre se devuelve una URL con la forma esperada, incluso si el app group no es válido”.4 En macOS 27, ese comportamiento choca con una restricción nueva.

macOS 27 dejó de preguntarle al usuario antes de denegar el acceso a contenedores de otro equipo. Leer archivos en el contenedor de datos o en el contenedor de app group de otro equipo de desarrollo antes generaba un diálogo de autorización. Ahora falla de forma predeterminada y solo se recupera si el usuario encuentra la entrada correspondiente en Privacidad y seguridad.1

Esos dos hechos se combinan en una falla que no emite ninguna señal en el punto donde ocurre. No aparece ningún diálogo. El método devuelve una URL en lugar de nil. La ruta se ve exactamente como la esperabas. La denegación sale a la superficie más tarde, en la operación de archivo, donde se lee como un archivo que no existe.

TL;DR

macOS 27 elimina el diálogo de autorización del usuario para acceder a contenedores de datos y contenedores de app group de otros equipos, y deniega ese acceso de forma predeterminada, con el control trasladado a la configuración de Privacidad y seguridad.1 El cambio está catalogado bajo System Integrity Protection como función nueva, no como corrección de errores. La propia guía de Apple sobre contenedores de app group sigue describiendo el comportamiento del diálogo que macOS 27 eliminó. Como la API de macOS devuelve una URL bien formada incluso para grupos a los que no puedes acceder, la denegación aparece al momento de la lectura y no en la llamada a la API. El acceso dentro del mismo equipo no se ve afectado: el límite es el Team ID.

Qué cambió

Las notas de la versión de macOS 27 traen una sola frase bajo System Integrity Protection:1

“Acceder a archivos en contenedores de datos y contenedores de app group de otros equipos de desarrollo ya no le pide autorización al usuario; esos accesos se deniegan de forma predeterminada y el usuario puede gestionarlos en la configuración de Privacidad y seguridad.”

Radar 161835690. Dos cláusulas, dos cambios distintos.

La primera elimina un diálogo. La segunda establece la denegación como estado predeterminado. La cobertura de este cambio suele empezar por la denegación, que es la mitad menos interesante. Endurecer los valores predeterminados es rutina. Un diálogo que desaparece, en cambio, modifica la forma de la falla que viven tus usuarios y la forma del reporte de error que te llega.

Antes, el usuario veía un diálogo y tomaba una decisión. Si la rechazaba, tu app sabía algo que una persona había elegido. Ahora no se le pregunta a nadie. El acceso simplemente no ocurre, y el único camino para habilitarlo pasa por un panel de configuración que el usuario no tiene motivo alguno para visitar, a menos que alguien se lo indique.

El comportamiento anterior que la documentación de Apple sigue describiendo

La restricción se apoya sobre una protección que llegó dos versiones antes. La guía de Apple sobre contenedores de app group dice:2

“En macOS 15 y posteriores, los contenedores de app group ofrecen [System Integrity Protection] para los archivos locales de tu app, incluso si la app no tiene la capacidad de entorno aislado. Estos contenedores de app group limitan el acceso de las apps que no pertenecen al app group. Las apps que no están en el app group y que intentan acceder a ubicaciones dentro de un app group o de un contenedor de datos de la app provocan un diálogo que le pide autorización al usuario.”

Esa página describe el diálogo en presente. Al momento de escribir esto, Apple no la ha actualizado para macOS 27.

La consecuencia práctica: quien tropiece con esto, busque en la documentación y llegue a la página oficial leerá que aparecerá un diálogo de autorización. No aparecerá. De ahí concluirá, con toda razón, que el diálogo está roto o que sus entitlements están mal configurados, y perderá tiempo en el lugar equivocado.

Vale la pena que verifiques el estado de esa página por tu cuenta en lugar de confiar en la fecha de este artículo. La documentación termina poniéndose al día.

Por qué la falla aparece tarde

El comportamiento de la API es lo que convierte un cambio de política en un problema de depuración.

La referencia de Apple para containerURL(forSecurityApplicationGroupIdentifier:) es explícita sobre la diferencia entre plataformas:4

“Una URL que indica la ubicación del directorio compartido del grupo en el sistema de archivos. En iOS, el valor es nil cuando el identificador de grupo no es válido. En macOS siempre se devuelve una URL con la forma esperada, incluso si el app group no es válido, así que asegúrate de comprobar que puedes acceder al directorio subyacente antes de intentar usarlo.”

La sección de discusión repite la advertencia: llamar al método con un identificador de grupo para el que no tienes entitlement devuelve igual una URL con la forma esperada, pero el directorio no existe y una app en un entorno aislado no puede crearlo.4

Por eso el patrón defensivo habitual no sirve de nada aquí:

// 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
}

La URL está bien formada. Apunta a ~/Library/Group Containers/<team>.<group>, que es exactamente donde viviría ese contenedor. Todo se lee como éxito hasta que se ejecuta una operación de archivo sobre ella.

La solución es intentar la lectura y manejar la falla, en vez de preguntar si tendría éxito:

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)
}

Resiste la tentación de recurrir a isReadableFile(atPath:) como comprobación previa. Apple desaconseja toda esa clase de pruebas:3

“No se recomienda condicionar el comportamiento al estado actual del sistema de archivos o de un archivo en particular. Hacerlo puede provocar comportamientos extraños o condiciones de carrera. Es mucho mejor intentar una operación (como cargar un archivo o crear un directorio), comprobar si hay errores y manejarlos con elegancia, que tratar de averiguar de antemano si la operación va a tener éxito.”

Hay una segunda razón, específica de este cambio. La misma página señala que isReadableFile(atPath:) “usa el ID de usuario y el ID de grupo reales” para decidir si un archivo es legible.3 Eso es evaluación de permisos POSIX. La denegación de un contenedor de otro equipo es una decisión de política que vive por encima de POSIX, así que una comprobación de bits de permiso no necesariamente la refleja. Una comprobación previa que devuelve true antes de una lectura que falla igual es peor que no tener ninguna, porque aleja la sorpresa todavía más de su causa.

Quien haya leído canOpenURL queda obsoleto en iOS 27 reconocerá la forma. Apple sigue quitando la capacidad de preguntar por adelantado y dejándote la de intentar. Intentar y manejar el error se está volviendo la respuesta general, no un rodeo para una sola API.

Fíjate en la asimetría entre plataformas. iOS devuelve nil y falla con honestidad en el punto de la llamada. macOS devuelve una URL y aplaza la falla. El cambio de macOS 27 elimina el diálogo: era la única señal que quedaba en la plataforma que ya era la más silenciosa de las dos.

Una trampa más: la guía de Apple indica que uses siempre la URL que devuelve este método en lugar de construir ~/Library/Group Containers/... a mano, porque la ubicación puede cambiar en versiones futuras.4 Quien haya fijado esa ruta directamente en el código no tiene ninguna llamada a método que comprobar, ni dónde poner la prueba de lectura.

Probablemente no dependa del SDK

Una pregunta natural es si quedarte en un SDK más antiguo aplaza el cambio. Las notas de la versión no lo dicen.

Lo que sí muestran es un patrón. Las notas de macOS 27 usan lenguaje explícito de condicionamiento por SDK en 11 entradas distintas: “En las apps compiladas con el SDK de macOS 27.0”, “En las apps compiladas con los SDK 27.0”, “Cuando tu proyecto tiene un objetivo de despliegue mínimo inferior a 27.0”.1 Apple matiza estos cambios cuando el matiz corresponde.

La entrada sobre los contenedores no lleva ningún matiz de ese tipo. Si se acepta la lectura razonable de que esa ausencia es deliberada, la restricción es una política a nivel de sistema operativo que se aplica a todo binario que se ejecute en macOS 27, sin importar con qué SDK se compiló.

Tómalo como una inferencia sólida y no como un hecho establecido, porque Apple no lo ha dicho de forma directa. La consecuencia operativa es la misma en cualquier caso: no cuentes con un SDK más antiguo como mitigación. Cuenta con que el acceso va a fallar.

A quién afecta de verdad

El acceso dentro del mismo equipo queda intacto. Las reglas de Apple convierten el límite del Team ID en algo estructural y no accidental: “Equipos de desarrollo distintos no pueden usar el mismo app group”, mientras que un mismo equipo sí puede compartir un grupo entre sus propias apps y procesos de apoyo.2

Las apps que se rompen son las que cruzan esa línea:

Herramientas de migración e importación. Cualquier cosa que lea el contenedor de un competidor o de un producto predecesor para importar datos del usuario. Es el caso más claro, y ya estaba detrás de un diálogo que los usuarios a veces aprobaban.

Utilidades de respaldo y sincronización. Las herramientas que enumeran contenedores de apps para respaldarlos ahora se saltan en silencio todo lo que quede fuera de su propio equipo.

Apps complementarias que cambiaron de manos. El caso más agudo, porque nada del código cambia. Dos apps salen bajo un mismo Team ID y comparten un grupo sin problemas. Una adquisición, la división de un equipo o el traslado a otra cuenta de desarrollador las deja bajo Team ID distintos. El identificador del app group sigue viéndose correcto, la URL sigue resolviéndose y los datos dejan de llegar.

Vale la pena detenerse en ese último caso, porque el momento juega en tu contra. De los cambios de Team ID se encarga quien gestiona la firma y la distribución, y desde esa posición la dependencia del contenedor compartido suele ser invisible. El código compila. Ambas apps se publican. Ninguna suite de pruebas lo detecta, porque las pruebas unitarias no ejercitan un contenedor real de otro equipo y CI compila ambos targets bajo la identidad que tenga la máquina de compilación. La falla aparece en producción, en las computadoras de los usuarios, como datos que dejaron de sincronizarse entre dos apps que el usuario considera, con razón, el mismo producto.

Si estás planeando una migración de Team ID, la auditoría es mecánica: busca con grep cada identificador de app group que declaren tus targets y, para cada uno, anota qué bundles lo leen. Todo identificador leído por bundles que van a quedar bajo Team ID distintos es una ruptura. Eso es una comprobación de diez minutos antes de la mudanza y un incidente de soporte después de ella.

Apple enumera qué puede participar en los app groups: ejecutables principales dentro de estructuras de bundle, extensiones de app, App Clips y XPC Services.2 Cada uno es un lugar donde esto puede manifestarse.

Cómo diagnosticarlo

Vale la pena hacer dos comprobaciones antes de asumir que hay un error en el código.

Confirma que el sistema haya validado siquiera tus entitlements. Apple documenta una comprobación en tiempo de ejecución del indicador de entitlements validados en un proceso en ejecución:2

sudo launchctl procinfo <pid>

Los perfiles de aprovisionamiento creados antes de que existiera la autorización de app groups pueden no llevarla, lo que produce fallas de acceso idénticas en apariencia a la denegación de macOS 27 pero con otra causa. Xcode actualiza los perfiles cuando “Automatically manage signing” está activado y el ajuste de compilación REGISTER_APP_GROUPS tiene el valor Yes.2

Ten claro qué estilo de identificador estás usando. Los grupos con el prefijo group. deben incluirse en el perfil de aprovisionamiento de la app. Los grupos con la forma <TeamID>.<group name> no necesitan perfil, porque el sistema verifica el prefijo del identificador de equipo contra la identidad de firma, pero esa forma existe solo en macOS y los Keychain Access Groups no la admiten.2

Después revisa Privacidad y seguridad, que es donde la nota de la versión dice que ahora vive el control del usuario.1

Diseñar para una denegación que ya no pasa por el usuario

El diálogo hacía trabajo de producto, no solo de seguridad. Le decía al usuario que existía una decisión, y le decía a tu app cuándo se había tomado. Ambas tareas caen ahora de tu lado.

Sondea el límite una sola vez. Intenta una lectura barata en cuanto resuelvas el contenedor, en lugar de descubrir la denegación a mitad de un bucle de sincronización. Una falla en el fondo de una cola produce un resultado parcial y un error atribuido al archivo que casualmente venía a continuación. Un solo intento en el punto de resolución te da un único lugar donde ramificar y, a diferencia de una comprobación previa de permisos, ejercita el mismo camino que van a recorrer tus lecturas reales.

Explica qué pasó, en los términos del usuario. “No se pudieron leer los datos” invita a un ticket de soporte por pérdida de datos. El mensaje preciso nombra el límite y el remedio: los datos pertenecen a una app de otro desarrollador, macOS bloquea ese acceso de forma predeterminada y el interruptor está en Privacidad y seguridad. Nadie puede actuar sobre una falla que no logra ubicar.

No reintentes. Una denegación es un estado de política, no un error transitorio. Los bucles con backoff contra un contenedor bloqueado consumen batería y llenan los registros sin cambiar nada. Falla una vez, muestra el estado y ofrece una nueva comprobación que el usuario pueda disparar después de cambiar la configuración.

Vuelve a comprobar cuando la app se active, en lugar de sondear. Cuando el usuario sale a cambiar una configuración y regresa, ese es el momento de probar el acceso otra vez. Sondear una ruta bloqueada con un temporizador produce la misma denegación en el intervalo que hayas elegido.

Pregúntate si de verdad necesitas la lectura entre equipos. Esta es la incómoda, y muchas veces la respuesta correcta. Una herramienta de migración que lee el contenedor de un competidor hace algo que la plataforma lleva tres versiones estrechando: aislamiento en macOS 15, luego diálogo de autorización y ahora denegación. Una vía de exportación controlada por la otra app, una importación de documentos mediante un selector de archivos que maneja el usuario o un formato de intercambio documentado sobreviven todos a este cambio. Las lecturas de contenedores que cruzan un límite de Team ID llevan una trayectoria, y la trayectoria apunta en una sola dirección.

El selector de archivos merece una mención aparte, porque es la salida que la plataforma tiene prevista. Cuando el usuario elige un archivo, concede el acceso de forma explícita a través del mecanismo de acceso con alcance de seguridad (security-scoped), lo que constituye un modelo de permisos más sólido que el diálogo que acaba de desaparecer, y funciona hoy sin cambiar ninguna configuración.

Conclusiones clave

Para quienes desarrollan apps de macOS: - Nunca interpretes un retorno distinto de nil de containerURL(forSecurityApplicationGroupIdentifier:) como prueba de acceso en macOS. Siempre devuelve una URL. Intenta una lectura y maneja el error. - Olvídate de isReadableFile(atPath:) como comprobación previa. Apple desaconseja predecir resultados del sistema de archivos, y ese método evalúa permisos POSIX, que una denegación de la capa de políticas no tiene por qué tocar. - Audita cualquier ruta de código que lea un contenedor perteneciente a otro Team ID. En macOS 27 falla sin preguntarle a nadie. - Si fijaste ~/Library/Group Containers/... directamente en el código, no tienes ninguna llamada que proteger. Pásate a la API para tener dónde poner la comprobación.

Para los equipos que publican herramientas de migración o respaldo: - Las lecturas entre equipos ya no están a un diálogo de distancia. Diseña con la denegación como estado predeterminado y diles a los usuarios dónde está la configuración. - La falla se presenta como datos ausentes, no como un error. Agrega mensajes explícitos, porque de lo contrario los usuarios lo reportarán como pérdida de datos.

Para quien vaya a cambiar de Team ID: - Una adquisición o la división de una cuenta corta en silencio el uso compartido de app groups entre apps que antes compartían el prefijo de Team ID. Nada en el código fuente lo advierte.

Preguntas frecuentes

¿Esto afecta a las apps que comparten un contenedor dentro de una misma cuenta de desarrollador?

No. El límite es el Team ID. Las reglas de Apple ya impiden que equipos distintos usen el mismo app group, y un mismo equipo puede compartir un grupo entre sus propias apps, extensiones, App Clips y XPC Services.2

¿Mis usuarios verán un diálogo que puedan aprobar?

En macOS 27, no. La nota de la versión indica que estos accesos ya no piden autorización y se deniegan de forma predeterminada, con la gestión trasladada a la configuración de Privacidad y seguridad.1

¿Puedo evitarlo compilando contra un SDK más antiguo?

Probablemente no. Las notas de la versión usan lenguaje explícito de condicionamiento por SDK en otros 11 cambios y no lo usan en este, lo que sugiere una política a nivel de sistema operativo que se aplica a todos los binarios.1 Apple no lo ha afirmado de forma explícita, así que trátalo como una inferencia sólida y planifica asumiendo que el acceso va a fallar.

¿Cómo distingo esto de un problema de aprovisionamiento?

Ejecuta sudo launchctl procinfo <pid> y revisa si el sistema activó en tu proceso el indicador de entitlements validados. Los perfiles de aprovisionamiento antiguos pueden ser anteriores a la autorización de app groups y producir una falla de aspecto similar con una causa que no tiene relación.2

La guía de Apple sobre contenedores de app group describe el comportamiento de macOS 15 y, al momento de escribir esto, no se había actualizado para el cambio de macOS 27.2 Verifica el estado actual de esa página en lugar de fiarte de la fecha de este artículo.

Fuentes


  1. Apple, “macOS 27 Golden Gate Beta 4 Release Notes.” System Integrity Protection, sección New Features: “Acceder a archivos en contenedores de datos y contenedores de app group de otros equipos de desarrollo ya no le pide autorización al usuario; esos accesos se deniegan de forma predeterminada y el usuario puede gestionarlos en la configuración de Privacidad y seguridad.” Radar 161835690. También es la fuente del lenguaje de condicionamiento por SDK usado en otras 11 entradas y ausente en esta. El HTML se renderiza del lado del cliente; la copia legible por máquina está en 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.” Fuente del comportamiento del diálogo en macOS 15, de la regla de que equipos de desarrollo distintos no pueden compartir un app group, de la distinción entre los identificadores group. y <TeamID>.<group name>, de la comprobación de entitlements validados con sudo launchctl procinfo, del ajuste de compilación REGISTER_APP_GROUPS y de la lista de participantes admitidos en un contenedor. Consultada el 1 de agosto de 2026, todavía describe el diálogo. 

  3. Apple, “isReadableFile(atPath:).” Fuente de la recomendación de no predecir el estado del sistema de archivos: “Es mucho mejor intentar una operación (como cargar un archivo o crear un directorio), comprobar si hay errores y manejarlos con elegancia, que tratar de averiguar de antemano si la operación va a tener éxito.” También es la fuente de la nota de que el método “usa el ID de usuario y el ID de grupo reales, y no los efectivos, para determinar si el archivo es legible”, que es la razón por la que una comprobación a nivel POSIX no es un sustituto confiable de una denegación de la capa de políticas. 

  4. Apple, “containerURL(forSecurityApplicationGroupIdentifier:).” Valor de retorno: “En iOS, el valor es nil cuando el identificador de grupo no es válido. En macOS siempre se devuelve una URL con la forma esperada, incluso si el app group no es válido, así que asegúrate de comprobar que puedes acceder al directorio subyacente antes de intentar usarlo.” También es la fuente de la recomendación de no construir la ruta del contenedor a mano. 

Artículos relacionados

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 lectura

El intérprete de fuentes de Apple ahora es Swift, y un 13 % más rápido

El equipo de seguridad de Apple reescribió el intérprete de hinting de TrueType de C a Swift con seguridad de memoria, l…

10 min de lectura

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 lectura