Your Mobile SBOM Is Wrong in Three Specific Ways

Reviewed 4 October 2026. Three things went wrong when we checked our own mobile SBOMs: CocoaPods dependencies were never scanned at all, the scanner invented a dependency that does not exist, and a correct SBOM reported phantom gaps. The first two pass silently; the third fails a build that should have passed.

Generating a software bill of materials used to be a box-tick. Under the EU Cyber Resilience Act it becomes evidence — from 11 December 2027, when the product requirements apply. Annex I, Part II, point (1) requires manufacturers to

identify and document vulnerabilities and components contained in products with digital elements, including by drawing up a software bill of materials in a commonly used and machine-readable format covering at the very least the top-level dependencies of the products

and Annex VII, point (2)(b) puts that SBOM in the technical documentation a market surveillance authority can ask for. The reporting duties have applied since 11 September 2026; the rest applies from 11 December 2027.

Which changes the question. “Did the SBOM step pass?” is no longer interesting. “Is the SBOM true?” is.

We built SBOM generation for three mobile package managers, ran it in CI against real projects, and then wrote a second check that compares the SBOM back against the lockfiles it came from. That second check is where the problems showed up.

1. If your iOS app uses CocoaPods, an OSV lookup sees nothing

Update, 25 October 2026: since we wrote this, at least one free, open-source CRA tool has started checking CocoaPods dependencies through the GitHub Advisory Database’s Swift ecosystem, though pods with no advisory there (often older Objective-C pods) still show nothing. OSV.dev itself still has no CocoaPods ecosystem.

Not “no vulnerabilities found”. Nothing checked.

OSV.dev has no CocoaPods ecosystem. Query one and you get:

{"code":3,"message":"invalid ecosystem"}

(api.osv.dev, checked 2 October 2026.)

The GitHub Advisory Database covers twelve ecosystems — Swift among them since 2023, CocoaPods not — so every OSV-backed scanner inherits the same hole, and Dependabot — which reads the GitHub Advisory Database directly — has the same gap for the same reason. On our CocoaPods sample (Alamofire, SwiftyJSON, KeychainAccess, FirebaseCrashlytics: 15 root pods, which syft reports as 21 components once subspecs are counted separately), the vulnerability check reported:

checked 0 component(s) against OSV
NOT CHECKED — 21 component(s) no public advisory source covers

Snyk is one tool that does cover it (its CocoaPods support), using its own curated database and its own lockfile parser rather than OSV. We did not survey every commercial scanner, so take this as what we tested rather than a claim about the market: of the free, OSV- and GitHub-Advisory-backed tooling we checked, none returned anything for a pod. A green dashboard is then telling you about the dependencies it could see, which is not the same as telling you that you are clear.

What to do

Map each pod to its source repository and query it as a Swift package instead — the Swift ecosystem is covered. Every pod in our sample resolved to a source repository this way; we have not checked how widely that holds beyond it. On the same sample it took us from 0 of 21 components checked to 21 of 21.

The catch, stated honestly: a pod and its Swift package usually share version numbers, but not always. Mark anything matched this way as matched indirectly, so a human knows which findings rest on an assumption.

2. Your SBOM tool may be inventing dependencies

Gradle writes a comment header into every lockfile it generates:

# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
# To regenerate this file, run: ./gradlew :dependencies --write-locks

syft 1.52.0 parses the last line into a package:

{ "name": " ./gradlew ", "version": "dependencies --write-locks", "type": "java-archive" }

It happens because syft doesn’t skip comment lines, and that line contains two colons, which is also the shape of a locked group:name:version coordinate. Any Gradle project with dependency locking enabled is affected when scanned with syft 1.52.0 — the content of the file Gradle writes for you is what triggers it. We tested 1.52.0; other versions may behave the same way.

We reported it: anchore/syft#5371, with a four-line reproduction. Open at the time of writing.

The consequence is small and specific: a dependency that does not exist, sitting in a document you may hand to an auditor, plus noise in vulnerability matching. Not a crisis — but “our SBOM lists a package called ./gradlew” is not a conversation you want to have during due diligence.

What to do

Filter components whose name or version contains whitespace or URL-encoded characters, or that have no version at all. Those are the shape of a parser accident rather than a dependency. Ours reports them as ignored rather than dropping them silently, because a tool that quietly deletes things it does not like is a tool you cannot audit.

3. A correct SBOM can look broken

CocoaPods ships libraries as subspecs. syft reports them individually — GoogleUtilities/Environment, GoogleUtilities/Logger, GoogleUtilities/Privacy — and in our sample it never emitted a bare GoogleUtilities component at all.

Podfile.lock‘s own checksum list names only the root pod, and that’s the level our coverage check compares at. So a check comparing the SBOM against Podfile.lock finds GoogleUtilities declared, no GoogleUtilities component, and reports a gap — on an SBOM that is completely correct.

This is the most damaging of the three, and it is the easiest to overlook. A false alarm in a compliance tool does not merely waste an afternoon: it teaches the team that the tool is wrong, and six weeks later a real finding gets the same shrug. A check that cries wolf is worse than no check, because it spends the credibility you need on the day something is actually broken.

What to do

Normalise both sides to the root package before comparing, and treat a subspec as evidence for its parent.

The pattern underneath all three

None of these produces a correct answer, and none of them looks like a failure of the SBOM step. The first two pass silently. The third does the opposite — it raises an alarm on an SBOM that is completely fine, which is the more expensive mistake.

An SBOM step that succeeds while cataloguing nothing is the standard silent failure — a wrong path, an unsupported lockfile format, a tool upgrade that changed a cataloguer. The file still appears. CI still goes green. You file it as evidence.

So the useful thing we built was not the SBOM generation, which is a dozen lines of YAML. It was the check that compares the SBOM back to the lockfiles and fails the build when it does not cover them. Three numbers from our CI, run on every push:

Sample Declared in lockfiles In the SBOM
Swift Package Manager (4 direct, 13 transitive) 17 17
CocoaPods (4 direct pods, 15 installed; 21 components before subspecs are normalised to root pods) 15 15
Gradle (4 direct, 14 locked) 14 14

All three from committed lockfiles alone — no Xcode, no pod install, no Gradle run, on free Linux runners, in well under a minute per project in our runs.

Four things worth doing this week

  1. Generate an SBOM from your lockfiles and read it. Not the summary — the component list. Count it against your lockfile by hand once. You will know within five minutes whether your tooling is telling you the truth.
  2. If you ship CocoaPods, check whether your scanner reads CocoaPods at all before trusting a clean result.
  3. Turn on Gradle dependency locking if you have not. It is three lines, and it is the difference between an SBOM that takes forty seconds and one that needs your whole Android toolchain to run first.
  4. Keep the SBOM for every release somewhere durable. GitHub Actions artefacts expire after 90 days by default; Article 13(13) requires manufacturers to keep the technical documentation at the disposal of market surveillance authorities for at least 10 years after the product is placed on the market, or for the support period, whichever is longer.

Engineering notes, not legal advice. Quoted text is from Regulation (EU) 2024/2847, © European Union, eur-lex.europa.eu; only the legislation published in the electronic edition of the Official Journal is authentic (Council Regulation (EU) No 216/2013, Article 1(2)), except during an exceptional disruption of the Publications Office’s systems, when the printed edition is (Article 3).

We build the CRA tooling these findings came out of. Not sure whether any of this applies to your app? Five questions will tell you.

Related Articles

Responses

Your email address will not be published. Required fields are marked *