Generic SAST tools were built for web backends. They miss the parts of a modern app that actually ship to users: the iOS plist that disables transport security, the service worker that caches a login response, the GraphQL endpoint that hands an attacker your entire schema. IOnclad reads all three. Here's what goes wrong in each, and what the scanner actually looks for.
These look unrelated, but they share a problem: the dangerous config lives in files most tooling never opens. An Info.plist, a sw.js, a resolvers.ts. IOnclad has a dedicated scanner for each.
Each surface has its own failure mode. Here are the ones that bite hardest.
Apple's App Transport Security forces HTTPS by default. The classic mistake is reaching for NSAllowsArbitraryLoads set to true in Info.plist to make one stubborn HTTP endpoint work, which switches plaintext back on for the whole app. Credentials and API responses then travel in the clear. The same plist also lowers minimum TLS below 1.2 or kills forward secrecy, each quietly reopening attacks the platform had already closed.
UserDefaults is an unencrypted plist. Any backup-extraction or forensic tool can read it. When an app calls UserDefaults.set(token, forKey: "authToken"), that token is sitting on disk in plaintext. The Keychain exists for exactly this, but it gets used without access control flags, or with a wildcard access group that lets any app from the same team read the item. Hardcoded encryption keys are worse: a key baked into the binary as a Data literal can be pulled straight out with Hopper or IDA.
A service worker sits in front of every request the page makes. Point a cache-first strategy at /auth or /login and you've built a trap: a cached session response can be served to the next person on a shared device, or keep working after logout. A root-scope worker controls admin pages and OAuth callbacks too. And importScripts() without an integrity check means whoever controls that remote URL controls your worker, and therefore every fetch the app makes.
Caching an auth response in a service worker is invisible in normal testing. It works fine for you. Then a second user logs in on the same browser profile, the worker serves your cached /session response, and they're now you. IOnclad flags this as Critical when an auth path appears inside a caching call.
Introspection lets a client download your whole schema: every type, query, and mutation. Handy in dev, a free map for an attacker in prod. Pair that with no query-depth limit and a single deeply nested query (user { friends { friends { friends { ... } } } }) becomes a denial-of-service. Add mutation resolvers that never check context.user and an unauthenticated client can run them at will.
The PWA cache trap is the one people don't see coming:
// sw.js, shipped Tuesday. "cache everything, it's faster" registerRoute(({ url }) => url.pathname.startsWith('/login'), new CacheFirst({ cacheName: 'pages' })); // the fix: never cache auth, always go to network registerRoute(({ url }) => url.pathname.startsWith('/login'), new NetworkOnly()); // before the fix: kiosk browser, user A logs in, logs "out". // user B opens /login, the worker serves A's cached session. ✗
Three dedicated scanners run 100% offline, each firing only when it sees the right file types. Your code never leaves your machine:
Info.plist, Swift, Objective-C, entitlements, pbxproj, and storyboards. It flags ATS turned off via NSAllowsArbitraryLoads, TLS lowered below 1.2, forward secrecy disabled, sensitive keys written to UserDefaults, Keychain items added without access control, wildcard keychain access groups, hardcoded encryption keys, and UIWebView. It checks the actual true/false value after a key, not just that the key is present./api/, importScripts() without integrity, registration over HTTP, and a fetch handler that intercepts everything with no URL filter. It bounds the check to the caching call's arguments, so a worker that explicitly excludes /auth with an early return does not get flagged.login and signup by design.IOnclad finds known patterns and common misconfigurations. It is a fast pre-launch check, not a guarantee or a substitute for a pentest.
NSAllowsArbitraryLoads. If one domain truly needs HTTP, add a narrow per-domain exception, never a global one. Keep minimum TLS at 1.2 or higher.SecItemAdd plus access control flags. Keep UserDefaults for non-sensitive preferences only. Never embed an encryption key in source./auth, /login, and /session. Cache-first belongs to immutable static assets and nothing else.importScripts().context.user in every non-public mutation.Scan your code and full git history in under a minute. Free verdict, no signup, nothing leaves your machine.