Table of contents
Laravel-Lang Composer tag-rewrite Supply Chain Attack
On 2026-05-22, an attacker rewrote every repository tag across four Composer packages in the Laravel-Lang ecosystem to point at malicious commits. The affected packages are laravel-lang/lang, laravel-lang/attributes, laravel-lang/http-statuses, and laravel-lang/actions. The rewrite took place on 2026-05-22 into the early hours of 2026-05-23. Every malicious commit makes the same two-file change: one entry added to composer.json, and one new file at src/helpers[.]php. This article walks through both files as they actually exist in a poisoned tree.
The compromise
The malicious commits in all four repositories share the same git author identity, Your Name <you@example.com>. The commits are not reachable from any branch in the upstream repositories; only the rewritten tags point to them. The combination of the shared author identity, the orphan-commit topology, and the compressed rewrite window across four repositories is consistent with one operator running through the four targets in sequence.
The composer.json change
The poisoned manifest’s autoload block contains two entries:
"autoload": {
"psr-4": {
"LaravelLang\\Lang\\": "src/"
},
"files": [
"src/helpers.php"
]
}The PSR-4 entry maps the package’s LaravelLang\Lang\ namespace to src/. The "files" entry is the attack vector.
Composer’s autoload.files directive is documented to load every listed file immediately when an application requires vendor/autoload.php. Unlike PSR-4 entries, which are lazy and only load classes on first reference, autoload.files entries are eager: they execute the moment the autoloader is bootstrapped. In a Laravel application this happens on every HTTP request boot. In a Composer-using CLI tool it happens on every command invocation. In a CI workflow it happens the first time any step touches the project’s autoload.
Inside helpers.php
The dropper opens with two decoy functions, laravel_lang_locale() and laravel_lang_fallback(), that wrap Laravel’s config() and return the application locale. The malicious behavior lives in an anonymous closure guarded by a LARAVEL_LANG_HELPERS define-check. The closure runs once per host, and the once-enforcement is a marker file:
The marker filename is an MD5 over three values: the install directory, the host’s network name returned by php_uname('n'), and the inode of helpers.php itself. The closure returns early on a second invocation, so the dropper fires exactly once per vendor/ tree per host.
The C2 host is kept out of plain-text scans by reconstructing it from a byte array:
The byte array spells flipboxstudio[.]info.
The fetcher tries file_get_contents first with a custom stream context, then falls back to libcurl if the first returns less than 50 bytes. Both transports set verify_peer => false, CURLOPT_SSL_VERIFYPEER => false, and a spoofed Mozilla/5.0 User-Agent. The 50-byte minimum is the dropper’s signal-quality check: it tolerates a transport failure but rejects an empty or near-empty response.
If the fetch returns content, the dropper writes the response to a random filename under sys_get_temp_dir()/.laravel_locale/ (bin2hex(random_bytes(6)) produces a 12-hex-character name) and branches on OS for launch. On Linux and macOS the launch is exec("php \"$f\" > /dev/null 2>&1 &"). On Windows the dropper writes a 4-byte-randomized .vbs shim that calls WScript.Shell.Run with a non-blocking flag, invoked from cscript //nologo //b. Every filesystem and network call in the closure uses PHP’s @ error-suppression operator, so no warning or stack trace reaches PHP-FPM, the web server, or error_log.
The full closure is roughly fifty lines of PHP. By the time control returns from vendor/autoload.php, the stage-two PHP is already running detached in the background.
What runs next
Analysis of the stage-two payload served from hxxps://flipboxstudio[.]info/payload shows a PHP credential stealer targeting cloud, container, and developer-machine credentials: EC2 instance metadata at 169.254.169.254, Kubernetes service-account tokens at /var/run/secrets/, HashiCorp Vault, Jenkins master.key and credentials.xml, Linux /proc/<pid>/environ and /cmdline, and Chrome v127+ App-Bound Encryption via a dropped DebugChromium.exe. The harvested data is XOR-encrypted with the key k9X2mP7vL4nQ8wR1 and POSTed to hxxps://flipboxstudio[.]info/exfil.
Indicators of compromise
| Category | Indicator |
|---|---|
| C2 domain | flipboxstudio[.]info |
| Stage-one URL | hxxps://flipboxstudio[.]info/payload |
| Stage-two exfil URL | hxxps://flipboxstudio[.]info/exfil |
| Drop directory | <sys_get_temp_dir>/.laravel_locale/ |
| Stage-two PHP filename | ^[0-9a-f]{12}\.php$ |
| Windows launcher filename | ^[0-9a-f]{8}\.vbs$ |
| Windows stage-three binary | DebugChromium.exe |
| Cloud-metadata egress | Outbound to 169.254.169.254 from a PHP process |
| In-process constant | LARAVEL_LANG_HELPERS |
composer.json indicator | "files": ["src/helpers.php"] entry under autoload |
| Affected packages | laravel-lang/lang, laravel-lang/attributes, laravel-lang/http-statuses, laravel-lang/actions |
| Rewrite window | 2026-05-22 into 2026-05-23 |
| Malicious commit author | Your Name <you@example.com> |
Remediation
- Block
flipboxstudio[.]infoat DNS and HTTPS proxy layers, then investigate. - Audit
composer.lockagainst upstream history. For eachsource.referenceSHA recorded underlaravel-lang/lang, laravel-lang/attributes, laravel-lang/http-statuses, orlaravel-lang/actions, look the SHA up in the upstream repository and check the commit author. Any commit authored byYour Name <you@example.com>is a poisoned install. Lockfiles whose pinned SHAs pre-date 2026-05-22 and are reachable from upstream branch history are safe, provided nocomposer updatehas run since. - Reinstall against a known-good commit. Tag-based version constraints in
composer.jsonresolve to whatever the rewritten tag now points at, so re-runningcomposer installagainst a tag constraint is not sufficient. Pin each affectedlaravel-lang/*dependency to a commit SHA that pre-dates 2026-05-22 and is reachable from upstream branch history, then runcomposer update <package>to refetch. Confirm afterwards thatvendor/laravel-lang/*/composer.jsonno longer contains the"files": ["src/helpers.php"]autoload entry and thatsrc/helpers.phpis no longer in the package tree. - Remove working artifacts. Delete
<sys_get_temp_dir>/.laravel_locale/on every affected host. - Rotate credentials. On CI runners that ran the install, rotate every cloud key, registry token, and deploy key the workflow had access to, and audit the job’s GitHub API activity. On developer machines, treat the host as fully compromised.
- Going forward, treat the lockfile’s pinned commit SHAs as the source of truth and verify each SHA is reachable from the upstream repository’s branch history before deploying. Tag-based resolution alone does not detect the kind of tag rewrite seen in this incident.
Mend.io coverage
Mend.io has issued an MSC covering all four affected Laravel-Lang Composer packages:
- MSC-2026-5762
References
https://socket.dev/blog/laravel-lang-compromise
Aikido Security: https://www.aikido.dev/blog/supply-chain-attack-targets-laravel-lang-packages-with-credential-stealer
Laravel-Lang/attributes issue #1085: https://github.com/Laravel-Lang/attributes/issues/1085
StepSecurity: https://www.stepsecurity.io/blog/laravel-lang-supply-chain-attack