← Wszystkie wpisy

macOS 27 blokuje dostęp do kontenerów innych zespołów bez pytania

W systemie macOS metoda containerURL(forSecurityApplicationGroupIdentifier:) zwraca poprawnie wyglądający URL także dla app group, do której aplikacja nie ma żadnych uprawnień (entitlements). Apple opisuje to wprost: w iOS metoda zwraca nil przy nieprawidłowym identyfikatorze, natomiast w macOS „URL o oczekiwanej postaci zwracany jest zawsze, nawet jeśli app group jest nieprawidłowa”.4 W macOS 27 to zachowanie zderza się z nowym ograniczeniem.

macOS 27 przestał pytać użytkownika, zanim odmówi dostępu do kontenera innego zespołu. Odczyt plików w kontenerze danych aplikacji lub kontenerze app group należącym do innego zespołu deweloperskiego wywoływał wcześniej monit o autoryzację. Teraz domyślnie kończy się niepowodzeniem, a dostęp da się odzyskać wyłącznie wtedy, gdy użytkownik odnajdzie odpowiedni wpis w Prywatności i ochronie (Privacy & Security).1

Te dwa fakty składają się na awarię, która w momencie wystąpienia nie daje żadnego sygnału. Nie pojawia się okno dialogowe. Metoda zwraca URL, a nie nil. Ścieżka wygląda dokładnie tak, jak oczekiwano. Odmowa ujawnia się później, przy operacji na pliku, i wygląda jak brakujący plik.

W skrócie

macOS 27 usuwa monit o autoryzację przy dostępie do kontenerów danych i kontenerów app group innych zespołów, a taki dostęp domyślnie blokuje — sterowanie nim przeniesiono do ustawień Prywatność i ochrona.1 Zmianę zapisano w sekcji System Integrity Protection jako nową funkcję, nie jako poprawkę błędu. Własne wskazówki Apple dotyczące kontenerów app group wciąż opisują zachowanie z monitem, które macOS 27 zlikwidował. Ponieważ API w macOS zwraca poprawnie zbudowany URL nawet dla grup, do których nie ma dostępu, odmowa ujawnia się przy odczycie, a nie przy wywołaniu API. Dostęp w obrębie tego samego zespołu pozostaje nienaruszony: granicą jest Team ID.

Co się zmieniło

Informacje o wydaniu macOS 27 zawierają w sekcji System Integrity Protection jedno zdanie:1

„Dostęp do plików w kontenerach danych aplikacji i kontenerach app group należących do innych zespołów deweloperskich nie wywołuje już monitu o autoryzację użytkownika; takie odwołania są domyślnie blokowane, a użytkownik może nimi zarządzać w ustawieniach Prywatność i ochrona”.

Radar 161835690. Dwa człony zdania, dwie osobne zmiany.

Pierwszy człon usuwa monit. Drugi ustanawia odmowę jako zachowanie domyślne. Doniesienia o tej zmianie zwykle zaczynają od odmowy, czyli od mniej ciekawej połowy. Zaostrzanie domyślnych ustawień to rutyna. Zniknięcie monitu zmienia natomiast kształt awarii, której doświadczają użytkownicy, oraz kształt zgłoszenia błędu, które trafia do dewelopera.

Wcześniej użytkownik widział okno dialogowe i podejmował decyzję. Jeśli odmówił, aplikacja wiedziała, że taki wybór podjął człowiek. Teraz nikt nie jest pytany. Dostęp po prostu nie następuje, a jedyna droga do jego włączenia prowadzi przez panel ustawień, do którego użytkownik nie ma powodu zaglądać, dopóki ktoś mu o tym nie powie.

Wcześniejsze zachowanie, które dokumentacja Apple wciąż opisuje

Ograniczenie nakłada się na ochronę wprowadzoną dwa wydania wcześniej. Przewodnik Apple po kontenerach app group mówi:2

„W macOS 15 i nowszych kontenery app group zapewniają [System Integrity Protection] lokalnym plikom aplikacji, nawet jeśli aplikacja nie działa w piaskownicy aplikacji. Takie kontenery app group ograniczają dostęp aplikacjom spoza danej app group. Próba dostępu do lokalizacji wewnątrz app group lub kontenera danych aplikacji, podjęta przez aplikację spoza grupy, skutkuje monitem z prośbą o autoryzację użytkownika”.

Ta strona opisuje monit w czasie teraźniejszym. W chwili pisania tego tekstu Apple nie zaktualizowało jej pod kątem macOS 27.

Konsekwencja jest praktyczna: deweloper, który napotka ten problem, sięgnie do dokumentacji i trafi na oficjalną stronę, przeczyta tam, że pojawi się okno autoryzacji. Nie pojawi się. Wyciągnie więc rozsądny skądinąd wniosek, że monit jest zepsuty albo że entitlements są źle skonfigurowane — i zacznie szukać w złym miejscu.

Warto samodzielnie sprawdzić bieżący stan tej strony, zamiast ufać dacie tego wpisu. Dokumentacja z czasem nadrabia zaległości.

Dlaczego awaria ujawnia się z opóźnieniem

To właśnie zachowanie API zamienia zmianę polityki systemowej w problem diagnostyczny.

Dokumentacja Apple dla containerURL(forSecurityApplicationGroupIdentifier:) jasno opisuje różnicę między platformami:4

„URL wskazujący położenie współdzielonego katalogu grupy w systemie plików. W iOS wartością jest nil, gdy identyfikator grupy jest nieprawidłowy. W macOS zawsze zwracany jest URL o oczekiwanej postaci, nawet jeśli app group jest nieprawidłowa — należy więc upewnić się, że dostęp do katalogu docelowego faktycznie działa, zanim zacznie się go używać”.

Sekcja z omówieniem powtarza to ostrzeżenie: wywołanie metody z identyfikatorem grupy, do której brakuje uprawnienia, i tak zwróci URL o oczekiwanej postaci, ale katalog nie istnieje, a aplikacja działająca w piaskownicy nie może go utworzyć.4

Popularny wzorzec obronny nic tu zatem nie daje:

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

URL jest poprawnie zbudowany. Wskazuje na ~/Library/Group Containers/<team>.<group>, czyli dokładnie tam, gdzie taki kontener by się znajdował. Wszystko wygląda na sukces, dopóki nie zostanie na nim wykonana operacja plikowa.

Rozwiązaniem jest podjęcie próby odczytu i obsłużenie błędu, zamiast pytania, czy odczyt by się powiódł:

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

Warto oprzeć się pokusie użycia isReadableFile(atPath:) jako sprawdzenia wstępnego. Apple odradza całą tę klasę testów:3

„Opieranie zachowania programu na bieżącym stanie systemu plików lub konkretnego pliku nie jest zalecane. Może to prowadzić do dziwnych zachowań lub sytuacji wyścigu. Znacznie lepiej jest podjąć próbę wykonania operacji (na przykład wczytania pliku lub utworzenia katalogu), sprawdzić błędy i obsłużyć je w kontrolowany sposób, niż z góry ustalać, czy operacja się powiedzie”.

Jest jeszcze drugi powód, tym razem specyficzny dla omawianej zmiany. Ta sama strona zaznacza, że isReadableFile(atPath:) przy ocenie możliwości odczytu „używa rzeczywistego identyfikatora użytkownika i grupy”.3 To ocena uprawnień POSIX. Odmowa dostępu do kontenera innego zespołu jest natomiast decyzją polityki systemowej, umieszczoną warstwę wyżej niż POSIX, więc sprawdzenie bitów uprawnień nie musi jej odzwierciedlać. Sprawdzenie wstępne, które zwraca true przed odczytem i tak kończącym się niepowodzeniem, jest gorsze niż brak takiego sprawdzenia, bo oddala moment zaskoczenia od jego przyczyny.

Osoby, które czytały canOpenURL zostaje wycofane w iOS 27, rozpoznają ten schemat. Apple konsekwentnie odbiera możliwość zapytania z wyprzedzeniem, zostawiając możliwość spróbowania. Zasada „spróbuj i obsłuż błąd” staje się odpowiedzią ogólną, a nie obejściem dla jednego API.

Warto zwrócić uwagę na asymetrię między platformami. iOS zwraca nil i zawodzi uczciwie, w miejscu wywołania. macOS zwraca URL i odracza awarię. Zmiana w macOS 27 usuwa monit, czyli ostatni sygnał, jaki pozostawał na tej z dwóch platform, która i tak była cichsza.

Jeszcze jedna pułapka: Apple zaleca, aby zawsze korzystać z URL zwróconego przez tę metodę, zamiast składać ~/Library/Group Containers/... ręcznie, ponieważ lokalizacja może się zmienić w przyszłych wydaniach.4 Kto zapisał tę ścieżkę na sztywno, nie ma żadnego wywołania metody do sprawdzenia ani miejsca, w którym mógłby umieścić test dostępności.

Prawdopodobnie nie zależy to od SDK

Nasuwa się naturalne pytanie: czy pozostanie przy starszym SDK odracza tę zmianę? Informacje o wydaniu tego nie rozstrzygają.

Widać w nich natomiast pewien wzorzec. Sformułowania jednoznacznie wiążące zmianę z SDK pojawiają się w notatkach macOS 27 w 11 osobnych wpisach: „W aplikacjach zbudowanych z użyciem SDK macOS 27.0”, „W aplikacjach zbudowanych z użyciem SDK w wersji 27.0”, „Gdy projekt ma minimalny docelowy system starszy niż 27.0”.1 Apple dodaje takie zastrzeżenie wszędzie tam, gdzie ono obowiązuje.

Wpis dotyczący kontenerów takiego zastrzeżenia nie zawiera. Przy rozsądnym założeniu, że ten brak jest zamierzony, ograniczenie stanowi politykę na poziomie systemu i obejmuje każdy plik binarny uruchamiany na macOS 27, niezależnie od tego, którym SDK został zbudowany.

Należy to traktować jako mocny wniosek, a nie stwierdzony fakt, bo Apple nie powiedziało tego wprost. Konsekwencja operacyjna jest w obu wypadkach ta sama: nie warto planować starszego SDK jako środka zaradczego. Lepiej założyć, że dostęp się nie powiedzie.

Kogo to realnie dotyka

Dostęp w obrębie jednego zespołu pozostaje nietknięty. Reguły Apple czynią granicę Team ID kwestią strukturalną, a nie przypadkową: „Różne zespoły deweloperskie nie mogą używać tej samej app group”, podczas gdy jeden zespół może współdzielić grupę między własnymi aplikacjami i procesami pomocniczymi.2

Przestają działać te aplikacje, które sięgają poza tę granicę:

Narzędzia do migracji i importu. Wszystko, co czyta kontener konkurenta lub poprzednika, aby zaimportować dane użytkownika. To przypadek najbardziej oczywisty i już wcześniej chroniony monitem, który użytkownicy czasem zatwierdzali.

Narzędzia do kopii zapasowych i synchronizacji. Programy przeglądające kontenery aplikacji w celu wykonania kopii po cichu pomijają teraz wszystko spoza własnego zespołu.

Aplikacje towarzyszące, które zmieniły właściciela. Przypadek najostrzejszy, bo w kodzie nie zmienia się nic. Dwie aplikacje wychodzą pod jednym Team ID i bez problemu współdzielą grupę. Przejęcie, podział zespołu albo przeniesienie na osobne konto deweloperskie umieszcza je pod różnymi Team ID. Identyfikator app group nadal wygląda poprawnie, URL nadal się rozwiązuje, a dane po prostu przestają docierać.

Przy tym ostatnim przypadku warto się zatrzymać, bo okoliczności działają na niekorzyść. Zmianami Team ID zajmuje się osoba odpowiedzialna za podpisywanie i dystrybucję, a zależność od współdzielonego kontenera jest z tej perspektywy zwykle niewidoczna. Kod się kompiluje. Obie aplikacje trafiają do wydania. Żaden zestaw testów tego nie wychwyci, bo testy jednostkowe nie dotykają prawdziwego kontenera innego zespołu, a CI buduje oba cele pod tożsamością, którą akurat ma maszyna budująca. Awaria pojawia się na produkcji, na maszynach użytkowników, jako dane, które przestały się synchronizować między dwiema aplikacjami uznawanymi przez użytkownika — całkiem zasadnie — za jeden produkt.

Przy planowanej migracji Team ID audyt jest czysto mechaniczny: należy wyszukać (grep) każdy identyfikator app group deklarowany przez cele projektu i dla każdego z nich wypisać, które bundle go czytają. Każdy identyfikator czytany przez bundle, które trafią pod różne Team ID, oznacza awarię. Przed migracją to sprawdzenie na dziesięć minut, po niej — zgłoszenie do wsparcia technicznego.

Apple wymienia, co może uczestniczyć w app groups: główne pliki wykonywalne w strukturach bundle, rozszerzenia aplikacji, App Clips oraz XPC Services.2 Każdy z tych elementów jest miejscem, w którym problem może się ujawnić.

Diagnostyka

Zanim uzna się problem za błąd we własnym kodzie, warto wykonać dwa sprawdzenia.

Po pierwsze, należy potwierdzić, że system w ogóle zweryfikował entitlements. Apple opisuje sprawdzenie flagi „entitlements validated” dla działającego procesu:2

sudo launchctl procinfo <pid>

Profile provisioningowe utworzone, zanim autoryzacja app group w ogóle istniała, mogą jej nie zawierać, co daje błędy dostępu wyglądające identycznie jak odmowa z macOS 27, ale mające zupełnie inną przyczynę. Xcode odświeża profile, gdy włączona jest opcja „Automatically manage signing”, a ustawienie kompilacji REGISTER_APP_GROUPS ma wartość Yes.2

Po drugie, trzeba wiedzieć, którego stylu identyfikatorów się używa. Grupy z przedrostkiem group. muszą znaleźć się w profilu provisioningowym aplikacji. Grupy w postaci <TeamID>.<group name> profilu nie wymagają, ponieważ system weryfikuje przedrostek z identyfikatorem zespołu względem tożsamości podpisującej — ta forma działa jednak wyłącznie w macOS i nie jest obsługiwana przez Keychain Access Groups.2

Następnie warto zajrzeć do Prywatności i ochrony, bo według informacji o wydaniu to tam trafiło sterowanie po stronie użytkownika.1

Projektowanie pod odmowę, o którą nie można zapytać

Monit wykonywał pracę produktową, nie tylko zabezpieczającą. Informował użytkownika, że istnieje decyzja do podjęcia, a aplikacji mówił, kiedy została podjęta. Obie te role spadają teraz na dewelopera.

Jedna próba na granicy. Warto wykonać jeden tani odczyt zaraz po ustaleniu ścieżki kontenera, zamiast odkrywać odmowę w połowie pętli synchronizacji. Awaria w głębi kolejki daje wynik częściowy i błąd przypisany temu plikowi, który akurat był następny w kolejce. Pojedyncza próba w momencie ustalania ścieżki daje jedno miejsce na rozgałęzienie logiki, a w odróżnieniu od wstępnego sprawdzania uprawnień przechodzi dokładnie tą samą drogą co docelowe odczyty.

Trzeba powiedzieć, co się stało, językiem użytkownika. Komunikat „Nie udało się odczytać danych” aż się prosi o zgłoszenie do wsparcia w sprawie utraty danych. Trafny komunikat nazywa granicę i sposób jej pokonania: dane należą do aplikacji innego dewelopera, macOS domyślnie blokuje taki dostęp, a przełącznik znajduje się w Prywatności i ochronie. Użytkownik nie zareaguje na awarię, której nie potrafi zlokalizować.

Bez ponawiania prób. Odmowa to stan wynikający z polityki systemu, a nie błąd przejściowy. Pętle z wycofywaniem (backoff) na zablokowanym kontenerze zużywają baterię i zapychają logi, niczego przy tym nie zmieniając. Lepiej raz zasygnalizować niepowodzenie, pokazać stan i udostępnić ponowne sprawdzenie, które użytkownik uruchomi po zmianie ustawienia.

Ponowne sprawdzenie przy aktywacji, nie odpytywanie. Gdy użytkownik wychodzi zmienić ustawienie i wraca, to właśnie wtedy należy sprawdzić dostęp ponownie. Cykliczne odpytywanie zablokowanej ścieżki daje tę samą odmowę w dowolnie wybranym odstępie czasu.

Warto zapytać, czy odczyt spoza własnego zespołu jest w ogóle potrzebny. Pytanie niewygodne, a często dające właściwą odpowiedź. Narzędzie migracyjne czytające kontener konkurenta robi coś, co platforma zawęża od trzech wydań: najpierw piaskownica w macOS 15, potem monit, teraz odmowa. Ścieżka eksportu kontrolowana przez drugą aplikację, import dokumentu przez okno wyboru pliku prowadzone przez użytkownika oraz udokumentowany format wymiany danych — to wszystko przetrwa tę zmianę. Odczyty kontenerów przez granicę Team ID mają swoją trajektorię, a ta prowadzi tylko w jedną stronę.

Okno wyboru pliku zasługuje na osobną wzmiankę, bo jest furtką przewidzianą przez samą platformę. Użytkownik wskazujący plik nadaje dostęp jawnie, przez mechanizm security-scoped, co daje mocniejszą podstawę uprawnienia niż monit, który właśnie zniknął — i działa już dziś, bez zmieniania jakichkolwiek ustawień.

Najważniejsze wnioski

Dla twórców aplikacji na macOS: - Zwrot inny niż nil z containerURL(forSecurityApplicationGroupIdentifier:) nigdy nie jest w macOS dowodem dostępu. Ta metoda zawsze zwraca URL. Należy podjąć próbę odczytu i obsłużyć błąd. - Nie należy używać isReadableFile(atPath:) jako sprawdzenia wstępnego. Apple odradza przewidywanie wyników operacji na systemie plików, a sama metoda ocenia uprawnienia POSIX, których odmowa z warstwy polityki systemowej wcale nie musi dotyczyć. - Warto przejrzeć każdą ścieżkę kodu czytającą kontener należący do innego Team ID. W macOS 27 kończy się ona niepowodzeniem, nie pytając nikogo o zdanie. - Przy ścieżce ~/Library/Group Containers/... zapisanej na sztywno nie ma żadnego wywołania metody do zabezpieczenia. Warto przejść na API, żeby było gdzie umieścić sprawdzenie.

Dla zespołów wydających narzędzia migracyjne lub do kopii zapasowych: - Odczyt spoza własnego zespołu nie jest już oddalony o jeden monit. Trzeba projektować z odmową jako stanem domyślnym i wskazywać użytkownikom, gdzie znajduje się ustawienie. - Awaria objawia się brakiem danych, a nie komunikatem o błędzie. Konieczny jest wyraźny komunikat, bo inaczej użytkownicy zgłoszą to jako utratę danych.

Dla wszystkich zmieniających Team ID: - Przejęcie firmy lub podział konta po cichu zrywa współdzielenie app group między aplikacjami, które wcześniej miały wspólny przedrostek Team ID. Nic w kodzie źródłowym tego nie sygnalizuje.

Najczęstsze pytania

Czy dotyczy to aplikacji współdzielących kontener w obrębie jednego konta deweloperskiego?

Nie. Granicą jest Team ID. Reguły Apple i tak nie pozwalają różnym zespołom używać tej samej app group, a jeden zespół może współdzielić grupę między własnymi aplikacjami, rozszerzeniami, App Clips i XPC Services.2

Czy użytkownicy zobaczą monit, który mogą zatwierdzić?

W macOS 27 — nie. Informacja o wydaniu mówi, że takie odwołania nie wywołują już monitu o autoryzację i są domyślnie blokowane, a zarządzanie nimi przeniesiono do ustawień Prywatność i ochrona.1

Czy da się tego uniknąć, budując pod starszy SDK?

Prawdopodobnie nie. Informacje o wydaniu stosują jednoznaczne odniesienia do SDK przy 11 innych zmianach, a przy tej — ani razu, co sugeruje politykę na poziomie systemu obejmującą wszystkie pliki binarne.1 Apple nie stwierdziło tego wprost, więc należy traktować to jako mocny wniosek i zakładać, że dostęp się nie powiedzie.

Jak odróżnić to od problemu z provisioningiem?

Wystarczy uruchomić sudo launchctl procinfo <pid> i sprawdzić, czy system ustawił dla procesu flagę potwierdzającą weryfikację entitlements. Starsze profile provisioningowe mogą pochodzić sprzed wprowadzenia autoryzacji app group i dawać podobnie wyglądającą awarię o zupełnie innej przyczynie.2

Dlaczego dokumentacja wciąż opisuje monit?

Przewodnik Apple po kontenerach app group opisuje zachowanie z macOS 15 i w chwili pisania tego tekstu nie został zaktualizowany pod kątem zmiany w macOS 27.2 Warto sprawdzić bieżący stan tej strony, zamiast polegać na dacie tego wpisu.

Źródła


  1. Apple, “macOS 27 Golden Gate Beta 4 Release Notes.” System Integrity Protection, nowe funkcje: „Dostęp do plików w kontenerach danych aplikacji i kontenerach app group należących do innych zespołów deweloperskich nie wywołuje już monitu o autoryzację użytkownika; takie odwołania są domyślnie blokowane, a użytkownik może nimi zarządzać w ustawieniach Prywatność i ochrona”. Radar 161835690. Również źródło sformułowań wiążących zmiany z SDK, użytych w 11 innych wpisach i nieobecnych w tym. HTML renderuje się po stronie klienta; kopia do odczytu maszynowego znajduje się pod adresem 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.” Źródło informacji o zachowaniu z monitem w macOS 15, o regule, że różne zespoły deweloperskie nie mogą współdzielić app group, o rozróżnieniu identyfikatorów group. oraz <TeamID>.<group name>, o sprawdzeniu weryfikacji entitlements poleceniem sudo launchctl procinfo, o ustawieniu kompilacji REGISTER_APP_GROUPS oraz o liście komponentów mogących uczestniczyć w kontenerach. Pobrano 1 sierpnia 2026 — strona wciąż opisuje monit. 

  3. Apple, “isReadableFile(atPath:).” Źródło wskazówki odradzającej przewidywanie stanu systemu plików: „Znacznie lepiej jest podjąć próbę wykonania operacji (na przykład wczytania pliku lub utworzenia katalogu), sprawdzić błędy i obsłużyć je w kontrolowany sposób, niż z góry ustalać, czy operacja się powiedzie”. Również źródło uwagi, że metoda „używa rzeczywistego identyfikatora użytkownika i grupy, a nie identyfikatorów efektywnych, aby ustalić, czy plik nadaje się do odczytu” — dlatego sprawdzenie na poziomie POSIX nie jest wiarygodnym zamiennikiem dla odmowy z warstwy polityki systemowej. 

  4. Apple, “containerURL(forSecurityApplicationGroupIdentifier:).” Wartość zwracana: „W iOS wartością jest nil, gdy identyfikator grupy jest nieprawidłowy. W macOS zawsze zwracany jest URL o oczekiwanej postaci, nawet jeśli app group jest nieprawidłowa — należy więc upewnić się, że dostęp do katalogu docelowego faktycznie działa, zanim zacznie się go używać”. Również źródło zalecenia, by nie budować ścieżki kontenera ręcznie. 

Powiązane artykuły

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 czytania

Interpreter czcionek Apple jest teraz w Swifcie i o 13% szybszy

Zespół bezpieczeństwa Apple przepisał interpreter hintingu TrueType z C na bezpieczny pamięciowo Swift, przyspieszył go …

8 min czytania

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 czytania