A sealed shipping box is handed across a counter, along with a clipboard holding a form. A packing note listing the enclosed items with quantities and prices is affixed to the box. A visual metaphor for a bill of materials that is created while packing and ships with the package instead of being guessed at afterwards.

A Software Bill of Materials, SBOM for short, answers three questions: What is in this software, in which version, under which license? Whether the answer is worth anything is decided long before delivery. A recent case from the automotive industry shows what can go wrong, and it has more to do with PHP than you would expect.

A Trojan in the license list

The law firm JUN Legal is suing the Chinese automotive group SAIC before the Regional Court of Munich I. The accusation: through its MG brand, SAIC violates the terms of open source licenses. Under the pressure of these proceedings, SAIC published an open source attribution for the MG4 that lists the components built into the vehicle along with their versions, licenses, and copyright notices. With that, the group delivers what the licenses require and what had been demanded for more than a year.

One entry in that list stands out: Teardroid-phprat-95a4b56.

Teardroid is an Android botnet. The tool is used to build spyware for Android devices that reads contacts, text messages, call logs and location data, executes shell commands and reports its loot to a control panel. The source code is publicly available on GitHub. A car manufacturer is therefore stating in writing that it ships a Trojan.

Did SAIC actually infect the MG4 with a botnet Trojan? Certainly not. It would make no technical sense, and anyone who did it would hardly write it into their own license list. The actual explanation is more mundane, and more interesting for us.

SBOM generation is not a job for grep

How the entry came about can be read off the entry itself. Teardroid-phprat is the name of a GitHub repository, 95a4b56 an abbreviated commit hash. Repository plus commit instead of package name plus version number: this naming convention is the handwriting of code scanners. Such tools fingerprint binaries or source trees and match them against a database of repository snapshots. Some code fragment in the vehicle software matched a snapshot of this repository, probably a shared library or a copied helper function. The scanner recorded the hit, and nobody checked it. That is how an Android botnet ended up in the license list of an automotive group.

Chan-jo Jun, the lawyer behind the lawsuit, sums it up like this, translated from the German:

SBOM generation is not a job for grep; this exercise reveals just how bad things look at the manufacturers.

Whoever starts scanning only after the build is doing forensics on their own product. That approach produces false positives like Teardroid, it misses statically linked or renamed code, and it delivers a guess where a dependable statement is required.

An SBOM containing obvious nonsense is worse than none at all. It proves that nobody read it before it was published, and that devalues every other entry as well. If Teardroid is wrongly in the list, which component is wrongly missing from it? Whoever publishes the unchecked output of a tool as their own statement takes on that tool's mistakes. This holds for code scanners just as much as it does for language models.

The PHP RAT punchline

There is one detail I have withheld so far. The “php” in Teardroid-phprat really does stand for PHP. Earlier versions described themselves as an “easy to use Android PHP RAT”, a remote access Trojan, and the control panel the spyware reports its loot to was originally written in PHP.

That settles the question of jurisdiction, and I can move on to the more serious part. The tools SAIC was missing have been available in the PHP ecosystem for years.

Declared beats scanned

You can respond to the Teardroid mishap with a better configured scanner. The real question, though, is why anything had to be scanned in the first place. Whoever has to work backwards to determine what is in their software lost control over their dependencies long ago. A dependable SBOM falls out as a by-product of a build process that declares every dependency explicitly.

In the PHP ecosystem, this by-product is called composer.lock.

Whoever manages their dependencies with Composer already has that list, accurate down to the version and secured by hashes. composer info shows all dependencies managed in the project along with their exact version at any time. In substance that is an SBOM, just not yet in a format other tools can process. The Composer plugin cyclonedx-php-composer, for instance, generates a standards-compliant SBOM in CycloneDX format from the Composer metadata, with SPDX license identifiers and package URLs. That is what supply chain tools expect and, keyword Cyber Resilience Act, what regulation increasingly expects as well.

The difference to the scanner method is fundamental. An SBOM generated this way guesses at nothing, it records what actually happened during the build. Every listed component was resolved by Composer, verified against the hash written down in composer.lock, and installed. A Teardroid could only appear in such a list if somebody had installed it using composer require, and then it would rightfully be there.

This only works, however, if Composer is the single source of truth. That makes the SBOM another argument for a discipline that should apply anyway: dependencies are managed exclusively with Composer, with no PEAR installer alongside it. If your project still contains code that came in that way, my article “Blast from the Past” puts it into historical context. Above all, though: do not download a library and drop it into your own repository, bypassing Composer. Every dependency vendored that way is a blind spot where, at some point, nobody knows any more what came from where in which version. Software builds made of such blind spots are exactly the ones that have to be examined with scanners later on.

The blind spot affects more than the bill of materials. Since Composer 2.9, the resolver blocks updates to versions with known security advisories, and since Composer 2.10, the dependency policy framework keeps packages flagged as malicious away even during composer install. I have described how this works in detail in “The Bouncer in the Dependency Resolver” and “Composer and Packagist under supply chain stress”. These mechanisms operate on the dependency graph that Composer resolves. A library copied into the repository by hand is not part of that graph. Neither the blocking of known vulnerabilities nor a malware policy applies to it, and composer audit does not report it. If a vulnerability in it becomes known years later, nobody on the project will hear about it.

How PHPUnit does it

For PHPUnit, the question arises in an aggravated form. PHPUnit is distributed not only through Composer, but also as a PHP archive (PHAR) that bundles all dependencies into a single file. From a user's point of view, a PHAR is at first just as much a binary blob as the firmware of a car, because you cannot tell from the outside what is inside.

This is why the PHPUnit PHAR discloses its contents itself. Three command line options, documented at phpunit.de/verify.html, make them visible without anything having to be scanned.

php phpunit.phar --manifest lists every bundled package with its exact version number.

php phpunit.phar --sbom outputs a complete SBOM in CycloneDX XML format. For each component it contains group, name, version, license and package URL. Supply chain tools that track known vulnerabilities across the dependency tree can process this directly.

php phpunit.phar --composer-lock outputs the composer.lock that the dependencies were installed with when the PHAR was built. That is useful for reproducible builds and for source-based distributions.

This SBOM is not created only once a legal department comes under pressure. It sits inside the delivered artifact because it falls out of the build anyway: the PHAR is built from a Composer-managed dependency tree, and that tree is fully known. On top of that comes the cryptographic protection. Every PHAR release is PGP-signed, SHA256 hashes are published alongside it, and PHIVE automates the verification. Signature and SBOM complement each other. The signature attests that the artifact comes unmodified from its author, the SBOM attests what is inside it.

Conclusion

The MG4 case is being tried in court as a copyright question, and rightly so. Whoever profits commercially from Free Software has to abide by its terms. I have been publishing software under free licenses for more than 26 years and have a direct interest in those terms not being folklore.

For developers, though, there is something else in the Teardroid mishap. A license list is only as good as the knowledge behind it, and that knowledge is created while building, not while examining things afterwards. Every dependency is declared, pinned to a version and a hash, and managed through a single mechanism.

Whoever works with Composer gets most of this discipline for free. Your composer.lock knows what is in your software. Whoever has it scanned afterwards is only guessing, and may well end up guessing at a Trojan that was never there.