Safeguard
Open Source Security

Malicious Composer packages on Packagist

Three malicious Composer package campaigns hit Packagist in under a year -- each sitting undetected for months. Here's what happened and how to catch the next one faster.

Priya Mehta
DevSecOps Engineer
7 min read

In the span of roughly eleven months, Packagist -- the default package registry behind every composer install in the PHP ecosystem -- has absorbed three distinct malicious-package campaigns significant enough to draw independent write-ups from multiple security research teams. The most recent, disclosed in May 2026, involved eight compromised Composer packages rigged to pull down a Linux binary during installation. What made it notable wasn't the payload -- it was where the payload was hiding. Researchers at Socket found the malicious postinstall hook not in composer.json, where a PHP-focused review would look first, but buried in a package.json file shipped alongside the Composer package for front-end tooling. Two months earlier, in March 2026, a threat actor operating under the handle "nhattuanbl" was caught running a six-package Laravel-utility campaign that had been quietly live on Packagist since mid-2024. A third campaign, involving trojanized OphimCMS themes distributed as Composer packages, was traced back to June 2024 as well and wasn't caught until March 2026.

Three campaigns, three different techniques, one shared thread: each one sat on Packagist for months before anyone noticed. For an ecosystem that underpins Laravel, Symfony, and a large share of self-hosted PHP infrastructure, that dwell time is the story.

The May 2026 Attack: Malware Hiding in the Wrong Manifest

The eight-package campaign uncovered by Socket in May 2026 is the clearest recent illustration of how attackers are adapting to defender habits rather than defender tooling. The affected packages, including the Laravel starter kits devdojo/wave (over 6,400 GitHub stars) and devdojo/genesis (roughly 9,100 Packagist installs), are consumed by developers who expect a PHP-only supply chain audit to cover them. Instead, the attacker modified the packages' upstream repositories to add a postinstall script inside package.json -- a file most Composer-focused scanners and manual reviewers don't treat as part of the PHP dependency tree at all, since it belongs to the JavaScript tooling that ships alongside many Laravel front ends.

That script fetched a Linux binary from a GitHub Releases URL and executed it during installation. The binary was named gvfsd-network, a deliberate impersonation of the legitimate GNOME Virtual File System network-share daemon, chosen to blend into a process list on any Linux developer or CI machine. Socket's investigation subsequently found the same payload referenced across 777 files on GitHub, suggesting the campaign extended well beyond the eight confirmed Packagist packages and had been staged for reuse against other targets.

The mechanics matter because they expose a structural gap: Composer's dependency graph and npm's dependency graph are usually reviewed by different tools, different teams, or not at all when a repository straddles both ecosystems. An attacker only has to place malicious logic in the manifest a reviewer isn't looking at.

Not a One-Off: A Pattern Building Through 2026

The May incident followed, rather than preceded, a run of similar findings. In March 2026, Socket and multiple outlets reported on a Laravel-targeted campaign built around the packages nhattuanbl/lara-helper and nhattuanbl/simple-queue, both carrying an identical remote access trojan buried in a file called src/helper.php. A third package in the set, nhattuanbl/lara-swagger, shipped no malicious code of its own -- it simply declared lara-helper as a hard Composer dependency, turning a clean-looking utility into a delivery mechanism. The payload itself was heavily obfuscated: roughly 27,000 bytes collapsed onto a single line, with control flow shattered into randomized goto jumps, string literals hex- and octal-encoded, and every identifier replaced with meaningless random names -- textbook anti-review tradecraft aimed squarely at anyone who might actually open the file. Any Laravel application that installed the package ran a persistent RAT inside the same process as the web app, with access to the same .env values, database credentials, and API keys as the application itself. The trojan reconnected to its command server every fifteen seconds, giving the operator the ability to redirect it to a new host at will.

Around the same time, researchers uncovered six malicious Composer packages published under the ophimcms namespace, disguised as legitimate themes for the OphimCMS platform. Rather than a backdoor or credential stealer, these shipped trojanized jQuery assets designed to redirect site visitors, harvest browsing data, and inject unauthorized advertisements -- a monetization-first payload rather than an espionage-first one, but one that had also been sitting untouched on Packagist since June 2024.

Three campaigns, three payload types -- RAT, ad-injection malware, cross-ecosystem dropper -- and in every case, a discovery-to-publication gap measured in months, not days.

Why Packagist Keeps Showing Up in Incident Reports

None of this is new territory for the ecosystem. In 2023, Packagist disclosed that a compromise of its infrastructure had affected a set of packages collectively responsible for more than 500 million installs. A year earlier, Sonar researchers disclosed CVE-2022-24828, a command injection flaw in Composer that could have let an attacker hijack Packagist's own package-serving pipeline -- a bug researchers estimated could have touched over 100 million requests and potentially compromised millions of servers had it been exploited before disclosure.

Several structural factors keep pulling Packagist into these reports. First, scale and centrality: Packagist is effectively the only public PHP registry that matters, so a single successful campaign reaches every Laravel, Symfony, and WordPress-adjacent shop pulling from it. Second, PHP's deployment model concentrates risk: unlike a compiled binary shipped from CI, a compromised Composer package frequently runs directly inside a live, internet-facing web application process, with immediate access to the same environment variables and secrets the application uses. Third, review habits lag the ecosystem's real shape -- as the May 2026 attack showed, a PHP shop's security review often stops at composer.json and never opens the package.json sitting one directory over.

Composer and Packagist's Response -- and Its Limits

To its credit, the Packagist and Composer maintainer team has responded. Composer 2.10 introduced a native malware policy framework, giving teams a way to configure dependency policy and enabling faster removal of flagged malicious versions once they're identified, and Packagist.org now integrates with Aikido for malware detection on newly published packages. Packagist's own blog has been unusually transparent about each incident as it's confirmed, publishing timelines and remediation guidance rather than staying quiet.

These are real improvements, and they raise the cost of running an unsophisticated typosquatting campaign against Packagist today versus two years ago. But they are still fundamentally reactive: they depend on a malicious package being flagged, reported, or scanned after publication, and every campaign detailed above ran for months before that happened. A dependency policy engine only blocks what it's told to block. For a security team responsible for the applications actually consuming these packages, waiting on registry-side detection is not a strategy -- it's a hope.

How Safeguard Helps

This is precisely the gap Safeguard is built to close from the consumer side rather than the registry side. Safeguard ingests and generates SBOMs across your full dependency surface -- including the mixed Composer-and-npm manifests that let the May 2026 campaign hide in plain sight -- so a malicious package.json postinstall hook riding inside a "PHP" package doesn't fall outside anyone's inventory. Reachability analysis then tells your team whether a flagged or newly-malicious package is actually invoked by your application's code paths, cutting through alert noise so a compromised devdojo/genesis or nhattuanbl/lara-helper dependency gets prioritized correctly instead of triaged as one line among hundreds. Griffin AI, Safeguard's analysis engine, is tuned to recognize the obfuscation patterns these campaigns rely on -- goto-shattered control flow, hex-encoded strings, disguised binary names -- surfacing behaviorally suspicious packages even before they're named in a public advisory. And when a malicious or vulnerable version is confirmed, Safeguard can open an auto-fix pull request that pins or replaces the dependency automatically, shrinking the window between disclosure and remediation from weeks to minutes.

Never miss an update

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