Luma Commons mobile app programming company
    ios engineering

    Why Do the Same Security Failures Show Up in Every iOS Audit?

    NN
    Nikhil Nangia
    August 1, 2025
    12 min read
    Checklist of five common iOS security audit failures with warning icons on a dark background

    Why Do the Same Security Failures Show Up in Every iOS Audit?


    TL;DR / Key Takeaways
    - Five security issues appear in nearly every iOS codebase I audit: hardcoded secrets, PII in UserDefaults, missing certificate pinning, non-expiring auth tokens, and sensitive data in logs.
    - These aren't exotic attack vectors. They're basic hygiene problems caused by deadline pressure, and they're exactly what attackers check first.
    - Each one is fixable in days using the iOS Keychain, encrypted storage, short-lived tokens, and proper logging frameworks.
    - According to IBM's 2024 Cost of a Data Breach report, the average mobile-related breach costs $4.88 million -- making prevention far cheaper than remediation.

    I've reviewed dozens of iOS codebases for security. Five issues show up in almost every one.


    Not sometimes. Not occasionally. Almost every single time. These aren't sophisticated attack vectors or novel zero-day vulnerabilities. They're basic hygiene issues that get skipped because the sprint was tight, because "we'll fix it later," because nobody on the team owned security as their primary responsibility.


    According to the Verizon 2024 Data Breach Investigations Report, 68% of breaches involve a human element such as social engineering or misconfiguration. Mobile apps are no exception. I started keeping a tally about three years ago when I noticed the pattern during a security audit for a health tech startup. Their app had all five. A fintech client the following month had four out of five. The Series B company after that, all five again.


    Here's what they are, why they happen, and how to fix them.


    What Secrets Are Hiding in Your App Binary?


    The single most common finding, and the one that makes me wince every time: hardcoded secrets sitting in the compiled binary as plain string literals. API keys, client secrets, third-party SDK tokens, sometimes even database credentials.


    The short answer: if you've ever dropped an API key into a constants file "just to get the integration working," it's probably still there.


    During early development, someone adds an API key to ship a feature. It works. Nobody goes back to move it. Months later, the app has a dozen hardcoded secrets scattered across the codebase. The OWASP Mobile Top 10 lists "Insecure Data Storage" as the #2 mobile security risk, and hardcoded secrets are a textbook example.


    Why this matters: Anyone can download your app from the App Store and run `strings` on the binary. No jailbreaking required, no reverse engineering expertise, just a single terminal command. A 2023 Symantec study found that 73% of the top 100 iOS and Android apps contained at least one hardcoded API key. I've personally seen hardcoded keys that gave direct access to production databases, keys that allowed sending push notifications to every user, and an admin token that bypassed authentication entirely.


    How to fix it:


  1. Deliver secrets from your backend after authentication
  2. Store unavoidable on-device secrets in the iOS Keychain, never in UserDefaults or plist files
  3. Use your backend as a proxy for third-party SDK calls where possible
  4. Add a pre-commit hook or CI step using tools like trufflehog or gitleaks to catch secret patterns before they reach your repository

  5. Is PII Safe in UserDefaults?


    No. Not even close. UserDefaults is a simple key-value store that requires no setup, and that convenience is exactly why sensitive data ends up there.


    A developer needs to persist a user's email for pre-filling a login form. Or an account balance for a widget. Or a full profile object including name, phone, and address. UserDefaults is the fastest path, so that's where it goes. The developer might know it's not ideal, but the ticket says "persist user profile" and the sprint ends Friday.


    The reality: UserDefaults is stored as a plain XML plist file on the device filesystem. On a jailbroken device, it's trivially accessible. Even on non-jailbroken devices, backups (including iCloud backups not using end-to-end encryption) include UserDefaults data. According to Apple's own Platform Security Guide, data protection classes exist specifically because on-device storage without encryption is insufficient for sensitive content.


    Beyond UserDefaults, I regularly find apps writing sensitive data to plain text files in the documents directory, caching PII in unencrypted SQLite databases, or storing images of identity documents in the cache directory with zero protection. The NIST Mobile Security Guidelines (SP 800-163) explicitly recommend encrypting all sensitive data at rest on mobile devices.


    Data classification guide:


    Data TypeStorage MethodProtection Level
    Theme settings, onboarding flagsUserDefaultsNone needed
    Email, name, phone numberKeychainEncrypted at rest
    Financial data, health recordsEncrypted DB (SQLCipher)Encrypted + file protection
    Identity documents, card imagesEncrypted file + `completeFileProtection`Encrypted + locked-device protection

    Audit your data storage regularly. What starts as "just an email address" in UserDefaults grows into a full user profile over time. This is especially critical if you're building in fintech or health tech, where regulations like PCI-DSS and HIPAA set strict requirements.


    Why Does Missing Certificate Pinning Still Catch Teams Off Guard?


    Certificate pinning validates that your app is communicating with your actual server, not an impersonator with a valid certificate. Without it, anyone on the same network as your user can run a man-in-the-middle attack with a tool like Charles Proxy or mitmproxy.


    TLS feels secure. The connection is encrypted. Developers reasonably assume HTTPS means the connection is safe. It is safe against passive eavesdropping. But without pinning, it's not safe against an active attacker presenting their own certificate, which is what happens on compromised networks, corporate proxies, or targeted attacks.


    The other reason teams skip it: pinning is operationally complex. When your server's certificate rotates, the app needs to handle the new certificate. Get this wrong and the app breaks entirely. That risk makes teams nervous, so they skip pinning altogether.


    The numbers are stark. The IBM 2024 Cost of a Data Breach Report found that breaches involving stolen credentials (often captured via MitM attacks) took an average of 292 days to identify and contain. During a technical due diligence review, certificate pinning is one of the first things we check.


    How to fix it:


  6. Implement pinning using URLSession's delegate methods or a library like TrustKit
  7. Pin against the public key rather than the full certificate (public keys survive certificate rotation)
  8. Include backup pins for your next planned key rotation
  9. Build a mechanism to update pins remotely for emergency certificate changes
  10. Test it: run your app through a proxy. If you can see API traffic in Charles Proxy without extra setup, your pinning isn't working

  11. How Dangerous Are Auth Tokens That Never Expire?


    "The token works. Why would we expire it?" I've heard this more than once. From a pure functionality standpoint, it makes sense. Non-expiring tokens mean the user never gets unexpectedly logged out. No refresh logic. No edge cases around expired sessions.


    But the security cost is enormous. According to OWASP's Authentication Cheat Sheet, session tokens should have a defined maximum lifetime, and the recommended access token lifetime is 15 minutes or less.


    What goes wrong with immortal tokens:


  12. A compromised token grants permanent access with no time limit
  13. A stolen device gives the thief indefinite access to the account
  14. You have no mechanism to revoke access without server-side infrastructure (which most apps with non-expiring tokens haven't built)
  15. The Verizon DBIR consistently shows that stolen credentials are the #1 attack vector in breaches, accounting for 31% of all breaches over the past decade

  16. The fix:


  17. Use short-lived access tokens (15-30 minutes) paired with longer-lived refresh tokens (days or weeks, depending on risk tolerance)
  18. Store both tokens in the Keychain, never in UserDefaults
  19. Implement refresh logic that queues concurrent API calls while a refresh is in progress
  20. Build server-side token revocation for immediate session invalidation
  21. Set refresh tokens to expire too, requiring periodic re-authentication

  22. Your users might see a login prompt once a month. That's a small price for real security.


    What Sensitive Data Are Your Logs Leaking?


    This one is subtle and pervasive. It's the security issue most likely to survive even a deliberate security effort because it's scattered across the codebase in places nobody thinks to look.


    During development, logging is essential. You print API responses to debug integration issues. You log user IDs to trace problems. You include request payloads in error logs. All reasonable during development. The problem: these log statements don't get removed before release. And even if your logs are clean, third-party crash reporting SDKs and analytics tools can capture data you didn't intend to share.


    I once found a crash report that included a full transaction stack trace containing a user's credit card number as a function parameter. The developer never intended to log card numbers. But when the function crashed, the crash reporter captured every parameter in the call stack. A 2023 Georgia Tech study found that 42% of popular mobile apps inadvertently logged PII through crash reports.


    Where leaked data ends up:


  23. iOS device logs (accessible to anyone connecting the device to a Mac)
  24. Crash reports shared with Apple and third-party services
  25. Analytics events sent to external servers
  26. Console output visible during development sessions left in production builds

  27. How to fix it:


  28. Implement a logging framework that distinguishes debug from production levels
  29. Use compile-time flags (`#if DEBUG`) so debug log strings don't exist in the production binary
  30. Audit crash reporting configuration to ensure sensitive fields are redacted (most SDKs support data scrubbing)
  31. Review analytics events for PII leakage
  32. Add a code review checklist item: "Does this log statement contain anything you wouldn't want in a crash report?"

  33. What's the Pattern Behind These Failures?


    These five issues share a common root. They're not incompetence or negligence. They're deadline pressure applied to teams without a dedicated security focus.


    ShortcutWhy It HappensReal Cost
    Hardcode the API keyUnblock the integrationFull backend access exposed
    Use UserDefaults for PIIShip the feature fastPII accessible via device backups
    Skip certificate pinningAvoid rotation complexityEvery API call interceptable
    Keep tokens alive foreverAvoid refresh logicPermanent access from stolen tokens
    Leave debug logs inMight need them laterPII leaking to crash services

    The fix isn't just technical. It's cultural. Security needs to be part of the development process, not a phase after launch. A lightweight threat model at the start of a project takes a few hours. A security audit after a breach costs orders of magnitude more. According to IBM, organizations that identified a breach in under 200 days saved an average of $1.02 million compared to those that took longer.


    These are the basics that get skipped under deadline pressure. And they're the basics that attackers check first, because they know how common they are. The good news: every one of them is fixable, usually in days. The first step is acknowledging your app probably has at least three of the five. The second step is carving out time to fix them before someone else finds them for you.


    If your team needs an outside perspective on your iOS security posture, that's exactly what we do at Luma Commons. We run focused security and compliance reviews and iOS engineering audits that give you a clear fix list, not a 200-page PDF nobody reads.


    Frequently Asked Questions


    How often should mobile apps undergo a security audit?


    At minimum, once per major release cycle and after any significant architecture change. For apps handling financial or health data, quarterly reviews are the standard recommended by both PCI-DSS and HIPAA frameworks. Continuous automated scanning (SAST/DAST) should supplement manual audits.


    What is certificate pinning, and is it required for App Store approval?


    Certificate pinning is a technique that ensures your app only communicates with servers presenting a specific, expected certificate or public key. Apple does not require it for App Store approval, but it is strongly recommended by OWASP and considered a baseline expectation in regulated industries like fintech and healthcare.


    Can Apple's App Transport Security (ATS) replace certificate pinning?


    No. ATS enforces HTTPS and minimum TLS versions, which protects against passive eavesdropping. But ATS does not validate that the server's certificate belongs to your server specifically. An attacker with a valid CA-issued certificate for a different domain, or access to a compromised CA, can still intercept traffic. Pinning closes that gap.


    How should iOS apps store sensitive user data securely?


    Use the iOS Keychain for small sensitive values like tokens, passwords, and API keys. For larger datasets, use an encrypted database like SQLCipher. Apply `completeFileProtection` attributes to files containing sensitive content. Never use UserDefaults or plain-text plist files for anything personally identifiable.


    What tools can detect hardcoded secrets in iOS projects?


    Several open-source tools work well: trufflehog scans git history for secret patterns, gitleaks performs similar static analysis, and Apple's own Xcode static analyzer can flag some insecure storage patterns. Integrate these into your CI pipeline so secrets are caught before they reach your main branch.

    Did you find this useful?
    iOS Security
    Mobile App Security Audit
    Certificate Pinning
    Secure Data Storage
    OWASP Mobile
    NN

    Nikhil Nangia

    Founder & Seasoned iOS Expert

    Seasoned iOS expert with 9+ years of experience building fintech, regulated, and consumer mobile products. Nikhil specializes in Swift, app architecture, and technical due diligence for pre-acquisition reviews.