App Store Connect の TestFlight タブを開くと、アップロードしたばかりのビルドに黄色い「Missing Compliance」マークがついていて、横の「Manage」を押すと「あなたのアプリは暗号化を使っていますか?」という質問画面が出る。Rork 経由で iOS のビルドを上げ始めた人がほぼ全員ここで一度止まります。私の場合、2014 年から AdMob 収益で個人開発を続けている関係でこの確認画面を年に何十回も見ていて、最初の数年は毎回手作業で答えていました。
実際にはこの質問、ほとんどの Rork 製アプリでは Info.plist に key を一行加えるだけで恒久的に消せます。逆に放置しておくと外部テスターへの配布もストア提出もすべて止まったままになるので、累計 5,000 万 DL ぶんの個人運営を回している身としては、最初の 1 本を出した直後に必ず潰しておきたい類の落とし穴です。
Rork で出力した iOS プロジェクトを前提に、警告が出る背景と app.json 経由での修正、TestFlight 側で残った状態を整理する順番までを通しで追っていきます。
なぜ「Missing Compliance」が毎回出るのか
iOS アプリは米国の輸出管理規則(EAR)の対象で、暗号化機能を含むソフトウェアを米国外に配布する場合、年に一度の自己分類報告などの義務があるとされています。App Store Connect の「Manage」画面は、その情報を Apple が代行して扱うための入口です。
質問の本体は二つあります。第一に「アプリは暗号化を使うか」。第二に「使う場合、その暗号化は exempt(適用除外)に該当するか」。Skip ボタンを押しても、その質問は次のビルドにも、別のテスター招待にも、ストア提出にも常について回ります。Skip は一時的な保留で、解消ではありません。
ここで重要なのは、HTTPS や iOS 標準 API 経由の暗号化、ユーザー認証用のローカルハッシュ程度であれば exempt 扱いになる、という Apple の整理です。Rork で生成したアプリで fetch() を投げているだけ、AdMob のリクエストが TLS を使っているだけ、Firebase Remote Config が HTTPS で取りに行くだけ、というケースは exempt のラインに収まります。
つまり、ほとんどの Rork 製アプリでは「使うけれど exempt」あるいは「専用の暗号化は使っていない」と宣言できる状態にあって、それを Info.plist で機械的に伝えれば毎回の確認は不要になります。
一行で済ませる Info.plist の key
Apple が用意している恒久解決用の key は ITSAppUsesNonExemptEncryption です。
「Non Exempt な暗号化を使っているか」という意味なので、値の意味が直感と逆になります。
falseを入れる → 「適用除外でない暗号化は使っていない」と宣言。質問画面が出なくなるtrueを入れる → 「適用除外でない暗号化を使う」と宣言。別途 ECCN 番号や ERN の登録が必要- key 自体がない → 毎回 App Store Connect で手動回答を求められる
判断軸はシンプルです。アプリ内で独自実装の暗号化ライブラリを抱えていたり、エクスポート可能な暗号機能を売りにしていたりしなければ、false で問題になりません。Rork で出した壁紙アプリ・ユーティリティ・AI チャットなどはほぼ全部このパターンに収まります。
判断に迷ったときは Apple のフォーラム回答や、自分の使っているサードパーティ SDK(AdMob、Firebase、RevenueCat、Sentry 等)の公式 FAQ の「export compliance」項目を一通り読んでから判断するのが安全です。私自身、新しい SDK を入れたタイミングだけは念のため確認する習慣にしています。
Rork プロジェクトでの設定方法
Rork は内部的に Expo の app.json(または app.config.js)で Info.plist を組み立てるので、Xcode の plist を直接触らずに済みます。ios.infoPlist 配下に key を追加するのが正攻法です。
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.4.0",
"ios": {
"bundleIdentifier": "com.example.myapp",
"buildNumber": "1",
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false
}
}
}
}JSON の false リテラルを直接書ける点に注意してください。"false" と文字列で書くと plist 側でも文字列として扱われ、Apple のチェックが反応せず警告が消えません。これは初回に必ず踏むつまずきどころで、私も最初は同じことをやりました。
app.config.js 派の人は次のように書けます。
export default {
expo: {
name: "MyApp",
ios: {
infoPlist: {
ITSAppUsesNonExemptEncryption: false,
},
},
},
};EAS Build でビルドしている場合は、設定変更後に eas build --platform ios --profile production を回すだけで Info.plist に反映されます。Rork 内のクラウドビルドを使っている場合も、app.json を保存して次回ビルドを実行すれば自動で組み込まれます。
既にアップロード済みのビルドを片付ける
key を追加するのは次のビルドからの話なので、既に「Missing Compliance」のついたビルドが App Store Connect に残っている状態は別途処理する必要があります。古いビルドを残したままだと、内部テスターには配れても外部テスターやストア提出ができないので、ここで片付けておきます。
App Store Connect の TestFlight タブで対象ビルドを選び、「Export Compliance Information」の「Manage」をクリックします。
- 「Does your app use encryption?」→ Yes
- 「Does your app qualify for any of the exemptions?」→ Yes
- 「Does your app implement any standard encryption algorithms instead of, or in addition to, using or accessing the encryption within Apple's operating system?」→ No
この組み合わせで保存すれば、そのビルドは「Missing Compliance」が外れます。実態として HTTPS や iOS 標準暗号しか使っていない Rork 製アプリは大半このルートに収まります。独自の暗号化を実装している自覚があるなら、ここで Yes に進んで ERN と年次自己分類報告の準備に入る判断になります。
annual self-classification report の扱い
ITSAppUsesNonExemptEncryption=true 側に進んだ場合だけ、米国 BIS への年次自己分類報告の提出義務が発生する可能性があります。false 側に進めば、この報告は不要というのが Apple のガイダンスです。
ここを過剰に心配して全部 Skip にしてしまうと、結局リリースが毎回手動承認待ちになります。私の運用では、HTTPS 通信しかしないアプリ群は迷わず false、独自暗号を実装した一部アプリだけ年次報告のフローを別途回すという二段構成で割り切っています。
設定後の確認
app.json 変更後に EAS Build を回し、新しい build を TestFlight にアップロードしたら、Activity または TestFlight タブに表示されたビルドの右側に黄色いマークが付いていないことを確認します。「Missing Compliance」が出ないこと、外部テスターのグループにそのまま割り当て可能になっていることがゴールです。
XCode 経由でローカルビルドを上げている場合は、ビルド前に ios/MyApp/Info.plist 内に ITSAppUsesNonExemptEncryption の項目が <false/> として書かれているか直接確認できます。Rork や Expo のビルドキャッシュが残っていて反映されないと感じたら、expo prebuild --clean を一度通すと確実です。
設定が一度入ってしまえば、以降のリリースで Manage 画面を開く必要は完全になくなります。6 本の壁紙アプリを並行配信している私の運用でも、これを入れて以降は毎週の配信フローから Export Compliance の手作業が消えました。配信頻度が高い個人開発者ほど効果が大きい一行修正だと考えています。
新しい iOS ビルドを今日中に上げる予定があるなら、その前に app.json の ios.infoPlist に ITSAppUsesNonExemptEncryption: false を加えて回してみてください。