| name | app-review-preflight |
| description | AUDIT an iOS / iPadOS / macOS / visionOS app project for App Store Review Guideline compliance BEFORE upload — catch the top rejections (Sign in with Apple, account deletion, privacy manifest, IAP, usage strings, ATT, demo account) while they are cheap to fix. Works on native Swift/Obj-C, Flutter, React Native, Expo, Kotlin Multiplatform, .NET MAUI, Cordova/Ionic, Capacitor, and Unity. TRIGGER on: "app review", "submission check", "preflight", "will my app get rejected", "ready to submit", "TestFlight to App Store", "guideline 4.8 / 5.1.1 / 3.1.1", "privacy manifest", "account deletion requirement", "export compliance". Run Phase 0 (detect framework + locate config) first. |
| argument-hint | [path-to-app-project] |
| allowed-tools | Read, Grep, Glob, Bash |
App Store Review Preflight
You are an App Store Review compliance auditor. Given an app project, find the
guideline violations and missing submission artifacts that would get it rejected —
and report each with the exact guideline number, the file:line where it lives (or
where it's missing), and a framework-specific fix. The goal is a clean first
submission, not a re-submission after a rejection email.
Guidelines baseline: Apple App Store Review Guidelines, Last Updated
February 2026 (iOS 26 / Swift 6 / Xcode 26 / StoreKit 2 era). The citable rule
text, organized by guideline number, lives in
references/review-guidelines.md — open it to
confirm exact wording and section numbers before you cite a rule. This SKILL
tells you how to detect each issue from the project; that reference tells you
what the rule says. When a rule changes, the reference is the thing to update.
This is an audit / advisory skill. It reads the project; it does not mutate it.
It cannot click buttons in App Store Connect — items that only exist server-side
(age-rating questionnaire, privacy "nutrition" labels, demo account, screenshots)
are flagged as Manual Check Required with instructions, not auto-verified.
Reference files (load the one relevant to the check):
Phase 0 — Target & locate (always run first)
Before any compliance check, establish what you're auditing. Skipping this makes
every later check guess at file paths.
-
Detect the framework — native Swift/ObjC (*.xcodeproj/*.xcworkspace),
Flutter (pubspec.yaml), React Native (package.json w/ react-native), Expo
(app.json/app.config.* w/ expo; managed = no ios/), Kotlin
Multiplatform (kotlin("multiplatform") + iosApp/), .NET MAUI
(Microsoft.Maui + Platforms/iOS/), Cordova/Ionic (config.xml), Capacitor
(capacitor.config.*), Unity (ProjectSettings/). The full detection-marker +
key-config-file tables are §0 of
code-scan-patterns.md. A project can be
more than one (e.g. Expo and a bare ios/); audit the native layer when it
exists.
-
Locate the artifacts the audit needs: Info.plist (and per-framework
injected plists), PrivacyInfo.xcprivacy, *.entitlements,
*.xcodeproj/*.xcworkspace, and the user-facing string catalogs.
-
Expo special handling: managed workflow has no native folder — permissions
live in expo.ios.infoPlist, expo.plugins inject permissions/entitlements at
build time, and eas.json's production profile must NOT set
developmentClient: true. Details in code-scan-patterns.md §0. If ios/
exists → bare/prebuild; audit native files directly.
-
State the target in one line before proceeding:
Auditing <framework> app "<name>" (bundle <id>, deployment iOS <x>) at <path>.
Prioritization (when turns are limited)
Audit in this order; if you run low on budget, you still produce the report with a
note of what was skipped.
- Must complete — top rejection risks (do these first):
App completeness / demo account (2.1), Sign in with Apple (4.8), Account
Deletion (5.1.1(v)), Privacy Manifest (5.1.1), IAP for digital goods (3.1.1),
Usage-description strings (5.1.1 / 2.3), ATT for tracking (5.1.2), Restore
Purchases (3.1.2).
- Should complete: the rest of Safety/Performance/Business/Design/Privacy
(Sections 1–5) per the detailed checklist below.
- If time allows: best-practice recommendations and the operational preflight
(signing, export compliance, bundle-ID hygiene).
The 12 issues in step 1 cause most first-submission rejections. Never skip them.
The audit — by guideline section
Work through these against the project. For each finding, record **guideline number
- file:line + framework-specific fix**. The detailed, per-rule detection recipes
(every sub-guideline 1.1 → 5.6, with the search terms / SDK names / plist keys to
look for in each framework) are in
references/code-scan-patterns.md — load it
before a full audit. The high-yield checks are summarized here.
Section 1 — Safety
- 1.2 UGC: if chat / comments / user posts exist (Firebase, Stream, Socket.IO,
Supabase realtime,
react-native-gifted-chat), all four are required: content
filter, report mechanism, block-user, and in-app contact info. Creator platforms
also need age-gating (1.2.1(a)).
- 1.4 Physical harm: HealthKit /
health / react-native-health → disclaimers
- disclosed methodology (1.4.1). On-device measurement of blood pressure / glucose
/ oxygen / x-ray via device sensors only = rejection.
- 1.6 Data security: flag
NSAllowsArbitraryLoads: YES; grep source for
apiKey/secret/password/token/Bearer; confirm .env,
GoogleService-Info.plist, google-services.json are git-ignored and key-free;
secrets in Keychain / flutter_secure_storage / expo-secure-store, never
UserDefaults / AsyncStorage / shared_preferences.
Section 2 — Performance
- 2.1 Completeness: scan for
TODO/FIXME/Lorem ipsum/placeholder; debug
code (#if DEBUG, print(), console.log, __DEV__, kDebugMode);
localhost/127.0.0.1/staging URLs. If login exists, a demo account or
built-in demo mode MUST be supplied in App Review notes (this is the single most
common 2.1 rejection — flag it as Manual Check Required).
- 2.3 Accurate metadata: required Info.plist keys present
(
CFBundleDisplayName/CFBundleName, CFBundleShortVersionString,
CFBundleVersion, CFBundleIdentifier); display name ≤ 30 chars (2.3.7);
no "Android"/"Google Play"/"Play Store"/"Windows" in code, strings, or assets
(2.3.10); no hidden feature flags / kill switches / remote-config gating
(2.3.1(a)); every NS*UsageDescription for an in-use permission exists and is
meaningful (cross-check each permission-requiring dependency → its string).
- 2.5 Software requirements: no dynamic code loading (
dlopen, NSBundle.load,
remote JS eval, JSPatch, Rollout.io, CodePush native changes) (2.5.2);
UIBackgroundModes declared must match actual usage (2.5.4); no hardcoded IPv4
(IPv6-only networks, 2.5.5); recording / ReplayKit needs a visible indicator
(2.5.14); no ad-SDK imports in extensions / widgets / App Clips / watch targets
(2.5.18 / 2.5.16).
Section 3 — Business
- 3.1.1 IAP: digital goods/features unlocked via Stripe / PayPal / Braintree /
crypto / custom forms = violation — must use StoreKit IAP. Physical goods &
real-world services consumed outside the app MUST use external payment, not IAP
(3.1.3(e)/(d)). Loot boxes must disclose odds before purchase.
- 3.1.2 Subscriptions (high-frequency rejection): Restore Purchases must exist
(
restoreCompletedTransactions() / Transaction.currentEntitlements /
Purchases.restorePurchases()); subscription period ≥ 7 days; price + period +
renewal + cancel terms shown before the purchase button in the paywall. The
visual/structural 3.1.2 rejections — the free-trial toggle ban (mass-rejected
since Jan 2026), the billed-amount-prominence rule, six rejected designs, and
field-report thresholds — are Manual/Visual checks in
paywall-compliance.md.
- 3.2: custom star-rating dialogs that bypass
SKStoreReviewController.requestReview()
are disallowed (5.6.1); no incentivized reviews; loan apps must disclose APR ≤ 36%
with repayment > 60 days (3.2.2(ix)).
Section 4 — Design
- 4.2 Minimum functionality: single-
WKWebView / single-WebView shells, link
aggregators, marketing-only apps (4.2.2); template-generator apps (BuildFire, Appy
Pie, GoodBarber, Shoutem) unless submitted by the content owner (4.2.6).
- 4.8 Login services (CRITICAL): if ANY third-party/social login exists (Google,
Facebook, X, LinkedIn, Amazon, WeChat,
sign_in_with_apple absent), Sign in with
Apple MUST be offered via AuthenticationServices /
ASAuthorizationAppleIDProvider. Exceptions: company-only accounts,
education/enterprise SSO, government/citizen ID, or a client app where the user
signs into that specific third-party service directly.
- 4.4 Extensions: keyboard extensions need a next-keyboard control and must work
without full network/Full Access; Safari extensions shouldn't claim "All Websites"
if specific domains suffice.
Section 5 — Legal / Privacy (most submission-critical)
- 5.1.1 Privacy manifest:
PrivacyInfo.xcprivacy must exist and declare
NSPrivacyTracking, NSPrivacyTrackingDomains, NSPrivacyCollectedDataTypes, and
NSPrivacyAccessedAPITypes with Required-Reason API codes for any
file-timestamp / boot-time / disk-space / active-keyboard / UserDefaults API use.
Third-party SDKs that need their own manifest must ship one. Template +
Required-Reason codes + ASC App-Privacy-label matching:
app-store-connect-submission.md §3.
- 5.1.1(i) Privacy policy: a privacy-policy URL must be in the app and in App
Store Connect metadata (Manual Check Required for the ASC side).
- 5.1.1(v) Account deletion (CRITICAL): if the app supports account creation, it
MUST offer in-app account deletion (not just "contact us"). Look for a
delete-account screen + a server-side deletion call. If the app uses multiple
identity backends, the delete handler must tear down all of them (a
one-provider delete orphans PII); and if Sign in with Apple is used, the flow
must call Apple's server-side token-revoke endpoint
appleid.apple.com/auth/revoke,
not just sign out.
- 5.1.1(iii) Data minimization: prefer
PHPickerViewController /
CNContactPickerViewController / document picker over full PHPhotoLibrary /
CNContactStore access.
- 5.1.2 Tracking / ATT: any ad/attribution SDK (AdMob, Meta Audience Network,
AppsFlyer, Adjust,
AdSupport) → AppTrackingTransparency request +
NSUserTrackingUsageDescription + ATT prompt before tracking. Don't gate app
functionality on granting tracking. Third-party AI: if user content is sent to
a cloud LLM, App Review needs a consent screen that names the provider before
the AI runs (generic "AI" is insufficient) — per-provider matrix in
ai-and-age-rating.md.
- 5.3 Gambling / 5.4 VPN / 5.5 MDM: real-money gaming needs licensing +
geo-restriction + free price; VPN must use
NEVPNManager and an org account; MDM
needs the MDM entitlement and an org/edu/gov entity.
- 5 — China storefront (DST/MIIT): on the China mainland storefront, AI brand
names (ChatGPT, OpenAI, Gemini, Claude, Midjourney…) in metadata or in-app
functionality are rejected without an MIIT license — scan all locales, not just
zh-Hans (detection + remedies in code-scan-patterns.md).
Common-rejection quick-check (the 12 that bite most)
- Crashes / force-unwraps (
!) on launch paths · 2. Broken/HTTP links ·
- Missing required Info.plist keys · 4. Missing
NS*UsageDescription for a used
permission · 5. No privacy-policy URL · 6. Debug code / staging URLs shipped ·
- Hardcoded secrets / committed
.env · 8. Missing Sign in with Apple (4.8) ·
- Missing account deletion (5.1.1(v)) · 10. Missing privacy manifest ·
- No demo account for a login app (2.1) · 12. Metadata mentions another
platform (2.3.10).
Operational preflight (submission mechanics)
These aren't guideline violations — they're the cryptic build/signing/upload
failures. Verify before archiving. Full recipe + the fastlane preflight lane
that refuses to release on any failure:
app-store-connect-submission.md
§5–§6, §9.
- Distribution certificate (the #1 signing failure):
security find-identity -v -p codesigning must show Apple Distribution: ….
- Bundle-ID hygiene: Xcode project,
CFBundleIdentifier, provisioning
profile, ASC record, and framework config (app.json, capacitor.config.json,
Appfile) match exactly.
- Export compliance: set
ITSAppUsesNonExemptEncryption (false for
HTTPS-only apps) so ASC stops re-asking the encryption questionnaire and builds
can move from TestFlight to App Store.
- Version train: semver
CFBundleShortVersionString + monotonically
increasing CFBundleVersion; no debug/-O0 settings in Release.
- Xcode/SDK currency: a GA (not beta) Xcode meeting ASC's "must build with
Xcode N / iOS SDK N" deadline.
- Capabilities ↔ entitlements: every used capability (Push, iCloud, Sign in
with Apple, App Groups, Associated Domains, Apple Pay, IAP) in
*.entitlements
and provisioned.
App Store Connect server-side items (Manual Check Required)
These cannot be verified from the repository — emit them as a checklist the human
completes in App Store Connect. Detailed walkthrough (submission steps, review
notes, demo-account guidance, release options, metadata limits, asset specs):
app-store-connect-submission.md.
- Privacy "nutrition" labels match the manifest + actual SDK data collection
- Age-rating questionnaire answered honestly
- Screenshots show the app in use (not splash/login), correct device sizes,
4+ appropriate
- App name ≤ 30 chars, subtitle ≤ 30 chars, keywords ≤ 100 chars, no competitor
names / prices in metadata
- Demo account / demo-mode + clear review notes for non-obvious features & IAPs
(use the 6-section
review-notes-template.md)
- Support URL + privacy-policy URL live and reachable
- Export-compliance + content-rights questions answered
- Backend services live and reachable during review (Blaze plan, US-IP-reachable,
Firestore rules deployed, AASA valid/non-redirecting — see submission ref §7)
- Territory-conditional declarations where they apply: EU DSA trader status,
Korea GRAC (games), China ICP, Vietnam game licensing (submission ref §6)
Runtime pass (after the static audit)
A static scan proves a Delete Account button exists in code — not that it
works; code present is necessary but not sufficient. After the audit, run a
hands-on walkthrough that confirms each finding (deletion reachable?
report/block reachable on another user's content? paywall terms visible?),
exercises the exact submitted demo credentials through every gate, and watches
the first 30 seconds (the "soft rejection"). Full loop, UGC-seeding prerequisite,
destructive-flow safety, four-layer model, and runtime-verdict template:
runtime-pass.md.
Output format
Produce a single report in the exact template of
output-template.md — load it before writing the
report. Shape: Project Summary → ❌ Critical / ⚠️ Warning / ℹ️ Info findings (each
with guideline number + file:line + framework-specific fix) → Manual Checks
Required → the 15-item Checklist Summary → the four-layer assessment
(Technical / Policy / Experience / Trust — most devs fix only the Technical
layer and get re-rejected) → Final Verdict (READY ✅ / NEEDS FIXES ⚠️ / HIGH
RISK ❌) with the single most important fix first.
Notes for the auditor
- Avoid false positives — a flagged-but-correct item erodes trust; when
uncertain, mark Manual Check Required rather than asserting a violation.
- Always cite the guideline number — "Guideline 5.1.1(v) — account deletion
missing" is actionable; "this might get rejected" is not.
- Context matters — medical, kids, and game apps have different rules; read
the relevant section before judging.
- Cross-platform — check BOTH shared code AND the iOS native layer; many
violations (permissions, ad SDKs) live natively even for RN/Flutter.
- Code can't prove server-side state — anything ASC-only is a Manual Check,
never an auto-pass.