소스의 PrivacyInfo.xcprivacy는 정상인데 App Store Connect가 중첩 Framework 안의 다른 파일을 가리키고 있습니까?
가장 빠른 해결법은 메일의 Bundle 경로를 기준으로 최종 xcarchive에서 책임 Target을 찾은 뒤, 형식·선언·SDK 출처·번들 위치를 따로 검증하고 새로 Archive, 재서명, 실제 업로드까지 완료하는 것입니다.
이 글은 ITMS-91056 또는 유사한 개인정보 목록 오류로 TestFlight나 App 제출이 멈춘 독립 개발자에게 적합합니다. Swift Package Manager, CocoaPods, XCFramework를 사용하며 문제가 자신의 코드인지 의존성 유지 관리자 문제인지 구분하기 어려운 팀도 대상입니다. 원격 Mac이나 지속적 통합 환경에서 소스와 최종 산출물이 달라지는 이유를 확인하려는 경우에도 사용할 수 있습니다.
01 오류 경로가 첫 번째 증거가 됩니다
App Store Connect 개인정보 목록 무효 오류를 받으면 먼저 메일의 오류 코드, 파일 이름, Bundle 경로를 따로 기록합니다. 예를 들어 메일이 주 앱이 아니라 특정 Framework의 PrivacyInfo.xcprivacy를 지목한다면, 주 앱에 새 파일을 추가하는 것은 첫 조치가 아닙니다.
Apple은 무효한 PrivacyInfo.xcprivacy가 포함된 제출을 거절할 수 있다고 안내합니다. 오류 종류도 서로 다릅니다. 무효한 파일, 파일 누락, Required Reason API 미선언, 공식 대상 SDK의 서명 또는 매니페스트 문제를 같은 방식으로 처리하면 수정 방향이 어긋납니다. Apple의 무효 개인정보 매니페스트 문제 해결 문서를 기준으로 오류 경로를 먼저 분류합니다.
주의: 오류 메일의 프로젝트 이름, Bundle ID, SDK 이름, 계정, 파일 경로와 로그는 외부에 공유하기 전에 모두 가리십시오. 단순히 파일을 열어 보는 작업과 Archive 내부 파일을 수정하는 작업은 전혀 다릅니다.
다음 순서로 증거를 고정합니다.
- 오류 코드와 메일에 표시된 Bundle 경로를 복사합니다.
- 제출에 사용한
.xcarchive를 별도 복사본으로 보존합니다. - Archive 안에서 오류 경로와 같은 파일을 찾습니다.
- 해당 파일이 주 앱, Extension, Framework, App Clip, Mac Catalyst 또는 macOS Bundle 중 어디에 속하는지 표시합니다.
- 현재 소스 디렉터리의 파일과 Archive 내부 파일을 구분합니다.
소스 폴더를 확인하는 것만으로는 충분하지 않습니다. 빌드 스크립트, 패키지 리소스 설정, 복사 단계가 최종 산출물을 바꿀 수 있기 때문입니다.
02 plist 형식 검사는 통과 조건의 일부일 뿐입니다
PrivacyInfo.xcprivacy는 plist 문법만 맞으면 끝나는 파일이 아닙니다. 루트 객체, 배열과 사전의 계층, 문자열과 불리언의 자료형, 빈 배열, 허용되지 않은 필드를 모두 확인해야 합니다. Apple의 앱 또는 서드파티 SDK에 개인정보 매니페스트를 추가하는 안내는 파일의 배치와 구조를 함께 설명합니다.
Xcode의 속성 목록 편집기에서 먼저 확인하고, 복사한 Archive 또는 별도 작업용 파일에 다음처럼 plutil 검사를 실행합니다.
ARCHIVE_PATH="/path/to/YourApp.xcarchive"
MANIFEST_PATH="$ARCHIVE_PATH/Products/Applications/YourApp.app/PrivacyInfo.xcprivacy"
plutil -lint "$MANIFEST_PATH"
plutil -p "$MANIFEST_PATH"
실제 경로는 오류 메일의 Bundle 구조에 맞춰 바꿉니다. 위 명령은 원본 Archive를 수정하지 않지만, plutil -replace, plutil -insert, 편집기 저장 명령은 파일을 바꿀 수 있으므로 복사본에서만 실행해야 합니다.
plutil -lint가 성공하면 문법이 읽힌다는 뜻입니다. App Store Connect가 요구하는 API 카테고리, 승인된 사유 값, SDK별 매니페스트 요구까지 충족했다는 뜻은 아닙니다. 따라서 다음처럼 세 단계로 판정합니다.
- 문법 통과: plist가 파싱되고 루트 형식이 예상과 같습니다.
- 규칙 통과: 키, 배열, 사유 값과 자료형이 Apple의 허용 범위에 맞습니다.
- 제출 통과: 책임 Bundle의 서명 상태와 업로드 후 처리 결과까지 정상입니다.
03 ITMS-91056과 Required Reason API는 실제 호출에 맞춰 대조합니다
ITMS-91056 Invalid privacy manifest를 수정할 때는 오류 문구를 바로 지우거나 주 앱 매니페스트를 복사하지 않습니다. 먼저 해당 Bundle이 실제로 사용하는 Required Reason API를 추적합니다.
Apple은 Required Reason API에 대해 API 범주와 승인된 사유를 실제 기능에 맞게 선언하도록 요구합니다. Required Reason API의 공식 설명과 항목 추가 안내를 함께 확인합니다.
증거는 세 곳에서 모읍니다.
- 앱 코드와 빌드된 의존성에서 API 호출을 검색합니다.
- 패키지 잠금 파일과 의존성 목록에서 호출 주체를 확인합니다.
- Xcode의 개인정보 보고서와 최종 Bundle의 매니페스트를 서로 대조합니다.
승인된 사유 중 기능과 가장 비슷해 보이는 값을 임의로 고르면 안 됩니다. API가 실제로 어떤 기능에 쓰이는지 설명할 수 있어야 합니다. 주 앱에서 호출하지 않은 API를 주 앱 매니페스트에 추가해 경고를 숨기는 방식도 올바른 해결책이 아닙니다.
특히 서드파티 SDK가 해당 API를 사용한다면 SDK가 제공해야 하는 매니페스트와 주 앱의 매니페스트를 구분합니다. 주 앱의 파일이 SDK 내부 호출을 대신 선언한다고 가정하지 마십시오. Apple의 개인정보 보고서 설명을 사용해 보고서에 나타난 주체와 최종 Bundle을 맞춥니다.
04 서드파티 SDK 파일은 출처에 따라 처리합니다
“서드파티 SDK 개인정보 목록 무효는 직접 고치면 되나요?”라는 질문에는 출처를 확인한 뒤 결정해야 한다고 답해야 합니다. 의존성 관리 방식마다 책임 위치가 다릅니다.
- Swift Package Manager: 패키지의 리소스 선언과 잠금된 버전을 확인합니다.
- CocoaPods: 설치된 Pod의 리소스 복사와 빌드 단계를 확인합니다.
- 동적 Framework: Framework 내부의 매니페스트와 서명 상태를 확인합니다.
- XCFramework: 실제 빌드 대상에 포함된 플랫폼별 Framework를 확인합니다.
- 직접 제공한 바이너리: 공급자가 배포한 매니페스트, 버전, 서명 자료를 확인합니다.
Apple의 서드파티 SDK 요구사항에 포함되는 SDK라면 매니페스트뿐 아니라 필요한 서명 조건도 함께 검토해야 합니다. 우선순위는 유효한 파일을 제공하는 버전으로 업그레이드하는 것입니다. 패키지 버전을 올리기 전에는 잠금 파일, 변경 내역, 재빌드 결과를 보존합니다.
Archive 안의 SDK 파일을 급히 수정하는 방법은 마지막 임시 조치입니다. 서명된 Framework의 파일을 바꾸면 코드 서명이 더 이상 일치하지 않을 수 있습니다. 수정 뒤에는 해당 코드와 상위 앱을 조건에 맞게 다시 서명해야 하며, 배포 인증서와 프로비저닝 설정도 다시 확인해야 합니다. 장기적으로는 수정된 의존성 버전을 고정하거나 유지 관리자에게 유효한 배포물을 받아 교체해야 합니다.
경험상 “파일 하나만 고치고 다시 업로드”하는 방식은 다음 빌드에서 같은 오류를 재현하기 쉽습니다. 임시 Archive 수정은 원인 확인용으로만 사용하고, 소스 또는 의존성 버전에 반영한 뒤 새 Archive를 생성하십시오.
05 어떤 Bundle에 파일이 들어갔는지 확인합니다
PrivacyInfo.xcprivacy가 소스에 있어도 올바른 Target에 포함되지 않으면 최종 Archive에서 사라질 수 있습니다. 반대로 같은 프로젝트에 여러 Target이 있으면 주 앱 파일 하나만으로 모든 Bundle의 요구를 해결할 수 없습니다.
다음 대상을 각각 확인합니다.
- 주 iOS 앱
- Notification 또는 Share Extension
- App Clip
- 동적 또는 정적 Framework
- Mac Catalyst 앱
- macOS 앱과 내부 Bundle
Swift Package가 리소스를 선언하지 않았거나, Xcode Target Membership가 빠졌거나, 빌드 스크립트가 파일을 잘못된 디렉터리에 복사하면 소스와 Archive가 달라집니다. 먼저 Archive 구조를 확인합니다.
ARCHIVE_PATH="/path/to/YourApp.xcarchive"
find "$ARCHIVE_PATH/Products" \
-name "PrivacyInfo.xcprivacy" \
-print
찾은 각 파일의 상위 Bundle을 오류 메일의 경로와 비교합니다. 주 앱에 파일이 있다는 사실보다 책임 Bundle 안에 파일이 있는지가 중요합니다. Xcode 개인정보 보고서에서도 같은 대상이 표시되는지 교차 검증합니다.
여러 Target과 Extension에 각각 추가해야 하는 경우
각 Target 또는 내장 Bundle이 독립적으로 개인정보 API를 사용하거나 자체 매니페스트를 요구한다면 각각의 최종 위치에 파일이 필요할 수 있습니다. 단순히 파일을 여러 곳에 복사하는 것이 아니라, 해당 코드와 의존성이 실제로 사용하는 API와 데이터 처리를 기준으로 작성해야 합니다.
Target이 같은 Framework를 공유한다면 Framework 자체의 매니페스트와 앱의 매니페스트를 분리해 확인합니다. App Clip이나 Extension이 별도 Bundle로 포장된다면 Archive에서 각각의 경로를 확인한 뒤, 어느 경로가 오류에 표시됐는지 기록합니다.
06 `plutil` 통과 후에도 거절되는 이유를 분리합니다
문법 검사와 App Store Connect 처리는 서로 다른 단계입니다. 다음 원인이 대표적입니다.
- 허용되지 않은 키나 사유 값이 들어갔습니다.
- 배열 안의 자료형이 잘못되었습니다.
- 실제 API 호출과 선언된 이유가 다릅니다.
- 다른 중첩 Bundle에 무효한 파일이 남아 있습니다.
- SDK 매니페스트는 정상처럼 보이지만 서명이 깨졌습니다.
- 수정한 Archive를 다시 서명하지 않았습니다.
- 로컬에서 검사한 Archive와 실제 업로드한 Archive가 다릅니다.
따라서 성공 여부를 한 문장으로 판단하지 않습니다. 로컬 plist 검사 성공, Archive 구조 완성, 코드 서명 유효, 업로드 접수, App Store Connect 처리 완료를 별도 상태로 기록합니다.
07 조건별로 다음 조치를 선택합니다
아래 조건 목록은 수정 방법을 고르는 분기점입니다.
- 오류 경로가 주 앱의 파일이고 실제 API 호출도 주 앱에서 발견되면
주 앱 Target의 매니페스트와 승인된 사유를 수정한 뒤 새 Archive를 만듭니다. - 오류 경로가 Framework 또는 SDK 내부이고 해당 SDK가 업데이트를 제공하면
직접 Archive를 고치지 말고 의존성 버전을 올린 뒤 잠금 파일과 빌드 로그를 보존합니다. - SDK가 업데이트를 제공하지 않지만 호출과 사유를 명확히 확인할 수 있으면
유지 관리자에게 수정본을 요청하고, 임시 Archive 조치는 재서명 조건을 확인한 경우에만 사용합니다. - 소스에는 파일이 있지만 Archive에 없으면
Target Membership, Package 리소스 선언, 복사 단계와 빌드 스크립트를 수정합니다. plutil은 통과하지만 App Store Connect가 계속 거절하면
최종 Bundle의 키와 값, 중첩 파일, SDK 서명, 실제 업로드 파일을 다시 대조합니다.- 코드 호출의 주체를 확인하지 못하면
임의의 approved reason을 추가하지 말고 의존성 목록과 개인정보 보고서를 먼저 확보합니다.
08 두 표로 최종 검증 상태를 고정합니다
| 점검 대상 | 확인할 증거 | 통과 기준 | 다음 조치 |
|---|---|---|---|
| 오류 경로 | App Store Connect 메일 | 파일명과 Bundle 경로를 기록함 | Archive에서 동일 경로 검색 |
| plist 형식 | plutil -lint, 속성 목록 편집기 |
파싱 성공, 루트와 자료형이 정상 | 규칙과 값 검토 |
| API 선언 | 코드 검색, 의존성 목록, 개인정보 보고서 | 실제 호출과 approved reason이 일치함 | 불일치 시 선언 수정 |
| SDK 출처 | 패키지 잠금 파일, Framework 내부 파일 | 제공 주체와 버전이 확인됨 | 업그레이드 또는 유지 관리자 문의 |
| Bundle 위치 | Archive의 Products 구조 |
책임 Target 안에 파일이 존재함 | Target 설정과 복사 단계 수정 |
| 서명 | 최종 Framework와 앱의 서명 확인 | 수정 후 서명 상태가 유효함 | 필요 시 전체 재서명 |
| 업로드 | 업로드 기록과 App Store Connect 처리 상태 | 새 Archive가 접수되고 처리 완료됨 | 실패 경로를 다시 분류 |
| 상태 | 허용되는 결론 | 아직 하면 안 되는 일 |
|---|---|---|
| 소스만 정상 | 코드와 설정의 출발점이 정상 | 제출 성공으로 판단하지 않기 |
| 로컬 검사 성공 | plist 문법이 정상 | API 사유가 맞다고 단정하지 않기 |
| Archive 확인 완료 | 실제 산출물의 경로가 확인됨 | 이전 Archive 재사용하지 않기 |
| 서명 확인 완료 | 수정된 코드가 서명 조건을 충족함 | 다른 산출물 업로드하지 않기 |
| 업로드 접수 | 서버가 파일을 받음 | 처리 완료로 간주하지 않기 |
| 처리 완료 | 해당 제출이 개인정보 오류를 통과함 | 다음 빌드에서 잠금 파일과 로그 삭제하지 않기 |
09 새 Archive와 실제 업로드로 마무리합니다
수정 후에는 기존 Archive를 덮어쓰지 말고 새로 생성합니다. 새 Archive 안에서 다시 PrivacyInfo.xcprivacy를 검색하고, 오류 메일이 가리킨 책임 Bundle의 내용을 확인합니다. 그 다음 최종 앱과 중첩 코드의 서명 상태를 확인합니다.
배포 과정은 다음처럼 기록합니다.
- 의존성 잠금 파일과 빌드 커밋을 보존합니다.
- Xcode 빌드 로그와 개인정보 보고서를 저장합니다.
- 새 Archive의 생성 시각과 식별 정보를 기록합니다.
- 업로드한 파일이 새 Archive인지 확인합니다.
- App Store Connect에서 접수와 처리 완료를 각각 확인합니다.
원격 Mac 또는 지속적 통합 환경이라면 이 기록이 특히 중요합니다. 환경이 바뀌면 패키지 버전, 스크립트, 인증서, Xcode 설정이 달라질 수 있습니다. Xcode의 베타 및 배포 안내를 참고해 배포 단계를 분리하고, 같은 커밋으로 다시 Archive할 수 있는 상태를 유지하십시오.
소스 수정 뒤에도 깨끗한 macOS 빌드 환경이 필요하다면 원격 Mac iOS 배포 환경 점검 방법처럼 Archive와 로그를 남길 수 있는 조건부터 확인하는 편이 안전합니다. 의존성 버전 변경이 원인이라면 Swift Package Manager 의존성 관리 가이드도 함께 검토하십시오.
Windows나 Linux에서 단발성으로 파일만 확인하는 방식은 Xcode Archive, 코드 서명, 실제 업로드를 한 번에 재현하기 어렵습니다. 로컬 Mac을 별도로 구매하면 장기간 안정적인 고정 부하에는 유리하지만, 인증서와 디스크를 직접 관리해야 하고 배포용 장비를 계속 켜 두어야 합니다. 반면 CALMVPS의 원격 Mac은 필요한 기간 동안 접근 가능한 macOS 환경에서 새 Archive, 로그, 의존성 잠금 파일을 보존하며 재검증하는 선택지가 될 수 있습니다. CALMVPS의 한국어 이용 환경에서 현재 작업 방식에 맞는 접근 조건을 먼저 확인하십시오. 장기적으로 매일 대규모 빌드를 수행하거나 물리 장치와 직접 연결해야 한다면 자체 Mac이 더 적합할 수 있습니다.