Safeguard
Open Source Security

Securing mobile app dependencies: CocoaPods and Gradle

CocoaPods and Gradle power millions of mobile apps. See how orphaned pods, build-script RCE, and dependency confusion put them at real risk today.

Priya Mehta
DevSecOps Engineer
7 min read

CocoaPods and Gradle sit underneath nearly every iOS and Android app shipped today, and both were built for convenience, not for adversarial conditions. In October 2024, researchers at EVA Information Security disclosed three CocoaPods vulnerabilities — CVE-2024-38366, CVE-2024-38367, and CVE-2024-38368 — that had existed since a 2014 server migration and affected an estimated 3 million apps built on CocoaPods packages. On the Android side, Gradle's build scripts are executable Groovy or Kotlin code, which means a single malicious build.gradle file can run arbitrary commands on a developer laptop or a CI runner before a single test executes. Neither ecosystem enforces package signing by default, and both have had real, exploited incidents, not theoretical ones. This post walks through what happened, why the tooling makes it possible, and how security teams can detect it before it ships to the App Store or Play Store.

What made the 2024 CocoaPods vulnerabilities so dangerous?

CVE-2024-38368 was dangerous because it let attackers silently take over "orphaned" pods that thousands of apps still depended on. When CocoaPods migrated its Trunk server in 2014, a bug left roughly 1,866 pods without a registered owner — meaning anyone could claim them and push a new malicious version under the original pod's name. Since developers reference pods by name in their Podfile, an app pulling in an orphaned dependency during a routine pod install would silently receive attacker-controlled code with no version pinning warning. The companion flaw, CVE-2024-38366, was a remote code execution bug in the Trunk server's session-verification endpoint that could have let an attacker execute code on the server hosting the specs for the entire public CocoaPods repository — a single-point compromise with blast radius across the ecosystem's roughly 100,000 published pods. A third bug, CVE-2024-38367, was arguably the most quietly dangerous: it allowed an attacker to hijack the email-based ownership-claim flow and take over an actively maintained pod, not just an abandoned one, meaning even a team diligently updating its dependencies had no reliable signal that the maintainer account behind a trusted pod had changed hands.

Why can a Gradle build file compromise a developer machine before code review even happens?

Because Gradle build files are not configuration — they're executable programs. Unlike a Podfile or a package.json, which are declarative data files, build.gradle and build.gradle.kts are full Groovy or Kotlin scripts that Gradle interprets and runs at build time, with whatever permissions the person running gradle build has. A malicious buildscript {} block, a custom Gradle plugin pulled from an unvetted Maven repository, or a compromised transitive plugin dependency can execute shell commands, exfiltrate environment variables (including CI secrets like signing keys and Play Store API tokens), or modify other build outputs — all before your test suite, your linter, or your SAST scanner ever sees a line of application code. This is the same class of risk documented in npm's postinstall script abuse, but Gradle's default execution model makes it broader: the entire build file, not just an install hook, is live code. There's a second, less obvious entry point: the Gradle Wrapper. Most Android repos ship a gradlew script and a gradle-wrapper.properties file pointing to a distribution URL; if that URL is swapped to an attacker-controlled host — something that has happened in poisoned open-source repos on GitHub — the very first ./gradlew build a new contributor runs downloads and executes a fully attacker-controlled Gradle binary, not just a plugin.

Do App Store and Play Store review processes catch malicious dependencies?

No — both Apple's App Review and Google Play Protect scan the compiled binary for behavioral red flags at submission time, not the source-level dependency graph that produced it. Apple's review focuses on runtime behavior, permissions use, and policy compliance of the final .ipa; it has no visibility into whether a Podfile.lock resolved a pod to a hijacked maintainer account three build cycles earlier. Google's Play Protect and Play Console pre-launch reports similarly analyze the shipped APK for malware signatures and suspicious permissions, not the provenance of the 500 Maven artifacts that were compiled into it. A dependency-confusion or orphaned-pod compromise that injects code behaving similarly to the legitimate library it replaced — logging analytics, making network calls to a plausible-looking domain — can pass store review entirely while still exfiltrating data or tokens. This is why dependency-level scanning has to happen in the build pipeline, not after the fact at submission.

Can dependency confusion attacks actually hit mobile apps, or is that just an npm problem?

Dependency confusion is not npm-specific — it works against any package manager that resolves names across multiple repositories, and Gradle/Maven setups are frequently misconfigured this way. The technique, popularized by researcher Alex Birsan in 2021 after he used it to get paid bug bounties from Apple, Microsoft, PayPal, and more than 30 other companies, relies on a build tool checking a public repository (Maven Central, JCenter mirrors, or a public Gradle Plugin Portal listing) for a package name that also exists as an internal, unpublished artifact. If a company's internal library is named com.acme:core-utils and that name isn't reserved on Maven Central, an attacker can publish a higher-versioned public package under the identical coordinates. Because many Android build configurations list public repositories like mavenCentral() before or alongside private ones without explicit repository-to-dependency mapping, Gradle's default resolution can pull the attacker's version instead of the legitimate internal one — with no error, and no signature mismatch, because there was never a signature required in the first place.

How do transitive dependencies make a "one pod" or "one library" problem into a hundred-library problem?

A single CocoaPods or Gradle dependency typically pulls in a dependency tree five to ten levels deep, and most teams have visibility into only the top level. A mid-sized iOS app with 40 direct Podfile entries commonly resolves to 200–400 total pods once transitive dependencies are included; a comparable Android app with 60 Gradle dependencies frequently resolves to 500+ artifacts across implementation and api configurations. Security teams that review only their Podfile or build.gradle — the files they wrote — miss the 90% of the dependency graph they didn't. This is precisely the mechanism that made the CocoaPods orphaned-pod flaw so persistent: a popular top-level pod could depend on an orphaned sub-dependency the app's own engineers had never heard of, inheriting the risk without ever adding it directly.

What should a security team actually check first, CocoaPods or Gradle?

Check whichever one resolves dependencies with the least verification, and for most teams shipping both platforms, that means auditing Gradle repository order first and CocoaPods source pinning second. Concretely: confirm every Android module's settings.gradle or build.gradle uses explicit repository content filtering (exclusiveContent {} or dependencyResolutionManagement with repositoriesMode.set(FAIL_ON_PROJECT_REPOS)) so public and private repos can't silently substitute for each other, and confirm every Podfile entry pins to an exact version or commit SHA rather than a loose version range (~> 1.2 still allows automatic upgrades to a compromised 1.2.9). Both checks take under an hour per repository and catch the two most common real-world exploitation paths documented above — dependency confusion on the Gradle side, and unpinned/orphaned pod takeover on the CocoaPods side.

How Safeguard Helps

Safeguard ingests both CocoaPods (Podfile.lock) and Gradle (build.gradle, gradle.lockfile) manifests to generate a full transitive SBOM, so security teams see the 300+ artifact dependency graph behind their 40 direct entries, not just the top-level list. Griffin AI, Safeguard's reasoning engine, correlates newly disclosed CVEs like CVE-2024-38366 against your actual dependency tree and runs reachability analysis to determine whether the vulnerable code path in a given pod or Gradle plugin is actually called by your app — cutting through alert noise instead of flagging every pod with a CVE regardless of exploitability. When Safeguard confirms a reachable, exploitable issue — an unpinned Podfile entry resolving to an orphaned pod, or a Gradle repository misconfiguration exposed to dependency confusion — it can open an auto-fix pull request that pins the safe version or corrects the repository resolution order, so the fix ships in the next build instead of the next audit cycle.

Never miss an update

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