astropod sign in

Laravel lang tag rewrite supply chain attack

Published on gastropod.io on 07-15-2026

Laravel-Lang and the Version That Wasn't: When a Tag Stops Meaning What You Think It Means


Most supply chain attacks work by getting a bad version published. The Laravel-Lang compromise worked by making an old, already-trusted version quietly stop being what it used to be.

On the night of May 22, 2026, someone with organization-level access to the Laravel-Lang GitHub org — the maintainers of laravel-lang/lang, a third-party PHP localization package used across a meaningful share of new Laravel projects, along with three sibling packages — didn't publish a new malicious release. They rewrote the git tags. Every one of them. laravel-lang/lang alone has 502 historical tags going back nearly a decade, and between 22:32 UTC and midnight, the attacker repointed a large share of those tags to a single malicious commit sitting in a fork they controlled. GitHub allows a tag to reference a commit from any fork of the same repo, which is normally a convenience. Here, it meant a version number that had shipped safely for years could be silently repointed without anyone touching the main branch or opening a pull request.

Anyone who ran composer install or composer update against one of the four affected packages during that window — even pinning to what looked like a known-good version — pulled the malicious commit instead. The payload landed in a new file, src/helpers.php, registered under Composer's autoload.files. That's the detail that made this one nasty: Composer's autoloader unconditionally requires every file in that list, so the backdoor ran the instant any PHP process booted — no import, no function call, no user action. It fingerprinted the host, then pulled a cross-platform stealer that went after cloud credentials, GitHub and GitLab tokens, SSH keys, Kubernetes and Vault secrets, browser and password-manager data, and cryptocurrency wallets. Researchers who counted differently — Aikido looking at currently published artifacts, Socket walking the full git history — landed on different totals (233 versions versus roughly 700), which is itself a small illustration of how hard "how much of this is actually out there" gets to answer once history itself has been edited.

The uncomfortable part

Version pinning is supposed to be the safety net here — you locked to 12.4.2, so you're fine, right? Not if 12.4.2 doesn't point to what it pointed to yesterday. A tag is a label an org controls, not a cryptographic guarantee. The actual guarantee is the content behind it, and that's a digest, not a string.

This is the specific gap Gastropod resolves artifacts around: identity is tracked by exact cryptographic digest, not by trusting that a version tag still means what it meant the last time it was pulled. If a previously-resolved package version shows up with a different digest than what's on record, that's a mismatch worth surfacing — a "this claims to be the same thing you already know, and it isn't" flag — rather than something that silently rides through on the strength of a familiar-looking version string. Paired with the audit spine tracking who pulled what and when, it also turns "did anyone in our org install this during the compromise window" from a scramble through Composer logs into a direct query. We cover how digest-based identity works on the product page, if you're curious about the mechanics.

It won't stop an org-level credential from getting stolen in the first place, and it wouldn't have kept the attacker out of Laravel-Lang's GitHub org. But it changes what happens in the hours after — whether "which of our services pulled the poisoned tag" is a question you can answer immediately or one you're still investigating three days later while secrets sit exposed.

Socket's full writeup has the technical breakdown and IOC list; StepSecurity's analysis includes the raw evidence from detonating one of the compromised versions in an isolated runner, worth a look if you're doing your own triage.

If your project depends on laravel-lang/lang, laravel-lang/http-statuses, laravel-lang/attributes, or laravel-lang/actions and you installed or updated between May 22–23, 2026, treat every secret on that machine as compromised and rotate before you do anything else.

← news