DEV Community

Cover image for Fake Laravel Packages Are Targeting Your .env: How to Audit Your Composer Dependencies
Hafiz
Hafiz

Posted on • Originally published at hafiz.dev

Fake Laravel Packages Are Targeting Your .env: How to Audit Your Composer Dependencies

Originally published at hafiz.dev


Something happened on March 4 that every Laravel developer should know about.

Security researchers at Socket published findings on three Packagist packages (nhattuanbl/lara-helper, nhattuanbl/simple-queue, and nhattuanbl/lara-swagger) that were silently deploying a cross-platform remote access trojan on any machine that installed them. The packages looked legitimate. Helper utilities. Queue tools. Swagger integration. The kind of thing you install without thinking twice.

The RAT connects to a command-and-control server, transmits a full system profile, and waits for instructions. On Windows. On macOS. On Linux. It doesn't care. And here's what should actually worry you: once installed, it runs in the same process as your Laravel app, which means it has full access to your .env file. Your database credentials, Stripe keys, API secrets. All of it. At the time of writing, the packages were still live on Packagist.

This is a supply chain attack. It won't be the last one.

What Actually Happened

The attack is clever in a boring, unsexy way. The threat actor published six packages under the same author account, three of them completely clean. Those clean packages existed just to build trust. Some were published months earlier, in mid-2024, so the account had history.

Then came the malicious ones.

nhattuanbl/lara-helper and nhattuanbl/simple-queue both contained an identical payload hidden in src/helper.php. The file is 27,000+ bytes delivered as a single continuous line after the opening <?php tag. Good luck reading that. The code uses three layers of obfuscation: hundreds of randomized goto jumps with meaningless labels, every string encoded in hex or octal so nothing readable appears in plaintext, and variable names that are randomly generated garbage strings like $riz07 and BsYhQ().

nhattuanbl/lara-swagger contained no malicious code at all. It just listed nhattuanbl/lara-helper as a hard Composer dependency. Install swagger, get the RAT included.

The activation mechanism is what makes it particularly nasty for Laravel apps. In lara-helper, the package registers a service provider through Composer's auto-discovery. That means helper.php gets loaded on every single application boot. Not just once. Every. Boot.

The RAT retries its C2 connection every 15 seconds indefinitely. Even if the attacker's server goes offline, you're still infected. They can point it at a new host whenever they want without touching your code at all.

This persistence is the part that makes it worse than a typical one-time credential grab. You're not dealing with a breach that happened and is now over. You're dealing with an open door that stays open until you close it deliberately.

Step 1: Check If You're Affected Right Now

If you've ever installed packages without carefully auditing them, run this in your project root:

grep -r "nhattuanbl" composer.json composer.lock
Enter fullscreen mode Exit fullscreen mode

If that returns nothing, you're clear from this specific attack. But run it across every project you maintain, not just the one you have open right now.

Also check your vendor directory directly:

find vendor -name "helper.php" -path "*/nhattuanbl/*"
Enter fullscreen mode Exit fullscreen mode

If you find anything, don't just delete the files. The service provider may have already registered and run. Treat it as a full compromise: rotate every secret in your .env, revoke all API keys, and check your server logs for outbound connections to helper.leuleu.net on port 2096.

Step 2: Run composer audit on Every Project You Own

Most Laravel developers have never run composer audit. I'd guess that's true for at least 70% of people reading this. It's been available since Composer 2.4, released in 2022, and most people still don't use it.

Here's how simple it is:

composer audit
Enter fullscreen mode Exit fullscreen mode

That's it. Composer queries the Packagist Security Advisory API and cross-references your installed packages against known CVEs and community-reported vulnerabilities. If something's wrong, you get output like:

Found 1 security vulnerability advisory affecting 1 package.
Package: vendor/package-name
CVE: CVE-2024-XXXX
Title: Remote code execution via crafted input
Affected versions: >=2.0.0,<2.3.5
Enter fullscreen mode Exit fullscreen mode

If you're clean: "No security vulnerability advisories found" and an exit code of 0.

One thing to understand: composer audit covers known vulnerabilities that have been reported and added to the advisory database. The fake packages in this attack weren't in any advisory database. The payload was discovered by Socket's own static analysis, not through CVE reports. So composer audit alone won't catch every threat. It's a floor, not a ceiling.

But it's a floor most developers aren't standing on yet.

There's one more nuance worth knowing. composer audit by default checks installed packages. If you want to audit against a composer.lock without actually installing, use:

composer audit --locked
Enter fullscreen mode Exit fullscreen mode

This is useful in CI environments where you want to validate the lock file before a full install step runs.

You can also run it during install:

composer install --audit
Enter fullscreen mode Exit fullscreen mode

Composer 2.4+ already does abbreviated auditing on composer require and composer update by default. The standalone composer audit command gives you the full report on everything currently installed.

Step 3: Add roave/security-advisories to Every New Project

composer audit tells you about problems after they're already installed. roave/security-advisories prevents them from being installed in the first place.

It's a meta-package with no PHP code. It's just a conflict list against every package version with a known security advisory. When you try to install a vulnerable version, Composer refuses with an error.

composer require --dev roave/security-advisories:dev-latest
Enter fullscreen mode Exit fullscreen mode

Add this to require-dev in every new Laravel project from this point forward. It won't protect against zero-days or novel malware like the RAT above, but it blocks a huge category of known-bad packages automatically.

The dev-latest version is intentional. There are no tagged releases because the list of vulnerabilities is constantly updated. You want the latest.

Step 4: Know the Red Flags Before Installing Any Package

The packages in this attack were designed to look legitimate. But there are signals that should have stood out.

Check the download count. nhattuanbl/lara-helper had 37 total downloads. nhattuanbl/lara-swagger had 49. Legitimate Laravel utility packages with real functionality get thousands of downloads. A Swagger integration with 49 downloads should immediately raise questions. Check packagist.org/packages/vendor/package-name before installing anything unfamiliar.

Look at the source before running it. This is the one that feels tedious but genuinely matters. Before you composer require anything obscure, click through to the GitHub repo and look at what's in src/. For small utility packages this takes two minutes. A 27,000-byte single-line PHP file is not normal. You'd catch it.

Check account history vs. publish dates. The nhattuanbl account existed since 2015 but all the malicious packages were published in late 2024. An old account suddenly releasing several packages in a short window is a pattern worth noticing. Established authors don't appear from nowhere.

Be skeptical of auto-discovered service providers. Auto-discovery is convenient, but it means the package's code runs on every boot without you explicitly invoking it. For packages from established authors like Spatie, it's fine. For something with 37 downloads, read the service provider first. If you keep your dependencies lean and deliberate (a habit that also pays off in other ways, as covered in the Laravel API best practices guide) you simply have less surface area to worry about.

There's also a broader principle here. There's a real difference between packages from authors with public track records and packages from accounts you've never heard of. Spatie, the core Laravel team, Livewire, Filament, Pest: these have open development histories, known maintainers, and real community scrutiny. A package from an account with no prior reputation and 49 downloads doesn't have any of that. That doesn't mean you can never install a small or niche package. It means the bar for reading it carefully should be higher, not lower.

Step 5: If You Were Hit, Here's What to Rotate

If you find anything suspicious, assume full .env exposure. Don't guess. Rotate everything.

Laravel app key: this protects encrypted cookies and session data:

php artisan key:generate
Enter fullscreen mode Exit fullscreen mode

Note: rotating the app key will invalidate encrypted values stored in your database (anything run through Crypt::encrypt). Hashed passwords are fine though; they don't use the app key. Plan accordingly.

Database password: change it in your database console, then update .env. Use something actually strong; the password generator generates cryptographically random strings if you need one quickly.

Stripe and payment credentials: go into your Stripe dashboard and roll the API keys. Do this first, before anything else. Payment credentials are the highest-value target.

OAuth client secrets and social auth: Google, GitHub, whatever you use. Regenerate in those provider consoles and update .env. If you're using Sanctum or Passport for API authentication, wipe all tokens from the database and invalidate active sessions. The differences in how each handles token storage are worth understanding if you haven't already. The Passport vs Sanctum breakdown covers the specifics.

Every other API key in your .env: go line by line. Any credential for an external service needs to be regenerated. If your app creates its own tokens internally, the hash generator can help you generate new signing keys fast.

After rotating, check your server logs for unexpected outbound connections:

grep "helper.leuleu.net" /var/log/nginx/access.log /var/log/apache2/access.log 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

Step 6: Lock This Down in CI/CD

The best time to catch a vulnerable dependency is before it reaches production. Not at 11pm reading a security advisory.

Here's a minimal GitHub Actions workflow that fails your build if composer audit finds anything:

name: Security Audit

on: [push, pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist --no-progress
      - name: Run security audit
        run: composer audit
Enter fullscreen mode Exit fullscreen mode

Since composer audit exits with a non-zero code when vulnerabilities are found, your pipeline fails automatically. No extra configuration needed.

If you have known vulnerabilities you can't immediately fix (say, you're pinned to an older package version for compatibility reasons), you can ignore specific advisories temporarily:

composer audit --ignore=CVE-2024-XXXX
Enter fullscreen mode Exit fullscreen mode

Be deliberate about it though. Ignored advisories have a way of staying ignored forever.

The Uncomfortable Reality

Here's my honest take: the security tooling around Packagist is actually pretty good. composer audit works. roave/security-advisories works. The advisory database is actively maintained. Composer 2.9 even added automatic blocking of packages with known advisories by default.

The problem isn't the tooling. It's that most developers never think about their dependencies as an attack vector until something goes wrong.

The packages in this attack had very low download counts. That's a weak signal, but it's a signal almost nobody checks. You see a package name that sounds useful, skim the README, and run composer require. I've done it. You've probably done it too.

The fix isn't to become paranoid about every package or stop using Packagist. It's to add one command to your workflow. Run composer audit. Add roave/security-advisories to new projects. Check download counts before installing anything you've never heard of.

These are five-minute habits. The RAT that's been running on someone's server since February isn't.

If you want to go further on the authentication side (reducing the blast radius of credential theft at the app layer), the passkeys guide is worth reading alongside this.

FAQ

Are these packages still available on Packagist?

At the time of writing, the packages were still live after Socket submitted takedown requests. Packagist's process for removing packages takes time. Verify the current status at packagist.org/packages/nhattuanbl rather than assuming they've been taken down.

Will composer audit catch attacks like this in the future?

Not automatically, no. composer audit checks against known advisories in the Packagist database. Newly discovered malware won't appear there until someone files an advisory. What it does catch is the much larger universe of packages with disclosed CVEs, which is still extremely valuable. Think of it as one layer in a defense-in-depth approach, not the whole defense.

How can I verify that roave/security-advisories is working?

Try running composer require laravel/framework:8.22.1. That version has a known vulnerability, and the package should block the install and throw an error. If it does, it's working correctly.

Does this affect Laravel apps on shared hosting?

Yes. The RAT payload is cross-platform and activates through Laravel's service provider auto-discovery. It doesn't need root access. Shared hosting environments are not protected by default.

Should I audit my old projects too?

Yes, specifically any project that's been actively updated in the last year or had packages added. Run grep -r "nhattuanbl" composer.lock across your project directories. If you want to scan everything at once: find ~/projects -name "composer.lock" -exec grep -l "nhattuanbl" {} \;.

Start Today, Not After the Next Incident

Supply chain attacks on package registries are increasing across every ecosystem: npm, PyPI, RubyGems, and now Packagist in a more targeted way. The Laravel ecosystem is large enough to be a worthwhile target, and this won't be the last attempt.

The good news is the tools exist and they're easy. composer audit takes three seconds. roave/security-advisories takes one line. Neither replaces a full security review, but both close the gap that most Laravel projects currently have, for almost zero effort.

Run the audit on your projects today.

If you're building a production Laravel application and want a security-conscious developer involved from the start, get in touch.

Top comments (0)