SBOM

SBOMs for Mobile Applications: iOS and Android

Mobile apps ship to millions of devices and can't be patched silently. Here's how to build SBOM practices for iOS and Android development.

James
Mobile Security Engineer
6 min read

Mobile applications have a unique relationship with their dependencies. Unlike a web service you can patch in minutes, a mobile app sits on millions of devices, and users update on their own schedule -- if they update at all. When a vulnerability hits a library bundled into your iOS or Android app, the blast radius is wide and the remediation timeline is long.

SBOMs for mobile apps aren't optional. They're the only way to maintain visibility into what you've shipped to devices you don't control.

Why Mobile SBOMs Are Harder

You Ship the Whole Stack

Web applications can rely on server-side patching. Mobile apps ship everything to the device: your code, your dependencies, static assets, embedded databases, native libraries. All of it is your responsibility.

Update Friction Is Real

Even after you push a fix to the App Store or Play Store, users adopt updates slowly. Version fragmentation means you might have ten different versions of your app in active use, each with a different dependency set. Your SBOM strategy needs to track all of them.

Mixed Language Ecosystems

A typical mobile app mixes languages and package managers:

  • iOS: Swift/Objective-C with CocoaPods, Swift Package Manager (SPM), or Carthage
  • Android: Kotlin/Java with Gradle, plus potential native C/C++ via NDK
  • Cross-platform: React Native (npm), Flutter (pub), Xamarin (NuGet)

Each ecosystem has its own dependency resolution, lockfile format, and quirks. Your SBOM tool needs to handle all of them within a single project.

Binary Frameworks and SDKs

Mobile apps frequently include pre-compiled SDKs -- analytics, crash reporting, advertising, payment processing. These binary frameworks don't have source code or lockfiles you can parse. They show up as .xcframework or .aar files with opaque contents.

Cataloging these correctly in an SBOM requires either vendor-provided SBOMs (rare in 2023) or manual inventory.

Generating SBOMs for iOS Apps

CocoaPods Projects

CocoaPods uses a Podfile.lock that lists every resolved dependency with exact versions:

# Generate SBOM from a CocoaPods project
cd my-ios-app
syft dir:. -o cyclonedx-json > sbom.json

Syft reads Podfile.lock and catalogs all pods. For deeper analysis:

# cdxgen has strong iOS support
cdxgen -o sbom.json --type swift .

Swift Package Manager

SPM uses Package.resolved (or Package.swift for definitions). Tools like cdxgen parse this directly:

cdxgen -o sbom.json .

The Package.resolved file is your best friend here -- it contains the exact commit hashes and versions of every resolved dependency.

Carthage

Carthage writes a Cartfile.resolved with pinned versions. SBOM tool support for Carthage is less mature, but cdxgen handles it:

cdxgen -o sbom.json .

Handling XCFrameworks

Pre-compiled frameworks are the blind spot. For each .xcframework in your project:

  1. Document the vendor, version, and known dependencies
  2. Request SBOMs from SDK vendors (this is becoming more common)
  3. Add manual entries to your SBOM for frameworks that tools can't detect
{
  "type": "framework",
  "name": "AnalyticsSDK",
  "version": "4.2.1",
  "supplier": { "name": "Analytics Corp" },
  "purl": "pkg:swift/analyticscorp/analytics-sdk@4.2.1"
}

Generating SBOMs for Android Apps

Gradle Projects

Android uses Gradle, and the dependency tree can be extracted directly:

# Generate dependency tree
./gradlew app:dependencies --configuration releaseRuntimeClasspath

# Generate SBOM with cdxgen
cdxgen -o sbom.json .

# Or with Syft
syft dir:. -o cyclonedx-json > sbom.json

The CycloneDX Gradle plugin provides native SBOM generation:

// build.gradle
plugins {
    id 'org.cyclonedx.bom' version '1.7.4'
}

// Generate SBOM
// ./gradlew cyclonedxBom

This produces a CycloneDX SBOM that includes all resolved dependencies with their transitive chains.

Native Dependencies (NDK)

If your Android app includes native C/C++ libraries via the NDK, those dependencies often come from CMake or ndk-build. Standard SBOM tools won't catch these automatically.

For NDK dependencies, you need to:

  1. Maintain a manual inventory of native libraries
  2. Use tools like readelf to inspect linked libraries in .so files
  3. Track the NDK version itself, which bundles specific versions of libc++, OpenSSL, etc.

AAR Dependencies

Android Archive (AAR) files from Maven repositories are well-handled by Gradle-based SBOM tools. Pre-compiled AARs from vendors outside Maven follow the same pattern as XCFrameworks -- you need vendor-provided metadata or manual tracking.

Cross-Platform Frameworks

React Native

React Native apps combine npm dependencies (JavaScript) with native iOS and Android dependencies:

# Scan the JavaScript layer
cd my-rn-app
syft dir:. -o cyclonedx-json > sbom-js.json

# Scan iOS dependencies
cd ios
syft dir:. -o cyclonedx-json > sbom-ios.json

# Scan Android dependencies
cd ../android
syft dir:. -o cyclonedx-json > sbom-android.json

Ideally, merge these into a unified SBOM. CycloneDX supports component nesting and composition, which lets you represent the JavaScript and native layers as sub-components.

Flutter

Flutter uses pubspec.lock for Dart dependencies:

cdxgen -o sbom.json --type dart .

Flutter also bundles the Dart runtime and Skia graphics engine. These should appear in your SBOM but won't be picked up from pubspec.lock.

CI/CD Integration

Xcode Cloud

# Post-build script in Xcode Cloud
#!/bin/bash
cd $CI_PRIMARY_REPOSITORY_PATH
npx @cyclonedx/cdxgen -o sbom.json --type swift .
# Upload to SBOM storage
curl -X POST https://api.safeguard.sh/sbom/upload \
  -H "Authorization: Bearer $SBOM_TOKEN" \
  -F "file=@sbom.json" \
  -F "project=my-ios-app" \
  -F "version=$CI_BUILD_NUMBER"

Android CI with GitHub Actions

- name: Generate SBOM
  run: |
    npx @cyclonedx/cdxgen -o sbom.json .

- name: Upload SBOM
  uses: actions/upload-artifact@v3
  with:
    name: android-sbom
    path: sbom.json

Version Tracking Across App Releases

Each app store release should produce and store an SBOM. Your tracking needs:

| Release | Store Version | Build | SBOM | Status | |---------|--------------|-------|------|--------| | v2.3.0 | 1.2.3 (45) | #312 | sbom-v2.3.0.json | Active | | v2.2.0 | 1.2.2 (44) | #298 | sbom-v2.2.0.json | Active | | v2.1.0 | 1.2.1 (43) | #287 | sbom-v2.1.0.json | Deprecated |

When a CVE hits, check every active release version. If v2.2.0 includes the vulnerable library, you know exactly which users are at risk based on version adoption metrics from the app store.

How Safeguard.sh Helps

Safeguard tracks SBOMs across multiple app versions simultaneously, which is exactly what mobile development demands. Upload an SBOM with each app release, and Safeguard monitors all active versions against current vulnerability data. When a CVE affects a library in your older release that's still on 40% of devices, you see it immediately. The platform supports every package ecosystem mobile apps touch -- CocoaPods, SPM, Gradle, npm, pub -- and gives you a single view across iOS and Android builds.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.