app-store-submission-auditor

SKILL.md

App Store Submission Auditor

Scans a project folder for App Store rejection risks. No back-and-forth — read the code, find the problems, report them. Adapts to the user's level and build state.


First-time developer

If user mentions "first app", "first time submitting", "never submitted before", or "new developer" → load references/first-time-dev.md and run that checklist first, then continue with the full audit below.


Config — check before scanning

Look for config.json in the skill directory. If it exists, read it for saved preferences (stack, app type, subscription status, EU distribution, previous audit results).

If it does NOT exist, create it after the first audit with what was detected:

{
  "mode": "vibe_coder",
  "stack": "flutter",
  "has_subscriptions": true,
  "has_social_features": true,
  "has_location": true,
  "eu_distribution": false,
  "first_time_submitter": false,
  "last_audit": "2026-03-19",
  "issues_fixed": []
}

On subsequent runs: load config, skip re-detection for anything already known, and note "Last audit: [date] — you fixed [N] issues since then." if issues_fixed is populated.


Gotchas — common false positives to avoid

These are the things Claude most often gets wrong when running this audit. Check these before flagging any issue.

1. Stripe for physical goods is fine Only flag Stripe/PayPal as a violation if it's being used for digital goods or features. Stripe for physical item swaps, marketplace escrow, or real-world services = NOT a violation. Only flag if credits/payments unlock in-app digital features.

2. WebView for help/legal is fine Only flag WebView as a web wrapper if it's the PRIMARY UI. A WebView loading a terms of service page, help article, or external link inside an otherwise native app is fine and expected. Only flag if fewer than ~5 native screens exist and WebView loads the main content.

3. NSUserTrackingUsageDescription without tracking SDKs If the key exists but no known tracking SDKs are found, flag as "unnecessary ATT prompt" (P1), not as a missing string. Don't flag it as missing — it's present. Flag it as potentially causing rejection for prompting unnecessarily.

4. Firebase Analytics ≠ automatic rejection Firebase Analytics collecting data is not a rejection by itself. It only becomes a problem if it's undisclosed in the privacy policy and App Privacy label. Flag as "needs disclosure" not as a hard rejection.

5. Staging URL might be intentional Flag staging/dev URLs as ⚠️ with "confirm this isn't in your production build" — not as a guaranteed rejection. The developer may have a staging build and a production build config. Don't assume it's in the production binary without asking.

6. Location in startup ≠ always wrong If the app's CORE purpose is location-based (e.g. a map app, a nearby listings app) and location is requested near startup, that's acceptable. Only flag if non-location features could work without it and location is still requested upfront.

7. No block feature ≠ reject for apps without chat Only flag missing block user feature if the app has actual user-to-user interaction (chat, messaging, social profiles). A marketplace with listings but no direct user messaging does not need a block feature.


Step 1 — Three detections before scanning

A) Detect user mode

Vibe coder signals: mentions Claude Code / Cursor / Bolt / "vibe coding" / "built with AI", casual language, no file/framework specifics, non-developer questions.

Technical signals: names files or frameworks unprompted, pastes code, uses dev terminology, references guideline numbers.

Default if unclear: vibe coder mode.

B) Detect stack (read top-level folder)

  • pubspec.yaml → Flutter → load references/flutter-patterns.md
  • package.json + ios/ → React Native → load references/flutter-patterns.md
  • *.xcodeproj only → Native Swift

C) Detect build state (read during initial folder scan)

Mid-build signals — flag if 2+ are true:

  • More than 3 TODO, FIXME, coming soon, placeholder in user-visible strings
  • Core flows missing entirely (no auth screens, no main feature, no navigation)
  • Stub/mock function names (stubAuth(), mockPayment(), fakeLocation())
  • Fewer than ~8 meaningful screens or widgets

If mid-build signals detected → pause and ask before continuing:

"Your app looks like it might still be in progress — I'm seeing [specific signals found]. Which would be more useful right now?

A) Submission audit — I'll flag everything Apple would reject B) Build checklist — I'll tell you what to finish before worrying about submission"

  • If A: continue with full audit, note mid-build state in executive summary
  • If B: run Build Checklist mode (see end of skill)

Step 2 — Announce mode

Vibe coder:

"Running in plain English mode — no jargon, copy-paste fixes. (Developer? Just say so.)"

Technical:

"Running in developer mode — technical language, exact code diffs. (Want plain English? Just say so.)"

Switch immediately if corrected.


Step 3 — Scan these files

Read without asking. Note missing files and continue.

Flutter: ios/Runner/Info.plist · pubspec.yaml · lib/main.dart · ios/Runner/PrivacyInfo.xcprivacy · *.entitlements · files matching: *auth* *account* *delete* *user* *block* *report* *location* *geo* *permission* *payment* *purchase* *subscription* *iap* *credits* *paywall* *api* *network* *config* *env* *constants* *privacy* *policy* *eula* *terms* *legal*

Native Swift: */Info.plist · AppDelegate.swift · *.entitlements · PrivacyInfo.xcprivacy · *Account* *Auth* *Delete* *Block* *Report* *Location* *Permission* *Payment* *Purchase* *IAP* *Subscription* *Network* *API* *Config* *Constants*


Step 4 — What to look for

Collect issues silently. Output everything at once in Step 5.

Severity:

  • 🚨 P0 — Apple will reject for this
  • ⚠️ P1 — Actively checked, likely flagged
  • 💡 P2 — Won't necessarily reject, worth fixing

Permission strings

Read every NS*UsageDescription in Info.plist.

Flag:

  • Empty value → 🚨
  • Generic value ("needed for app", "required for functionality", "to improve your experience") → 🚨
  • Feature exists in code but key is missing → 🚨
  • Key exists but no matching feature in code → ⚠️ over-requesting
  • NSLocationAlwaysUsageDescription without clear background need → ⚠️
  • Tracking SDK present but NSUserTrackingUsageDescription missing → 🚨
  • PrivacyInfo.xcprivacy missing or incomplete when tracking SDKs present → ⚠️

Location permission order

Flag if location is accessed before requestPermission() is called, or at app startup when not all features need it. Check Flutter (Geolocator) and native (CLLocationManager).


Account deletion

Flag: mailto: redirect → 🚨 · web URL redirect → 🚨 · no confirmation dialog → 🚨 · only one auth provider deleted → ⚠️ · no deletion flow found anywhere → 🚨


Block user

Flag: no block function exists → 🚨 · block and report are same function → ⚠️ · block only hides in UI, not enforced at data layer → ⚠️ · block missing from profile screen OR chat screen → ⚠️


Sign in with Apple

Flag: third-party social login exists (Google, Facebook, Twitter) but no Sign in with Apple → 🚨 (required when any third-party login is offered)


Payments & IAP

Flag: Stripe/PayPal/custom for digital goods → 🚨 · credits unlocking digital features outside IAP → 🚨 · no restore purchases call found → ⚠️ · Apple EULA URL not in codebase → ⚠️ · external purchase links for digital features → 🚨 · subscription terms not shown before purchase → ⚠️


Network config

Flag: hardcoded IPv4 literals → 🚨 · http:// in production config → 🚨 · staging URLs in production build → ⚠️ · NSAllowsArbitraryLoads: true → ⚠️


Encryption compliance

Read Info.plist for ITSAppUsesNonExemptEncryption.

Flag:

  • Key missing entirely → ⚠️ (Apple will ask during submission, blocks upload)
  • Set to true with no export compliance documentation → ⚠️

Vibe coder: "Your app needs to declare whether it uses encryption. If you only use standard HTTPS (which most apps do), add this to your Info.plist: Tell Claude Code: 'Add ITSAppUsesNonExemptEncryption with value NO to Info.plist'"

Technical: ITSAppUsesNonExemptEncryption missing from Info.plist. If app uses only HTTPS → set to NO. If custom encryption → set to YES and upload export compliance documentation in App Store Connect.


Debug artifacts

Flag: debugShowCheckedModeBanner: true → ⚠️ · "beta"/"demo"/"coming soon"/ "placeholder"/"TODO" in user-visible strings → ⚠️ · blank screens with no empty state UI → ⚠️ · excessive print statements → 💡


SDK inventory

Read pubspec.yaml / package.json. Flag undisclosed data-collecting SDKs: firebase_analytics facebook_* amplitude_flutter mixpanel_flutter appsflyer_sdk adjust_sdk → ⚠️ each (needs disclosure, not a rejection itself)


Regulated content

Scan user-visible strings. Flag: medical claims ("treats", "cures", "clinically proven") → ⚠️ · personalized financial advice without disclaimers → ⚠️ · safety/emergency features without disclaimers → ⚠️


ATT / tracking prompt

Read Info.plist and pubspec.yaml / package.json.

Flag if:

  • Tracking SDK present but NSUserTrackingUsageDescription missing → 🚨
  • NSUserTrackingUsageDescription present but vague → 🚨
  • PrivacyInfo.xcprivacy missing when tracking SDKs present → ⚠️
  • ATT requested at app launch before user has experienced any value → ⚠️
  • NSUserTrackingUsageDescription present but NO tracking SDKs found → ⚠️ (unnecessary ATT prompt — Apple rejects apps that ask for tracking without actually tracking)

Flutter: ATT must use app_tracking_transparency package. Flag if tracking SDKs exist but this package is absent from pubspec.yaml.


Web wrapper detection

Flag: webview_flutter / flutter_inappwebview as primary UI with fewer than ~5 native screens → 🚨 · WKWebView loading external URL as entire app → 🚨 · all navigation inside WebView with no native nav → 🚨

Not flagged: WebView used for a single supplemental feature alongside native functionality.


2025/2026 requirements

Flag:

  • New age rating tiers: if app has UGC, chat, or advertising — old 12+ may now need 13+/16+ under January 2026 updated questionnaire → ⚠️ (note: verify in App Store Connect)
  • External AI consent: if app sends personal user data to an external AI service and no consent modal found in code → ⚠️
  • Swift 6 (native only): ATT and StoreKit calls not marked @MainActor → 💡

Step 5 — Output

Output all sections at once after the full scan.


Language rules by mode

Vibe coder: Plain English. Explain what the thing is before saying it's wrong. Claude Code prompts specific to actual file and function found — not generic templates.

Format: Tell Claude Code: "[specific instruction referencing actual file/function found]"

Technical: Dev terminology fine. Exact code diffs referencing actual file and line. Guideline numbers where known.


Section 1 — Executive summary

[VIBE CODER]
YOUR APP AUDIT
══════════════
What your app does: [inferred — 1 sentence]
Issues found: [X] — [Y] will get you rejected, [Z] are risky

3 biggest things to fix:
• [top issue in plain English]
• [second]
• [third]

3 quick wins:
• [easiest fix]
• [second]
• [third]

[TECHNICAL]
AUDIT SUMMARY
═════════════
Stack: [stack] · Files scanned: [N] · Issues: [X] ([Y] P0, [Z] P1)
App: [inferred purpose]
Top risks: [top 3] · Fast wins: [top 3]

Section 2 — Risk register

Both modes get this table. Adapt language in cells to detected mode. Omit rows with no issues. Use Assumption — verify manually when no code evidence.

| Priority | Area | Finding | Evidence | Fix | Effort | Confidence |
|----------|------|---------|----------|-----|--------|------------|
| P0 🚨 | [area] | [finding] | [file:line or function] | [fix] | S/M/L | High/Med/Low |

Effort: S = <1hr · M = half day · L = multi-day


Section 3 — Detailed findings

Group by area. Skip clean areas entirely.

Each finding: what was found (specific file/function) · why Apple rejects it · exact fix Vibe: plain English + specific Claude Code prompt Technical: code diff


Section 4 — Reviewer experience

Simulate what Apple's reviewer does. Mark each: ✅ likely passes · ⚠️ risky · ❌ will fail · ⬜ can't determine from code

Install & launch
  [?] App opens without crashing

First run
  [?] Clear what app does on first screen
  [?] Permissions explained before requested
  [?] No dead ends or blank screens

Core features
  [?] Reviewer reaches main features without special setup
  [?] Test account path exists (if login required)

Purchases (if applicable)
  [?] Paywall shows price, billing, terms
  [?] Restore purchases visible
  [?] No external payment links for digital features

Account (if applicable)
  [?] Account creation works
  [?] Account deletion findable and works in-app

Links & legal
  [?] Support URL loads · Privacy policy loads · EULA accessible

Edge cases
  [?] No internet → graceful error (not crash)
  [?] Empty states have UI

Section 5 — Draft reviewer notes

Generate ready-to-paste text for App Store Connect → Notes for App Review. Infer every section from actual code. Only use [fill in] for things Claude can't know.

--- NOTES FOR APP REVIEW ---

ABOUT THIS APP
[Inferred from code]

TEST ACCOUNT
Email:    [fill in]
Password: [fill in]

HOW TO REACH KEY FEATURES
[Steps inferred from navigation structure]

PERMISSIONS
[Each NS*UsageDescription found — feature it's used for]

ACCOUNT DELETION
[Path inferred from code, or omit if not found]

[Any unusual flows, credits systems, or gated features]
--- END NOTES ---

Section 6 — Manual checklist + 4 post-scan questions

After the automated findings, end with this section.

[VIBE CODER]
THINGS TO CHECK YOURSELF
════════════════════════
In App Store Connect:
  □ Fill in the App Privacy section
  □ Age rating — updated tiers as of Jan 2026: check if your app
    needs 13+ or 16+ (not just 12+) if it has chat, UGC, or ads
  □ All subscription plans in ONE group
  □ Add Apple's EULA link to your App Store description:
    apple.com/legal/internet-services/itunes/dev/stdeula/

Your listing:
  □ Description matches current build
  □ Screenshots show the current version (not Figma)
  □ Support link loads · Privacy policy link loads

Before submitting:
  □ Test on a real iPhone, final build — not simulator
  □ Open on iPad — shouldn't crash
  □ Paste test account into Review Notes

Good to know:
  □ Phased release — after approval you can roll out to
    1% → 2% → 5% → 10% → 50% → 100% instead of everyone at once.
    Great for catching issues before full launch.
  □ Expedited review — if it's a critical bug fix or urgent,
    you can ask Apple to fast-track it in App Store Connect
    under App Review → Contact Us → Expedite Request.

[TECHNICAL]
MANUAL CHECKS — NOT IN CODE
════════════════════════════
App Store Connect
  □ App Privacy nutrition label completed
  □ Age rating updated (Jan 2026 questionnaire — new 13+/16+/18+ tiers)
  □ All subscription tiers in single subscription group
  □ Apple EULA in App Store description
  □ ITSAppUsesNonExemptEncryption set in Info.plist
  □ EU DSA trader status verified (if distributing paid app in EU)
Metadata
  □ Description matches current build · No prices in description
  □ No "Also available on Android" references
  □ Screenshots match current build (real UI, not marketing renders)
  □ Support URL resolves · Privacy policy URL live
Device testing
  □ Physical device, release build · iPad doesn't crash
  □ IPv6 network test (Apple review network is IPv6-only)
Review Notes
  □ Demo credentials added · Reviewer notes draft pasted
Useful options
  □ Phased release available after approval (1%→2%→5%→10%→50%→100%)
  □ Expedited review available for critical fixes (App Review → Contact Us)

After the manual checklist, ask these 4 questions:

[VIBE CODER]
One last thing — a few things I can't check from your code:

  1. Have you tested the app on a real iPhone recently?
     (Not just the simulator)

  2. Do your App Store screenshots show the current
     version of your app?

  3. Have you added a test account to your Review Notes?
     (Skip this if your app doesn't require login)

  4. Is this your first time submitting to the App Store?
     (Say yes and I'll walk you through a setup checklist)

[TECHNICAL]
Four things I can't verify from code:

  1. Tested on physical device with release build?
  2. Screenshots in ASC match current build?
  3. Demo credentials in App Review Notes? (skip if no login)
  4. First submission? (yes → I'll load the first-time setup checklist)

If user answers yes to question 4 → load references/first-time-dev.md


Closing line

Vibe coder:

"[X] things to fix. Start with the 🚨 ones — guaranteed rejections. Answer the 4 questions above and fill in your test account in the reviewer notes draft, then you're ready to submit. (Say "fix [anything]" and I'll write exactly what to tell Claude Code.)"

Technical:

"[X] issues. P0s first. Answer the 4 questions, fill reviewer notes credentials. (Say "fix [issue]" for corrected code.)"

If zero code issues:

"No code-level issues found. Answer the 4 questions above and run through the manual checklist — then you're good to submit."


Re-audit

If user says "I fixed [X]" or shares updated code:

  • Re-scan only that area
  • Confirm ✅ or explain specifically what's still wrong
  • Update that item in the risk register only
  • Note: metadata rejection = fix in App Store Connect, no new build needed binary rejection = fix code, create new archive, upload new build

Build checklist mode

Run if user chose option B after mid-build detection.

YOUR BUILD CHECKLIST
════════════════════
[What the app does so far — inferred from code]

FINISH THESE FIRST
[Unfinished things found — stubs, TODOs, missing flows.
Specific file references.]

ALREADY DONE ✅
[What's clearly implemented and working]

WHEN YOU'RE DONE BUILDING
Come back and say "audit my app" — I'll check for
App Store submission then.

Tone: encouraging. No rejection warnings. Focus on what to build next.

Weekly Installs
55
GitHub Stars
18
First Seen
Mar 18, 2026
Installed on
codex54
gemini-cli53
github-copilot53
amp53
cline53
kimi-cli53