Detecting malicious Composer releases in Packagist

Detecting malicious Composer releases in Packagist

A Composer supply chain attack can hide inside a version that already looks old and trusted. If a package keeps its name, tag, and release history, most review habits miss the change. That is exactly why tag rewrites matter: they can poison a package without a fresh release standing out in Packagist.

How a tag rewrite turns a normal release into a poisoned package

In this case, the attacker rewrote GitHub version tags so they pointed at malicious commits in a forked repository. Composer then treated those tags as legitimate historical releases and installed poisoned code from packages that looked stable on the surface.

The affected Laravel Lang packages included laravel-lang/lang, laravel-lang/http-statuses, laravel-lang/attributes, and possibly laravel-lang/actions. Packagist removed malicious versions and later unlisted the affected packages for a time, which is a neat reminder that package availability can change faster than a dependency lock file gets refreshed.

GitHub version tags stop meaning what they used to

A tag is supposed to mark a release point. Once an attacker can repoint tags to different commits, the tag stops being a trust boundary and turns into a pointer with excellent manners.

The ugly part is that the source tree can look untouched if you only check the main branch. Historical versions resolve to different code, but the package name and tag number still look normal. That makes a malicious package release much easier to miss in a routine review.

Composer autoload gives the payload an easy first move

The malicious release added src/helpers.php and put it into Composer autoload. That is a blunt but effective move because autoload makes PHP run the file early, without waiting for anyone to call the code directly.

That gives the payload an immediate foothold inside any project that pulls the package. Reviewers looking for suspicious application logic may never notice the helper file because it looks like the sort of thing a package can plausibly contain.

The release pattern that gives the game away

The same changes showed up across unrelated package names. That kind of shared behaviour is harder to explain away than a single odd commit in one repository.

Shared file changes across unrelated package names

The repositories shared the same fake author identity, the same modified files, and the same payload behaviour. That points to one actor with one compromised credential and org-wide push access, not a series of unrelated mistakes.

For defenders, the pattern matters more than the package name. If several Composer packages suddenly gain the same helper file, the same autoload change, and the same release timing, treat that as a coordinated compromise until proven otherwise.

Second-stage fetches and Windows artefacts

The first PHP stage fetched a second payload from flipboxstudio[.]info, then exfiltrated stolen data. On Windows, the payload dropped a base64-decoded executable into %TEMP% under a random .exe name and launched it.

That Windows stage used DebugElevator to extract Chromium App-Bound Encryption keys and decrypt stored browser credentials from Chrome, Brave, and Edge. The resulting collection targeted cloud credentials, Kubernetes secrets, Vault tokens, Git credentials, CI/CD secrets, SSH keys, browser data, cryptocurrency wallets, password managers, VPN settings, and local .env files.

Checking Packagist and your own estates without hand-waving

A trusted release needs more than a version number and a familiar package name. Check the tag, the commit it resolves to, and the published archive before you let dependency tooling pull it in.

Compare tags, commits, and published archives before you trust a version

A tag that no longer matches the expected commit should fail review immediately. The same applies when the published archive contains files that are absent from the source tree you expected, or when the autoload map suddenly pulls in a new helper file.

For Composer packages you already use, compare the recorded tag history against the current GitHub refs and the archive content on Packagist. If a release came from a forked commit path or the tag moved after publication, treat that version as suspect and pin away from it.

Hunt for outbound traffic, stolen secrets, and compromised tokens

Any estate that installed the affected versions needs a look for historical outbound connections to flipboxstudio[.]info. That is the sort of traffic that should never show up from a localisation package, which makes it a useful indicator.

Then check for reused secrets, rotated tokens, and odd authentication activity across cloud accounts, source control, CI/CD, and password stores. If the payload touched a workstation, assume browser credentials and local secrets were exposed, then rotate what matters before someone else does the tidy-up for you.

Related posts

Weekly Tech Digest | 06 Jul 2026

Stay updated with the latest in tech! This digest covers AI ethics, auto industry shifts, and the impact of politics on technology, exploring today's pressing issues.

wolfCOSE zero-allocation parsing in embedded C

wolfCOSE looks sensible only if you care about what your firmware actually has to carry. I like that, because on small targets the wrong crypto feature can cost more than the message itself, and there...

restic | v0.19.1

restic v0 19 1: safer FUSE mounts and mountpoint checks, robust backup source and exclude handling, clearer CLI JSON output, Windows SFTP deletion fixes