Security

    Does Apple App Review check for env variables in your build?

    A macOS Terminal running the strings command against an unzipped iOS IPA file beside an open Xcode scheme editor showing environment variable entries

    You uploaded your build to App Store Connect last week, and the same uneasy thought keeps coming back: Apple has the IPA, the reviewer has run it, and somewhere inside the build are the environment variables you set during your last archive. Does the review team see them? The honest answer is more nuanced than yes or no.

    Short answer

    Apple's App Review process does not specifically grep your iOS binary for environment variables or .env files. Review looks at runtime behavior, privacy declarations, and policy compliance, not at the contents of static string tables in your Mach-O executable. The deeper problem is that values you injected through environment variables at build time often end up as plain text inside the compiled binary anyway, fully readable by the strings command, as documented by NSHipster's analysis of secret management in iOS. Apple does not need to flag them, because anyone who downloads your app from the App Store can.

    What you should know

    • Environment variables in Xcode are a scheme setting, not a packaging mechanism. Variables set in the Run scheme are only present when launching from Xcode, per Apple's Xcode environment variable reference. They do not ship to TestFlight or the App Store.
    • Values baked into Info.plist, xcconfig, or generated Swift files during build do ship. They are stored as readable strings inside the IPA bundle.
    • App Review does not publish a check that scans for hardcoded credentials. No part of the App Review Guidelines names this behavior explicitly.
    • OWASP MASVS treats hardcoded API keys as a Storage weakness, not a review concern. See MASWE-0005 in the OWASP MAS project for the formal classification.
    • The risk is exposure after the app ships, not rejection during review. Attackers download the IPA, run strings, and pull keys in seconds.

    Does App Review actually look at environment variables in your build?

    The short answer is no, not the way a security scanner does. Apple's review team is concerned with whether your app behaves the way your metadata and Privacy Manifest describe it. Reviewers run the binary, look for crashes, check that Sign in with Apple is present when you offer other social logins, confirm your IAP flow uses StoreKit, and verify that any data collection matches your App Store Connect privacy answers. There is no public step in that process that says, extract embedded credentials and check whether you exposed them.

    That does not mean Apple is blind to bundled content. The static analysis layer that runs during build upload looks at API symbol usage, framework dependencies, and required reason declarations. If you call a Required Reason API without declaring it in your Privacy Manifest, the upload step will surface an ITMS warning, and your build may be blocked, per Apple's required reason API documentation. That warning is about API call patterns, not about strings in the binary that happen to look like AWS keys or Stripe tokens.

    If you submitted an app with an exposed OpenAI key, App Review will most likely not call it out. The build goes through, your app reaches the store, and the credential is readable by anyone who downloads the IPA from the App Store and unpacks it.

    Where do build-time env variables actually end up after Xcode compiles?

    Three places, depending on how you wired them in. Each behaves differently after the build is sealed.

    Source of env variableWhere it ends upReadable by strings on IPA?
    Xcode Run scheme environmentIn memory only during Xcode debug runsNo, not present in shipped build
    xcconfig file referenced by build settingsCompiled into Info.plist or as preprocessor macrosYes
    Generated Swift file (GYB or build script)Compiled into the Mach-O string tableYes, often as raw string literal
    .env file copied into bundle resourcesSits as plain text in the app bundleYes, trivially readable
    Fetched from backend at runtime over HTTPSNever touches the binaryNo

    The pattern is consistent: anything injected during build and referenced by your shipping code lands as a readable string inside the executable. The Run scheme environment, set in the Xcode scheme editor, is the rare safe case, because it only applies when the debugger launches the app on a connected device or simulator. It does not get archived into the .xcarchive that becomes your IPA, as described in Appcircle's guide to environment variables in iOS projects.

    This distinction trips up a lot of teams that started in web or backend work, where process.env.OPENAI_API_KEY reads from a hosted environment that never reaches the client. In iOS, the closest analogue is a value you fetch from your own backend at runtime, not a value bundled with the app.

    What happens to a .env file shipped with an iOS app?

    A .env file shipped inside the app bundle is the highest-risk pattern in this space. The file is plain UTF-8 text. It is included verbatim in the IPA. Anyone with the IPA can rename the file extension to .zip, extract the Payload directory, find your .env, and read every value. No reverse engineering required.

    Some React Native and Expo projects end up shipping a .env.production because their build script copies it into the bundle resources by default. Some Capacitor and Cordova projects do the same because the web build step bundles the entire dist directory. The fix is to add the file to your build's exclusion list (react-native-config reads .env at build time and exposes constants, rather than bundling the file itself), and to confirm with unzip -l YourApp.ipa | grep env that no .env* file survives the archive step.

    How do attackers actually pull secrets out of a shipped IPA?

    The same way a curious reviewer would, if they cared to look. The toolchain is short:

    1. Extract the IPA. Either pull from the App Store with ipatool, sideload to a jailbroken device and dump with frida-ios-dump, or download from a paid IPA mirror.
    2. Unzip. An IPA is a Zip file. Inside the Payload directory sits YourApp.app, a bundle of resources, frameworks, and the Mach-O executable.
    3. Run strings YourApp.app/YourApp | grep -E "sk-|AKIA|AIza|xoxb". This single command surfaces OpenAI, AWS, Google, and Slack keys by their well-known prefixes, as researcher Spaceraccoon documented in his writeup on hunting low-hanging credentials in iOS apps.
    4. Open Info.plist with plutil -p to find any custom keys you exposed for SDK configuration.
    5. For deeper analysis, load the executable in Ghidra or Hopper and follow references from suspicious string literals to their call sites.

    The investment is minutes, not hours. The reason teams treat hardcoded keys as a Storage class issue under OWASP MASWE-0005 is that the asymmetry between attacker effort and defender exposure is severe. Once a key is in the binary and the binary is in the App Store, the only fix is rotation at the provider.

    What is the safer way to handle env variables on iOS?

    Three patterns hold up under static analysis, in order of how much architectural rework they require.

    The first is to keep the secret on your own backend and call your backend from the iOS client, passing only short-lived session tokens. The OpenAI key, the Stripe secret key, the database admin credential never reach the device. The user signs in, your backend mints a session, and the app holds only that session.

    The second is to use Apple's Keychain for any credential the user owns (their OAuth refresh token, their session cookie, a per-device key generated on first launch). Keychain entries are encrypted with hardware-backed keys and bound to the device, so even a full file system dump on a jailbroken device cannot trivially read them, per Apple's Keychain Services documentation.

    The third, when the first two are not feasible, is to obfuscate any bundled key using a generated Swift file that XOR encodes the value at build time and decodes at runtime, the pattern NSHipster sketches out. Obfuscation slows down casual extraction, but it does not stop a determined attacker with Frida hooked into the decode function. Treat it as a delay tactic, not a solution.

    For builders shipping AI-generated iOS apps (Cursor, Replit Agent, Lovable, Windsurf), an external pre-submission scan helps catch the most common patterns before the app reaches the store. PTKD.com (https://ptkd.com) is one of the platforms that runs a static scan over compiled IPA and APK builds for credentials and OWASP MASVS storage issues, including the env variable patterns described above.

    What to watch out for

    A few patterns reliably end up shipping by accident.

    • .xcconfig files committed with real values. A teammate sets OPENAI_API_KEY = sk-real-key in Debug.xcconfig, commits, and forgets that the build references it. The value compiles into Info.plist as a string entry. Audit your shipping Info.plist with plutil -p against your archived build.
    • react-native-config misused as a runtime secret store. The library reads .env at build time and exposes constants. Anything you put in .env ends up in the bundle. Use it for build flags, not for credentials.
    • The "Apple will tell me if it is a problem" reflex. Review feedback does not name embedded secrets. Silence from the reviewer is not validation that your build is clean.
    • Forgetting that TestFlight builds carry the same risk. TestFlight installs an IPA on the tester's device. A tester with technical skill can extract it and find the keys, just like an App Store user.
    • Treating environment variable obfuscation as encryption. XOR with a hardcoded key is reversible by anyone who reads the decode function. It deters scrapers, not attackers with Frida.

    Key takeaways

    • App Review does not run a binary scan for environment variables or hardcoded credentials, and rejection on those grounds is rare. Treat that silence as the absence of evidence, not evidence of safety.
    • Run scheme environment variables are debug-only and do not ship. xcconfig values, generated Swift files, Info.plist entries, and bundled .env files all do, and they are readable from a downloaded IPA in minutes.
    • The fix is architectural: keep secrets on your backend, mint short-lived session tokens, store user credentials in Keychain. Obfuscation buys time but does not solve the problem.
    • For an external pre-submission read on compiled IPA and APK builds, some teams run their builds through PTKD.com (https://ptkd.com) for an OWASP MASVS-aligned credential scan before uploading.
    • #app-store-review
    • #env-variables
    • #ios-security
    • #hardcoded-secrets
    • #owasp-masvs
    • #ipa

    Frequently asked questions

    Does Apple ever reject apps for hardcoded API keys in the binary?
    Rarely, and not as a documented review check. App Review focuses on runtime behavior, Privacy Manifest accuracy, IAP compliance, and the App Review Guidelines themselves. There is no public step that scans your Mach-O binary for credential patterns. Rejections happen if the embedded key triggers a behavioral issue (a misconfigured analytics SDK violating tracking transparency, for instance), not because Apple grepped your IPA for sk- prefixes.
    If env vars work locally, why do they disappear in TestFlight builds?
    Because Xcode scheme environment variables are tied to the Run action, set in the scheme editor, and only apply when launching from the debugger. The Archive action that produces your TestFlight or App Store build does not consume them. Anything you need at runtime in a shipped build has to come from a different source: an xcconfig file, a generated Swift constant, an Info.plist entry, or a network call to your own backend.
    Is an obfuscated key safer than a plain hardcoded key?
    Slightly, but not enough to count as a real defense. XOR encoding or salt-based string obfuscation slows down the strings command and casual scraping, but it does not stop anyone running Frida to hook the decoder function and dump the cleartext value at runtime. NSHipster frames obfuscation as one step on a path that ends with not embedding the key at all. Treat it as a speed bump.
    What is the correct way to handle the OpenAI API key in an iOS app?
    Keep it on your backend. The iOS client sends the user's request to a backend endpoint, the backend authenticates the user with a short-lived session token, then calls OpenAI server to server with the real key. The phone never holds the OpenAI credential. This pattern handles rotation, rate limiting, abuse blocking, and audit logging in one place, and it is the only design that survives a determined attacker pulling your IPA.
    Can Apple Keychain hold an API key that ships with the app?
    Keychain is designed for credentials the device owns or earns at runtime: a user's OAuth refresh token, a session cookie, a per-device hardware-bound key. If the app's first launch populates Keychain with a hardcoded value bundled in the binary, the value still sat in the binary at install time and is recoverable from the IPA. Keychain protects values created on the device, not values shipped with it.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free