agentleFS
MKS-01MKS-01/appscreenmap/.claude/skills/stack-ios/SKILL.md

stack-ios

Skill2 starsChanged 16 days agoMITView on GitHub

Build and run the iOS side of the app stack scan — reading an app installed on the iOS Simulator (its .app and data container via simctl), fingerprinting the bundle (Frameworks/, Info.plist, Mach-O load commands, Kotlin/Native and Compose markers), mapping where it keeps data, and the Frida attach spike. Use when implementing or debugging `appscreenmap stack` for iOS (docs/PLAN.md §8.6, iOS stages), adding an iOS stack rule, or when an iOS scan names the wrong framework.

The file

Files
SKILL.md

iOS stack scan

The Android scan (packages/stack) reads the app installed on the emulator. iOS does the same on a booted simulator: no upload, no device: appscreenmap stack --platform ios --package <bundle-id> (or New project in the dashboard, which lists booted simulators beside emulators).

FileWhat
packages/stack/.../macho.pyload commands, thin and fat (arm64 slice), LC_BUILD_VERSION, LC_VERSION_MIN_IPHONEOS, LC_ENCRYPTION_INFO(_64)
packages/stack/.../ios.pyfingerprint_app(.app): Info.plist, Frameworks/, bundle paths, binary strings, database names
packages/stack/.../detect.py IOS_RULES, storage.py IOS_STORAGEthe iOS rules (kinds below)
packages/stack/.../audit.py build_ios_audit→ audits/stack.json (facts.platform: "ios", see docs/MAP_SCHEMA.md)
packages/drivers/.../ios.py app_container, user_appssimctl only (no idb): the .app / data folder, the User apps
apps/engine/.../cli/stack.py _cmd_stack_iosthe canvas (<project>-ios), build/app/<Name>.app, the report
packages/stack/tests/test_ios.pysynthetic Mach-O (macho(), fat()) and .app folders (app()), one case per framework

Plan and acceptance criteria: docs/PLAN.md §8.6, iOS. Simulator setup (booting, Device Hub, idb): the ios-simulator skill. Rule upkeep and confidence levels: the stack-rules skill.

Where the facts come from

FactSource on the simulatorAlready in the repo
The .app folderxcrun simctl get_app_container booted <bundle-id> appIosDriver.is_installed uses get_app_container
The data folderxcrun simctl get_app_container booted <bundle-id> dataIosDriver.clear_data resolves it (file: URL or path)
Version, bundle id, deployment target, build SDK, XcodeInfo.plist (CFBundleShortVersionString, MinimumOSVersion, DTSDKName, DTXcode)appscreenmap.store.facts.ios_facts / parse_ios_plist
Linked frameworks, min OS / SDK, encryptionthe main executable's Mach-O load commands: LC_LOAD_DYLIB, LC_BUILD_VERSION (minos, sdk), LC_ENCRYPTION_INFO_64 (cryptid)none: parse with struct (stdlib), handle fat binaries
Embedded frameworksFrameworks/*.framework, *.dylibsecurity/ios.py _read_bundle reads a bundle (dir or .ipa)
Code markersstrings in the main executable and <App>.debug.dylibsecurity/secrets.scan_bytes reads binaries

Debug builds split the code out. Xcode puts a debug build's code in <App>.debug.dylib beside a small stub executable. Scan both, or a debug KMP build reads as an empty Swift app.

Encrypted binaries. An App Store install is FairPlay-encrypted (cryptid 1): its code strings are noise. Framework names, resources and Info.plist still work. Simulator builds are never encrypted. Say so in notes; never guess.

Fingerprints (IOS_RULES; confidence as in stack-rules)

Rule kinds: framework (an embedded Frameworks/<name>.framework), bundle-path (a path prefix in the .app), bundle-file (a file name anywhere in the .app), symbol (bytes in an unencrypted binary: main executable, debug dylib, embedded frameworks), dylib (a linked library's install name contains it).

StackMarkerConfidence
FlutterFrameworks/Flutter.framework (+ App.framework)certain
React Nativemain.jsbundle; React.framework or React symbols; Hermes: hermes.framework / hermesvm.frameworkcertain (bundle + Hermes), strong
ExpoExpoModulesCore framework or symbolscertain
UnityFrameworks/UnityFramework.frameworkcertain
.NET MAUI / Xamarin*.dll / *.aotdata in the bundle, libmonosgencertain
Capacitor / CordovaCapacitor.framework, capacitor.config.json, public/; www/cordova.jscertain
Kotlin MultiplatformKotlin/Native symbols (kfun: prefix) in the executable, debug dylib or a framework; a *.framework exporting themcertain on iOS (unlike Android: Kotlin/Native only exists for KMP)
Compose Multiplatformcompose-resources/ folder in the .app; skiko (its renderer) in the binary; org.jetbrains.compose stringsstrong
SwiftUI / UIKitLC_LOAD_DYLIB of SwiftUI.framework / UIKit.frameworkstrong
Swift / Objective-ClibswiftCore linked or __swift5_* sectionsstrong

Checked on a real KMP + Compose Multiplatform debug build (locally, outside the repo): no Frameworks/, a compose-resources/ folder, and in <App>.debug.dylib about 65 000 kfun: symbols, skiko and org.jetbrains.compose strings, with SwiftUI linked; LC_BUILD_VERSION minos 18.0, sdk 27.0; no LC_ENCRYPTION_INFO.

Storage map (data container)

StorageWhere under the data containerAt rest
UserDefaultsLibrary/Preferences/<bundle-id>.plistplain
Core Data / SwiftData / SQLiteLibrary/Application Support/*.sqlite, default.storeplain unless the app encrypts
RealmDocuments/*.realm or Library/…optional
MMKVDocuments/mmkv/ (default)optional
React Native AsyncStorageLibrary/Application Support/<bundle-id>/RCTAsyncLocalStorage_V1/plain
Flutter shared_preferencesUserDefaults keys prefixed flutter.plain
Keychainnot in the container (the simulator's shared keychain)encrypted

Database names come from the binaries: C strings and Kotlin/Native's UTF-16 literals, minus reverse-domain names (those are Kotlin packages like com.example.db). SQLDelight's native driver keeps them in Library/Application Support/databases/ (checked on a simulator, 2026-10-27), so a KMP app gets that exact path; otherwise **/<name> (any folder).

Also note the file protection class where it matters (NSFileProtectionComplete) once the data-at-rest scan reads files. Detection comes from the bundle (frameworks, symbols); check a new locations path on the simulator by listing the data container after the app has run.

Build order (1–4 built; 5 next)

  1. Mach-O reader (packages/stack/.../macho.py): load commands, fat slices, LC_BUILD_VERSION, LC_ENCRYPTION_INFO_64, dylib names. Tests on synthetic headers built with struct (like elf() in test_stack.py).
  2. Bundle fingerprint (ios.py): the .app (or .ipa for later), executable + debug dylib + frameworks → the same signals / storage / libraries shape as Android, platform: "ios". iOS rules live beside Android's with iOS kinds (framework, bundle-path, symbol, dylib).
  3. Engine: appscreenmap stack --platform ios --package <bundle-id> (simctl instead of adb), canvases in the <project>-ios folder like iOS crawls, build/app/ holding a copy of the .app. Device facts come from the driver when idb is installed, else from simctl list alone.
  4. Viewer: StackDetails already renders any facts; add the iOS fields (deployment target, Xcode, encryption) and iOS words (Swift / SwiftUI). New project lists booted simulators beside emulators.
  5. Frida spike: simulator apps are macOS processes, so frida -n <process> may attach without a jailbreak. Try it on the Playground iOS build; record the result in §8.6 before building on it.

Verify

uv run pytest -q packages/stack apps/engine/tests/test_stack_cli.py apps/engine/tests/test_serve_stack.py
xcrun simctl list devices booted
xcrun simctl install booted <App>.app                      # a simulator build (an .ipa will not install)
uv run appscreenmap-engine stack --platform ios --package <bundle-id> --out <scratch>/runs
open "$(xcrun simctl get_app_container booted <bundle-id> data)"   # compare storage rows with what is really there

Grade against the plan's iOS acceptance criteria. Real apps are scanned locally and never named in the repo; the in-repo known answer is the SwiftUI Playground once it exists (until then, synthetic bundles in tests).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.