Security 9 min read Markdown

Packagist Made Stable Versions Immutable. Here Is What That Changes for You.

After the Laravel-Lang compromise, Packagist shipped version immutability, started publishing maintainer MFA status, and put release cooldowns on the roadmap. Some of your assumptions about composer.lock are now out of date, in both directions.

Matt King
Matt King
August 18, 2026
Last updated: August 18, 2026
Packagist Made Stable Versions Immutable. Here Is What That Changes for You.

For as long as most of us have used Composer, a version number on Packagist was a pointer rather than a fact. A maintainer could delete a tag and republish it, and the version you thought you had pinned could resolve to different code than it did last week.

That changed this year, and it changed because of a specific incident.

What prompted it

Over 22 and 23 May 2026, an attacker republished hundreds of malicious versions under historical release tags across several community-maintained Laravel localisation libraries. Reporting at the time put the count above 700 package versions.

Two details make this worse than a typical bad-package story.

The payload was in a helpers.php file wired into Composer's autoload.files entry. That means it ran on autoload, with no call site anywhere in your application. Installing was sufficient. Nothing had to use the package.

And it went into historical tags. Not new releases that cautious teams might have waited on, but versions that had existed for months and that lockfiles already referenced. A team that pinned a version a year ago and ran a fresh composer install in CI could receive the malicious code without changing a single line of their own configuration.

That is the specific hole immutability closes.

What Packagist actually shipped

Malware scanning, integrated in March 2026, flagging dangerous versions in the web UI and in the metadata Composer downloads.

Version immutability for stable versions, deployed in the week of 27 May 2026. A published stable version now refers to one set of code, permanently. Branch-based development versions such as dev-main remain mutable, which is correct, since that is the entire point of a development branch.

Public MFA status for maintainers, which Packagist has said it will begin publishing, alongside a stated intention to make MFA mandatory. No date has been attached to the mandate.

On the roadmap: FIDO2-confirmed staged releases, so that stolen credentials alone are not enough to publish. Minimum release-age policies, which would let you refuse versions published within the last few hours or days. Package freezing during an active compromise. Organisational package ownership, replacing the shared company accounts that make credential compromise so effective.

npm went through the same reckoning slightly earlier, implementing comparable controls in May 2026. The ecosystems are converging on the same answers because they were losing to the same attacks.

What this fixes, and what it does not

Worth being precise, because I have seen both over- and under-reactions.

Fixed. The code behind a version you already installed changing underneath you. If your lockfile references 1.4.2 and you install it today, you get what 1.4.2 was when it was published. That was genuinely not guaranteed before, and it is the exact vector used against Laravel-Lang.

Not fixed. A malicious new version. If an attacker publishes 1.4.3 with a payload, immutability guarantees only that 1.4.3 stays malicious. Anyone running composer update inside a caret constraint gets it. This remains the more common path.

Not fixed. A package that was already compromised when you first locked it. Immutability preserves the state, whatever the state was.

Not fixed. Transitive dependencies you have never looked at. Your lockfile probably pins several hundred packages, most of which you have never evaluated and could not name.

So the correct summary is that what you installed stays what you installed. That is meaningfully better and it is not the same as safe.

What to change in your own practice

Commit your lockfile and install from it. This is now worth more than it was, because the lockfile's guarantee is stronger. In CI and production, always:

composer install --no-dev --prefer-dist --no-interaction

Never composer update in a deployment pipeline. That was always true, and the difference now is that install gives you a real guarantee rather than a hopeful one.

Enable MFA on your Packagist account today if you publish anything. Given that MFA status is going to be public and mandatory MFA is planned, the only question is whether you do it on your schedule or on theirs. Nearly every recent PHP supply chain incident traced back to compromised maintainer credentials rather than to a flaw in Composer.

Approximate a cooldown now. Until minimum release-age lands natively, you can get most of the benefit by not updating dependencies the day a release appears. Most malicious releases are caught within hours. A weekly dependency update window, rather than continuous automated merging of every bump, costs you very little and skips the most dangerous period entirely. If you run Renovate or Dependabot, both support a minimum release age setting, and it is one of the highest-value configuration changes available to you.

Keep composer audit failing your build. Immutability does not tell you about known vulnerabilities in what you have already got.

composer audit --locked

The --locked flag audits the lockfile rather than the installed tree, which is what you want in CI where dev dependencies may differ.

Review autoload.files in your dependency tree. The Laravel-Lang payload worked because Composer executes those files automatically. It is worth knowing which of your dependencies use that mechanism, since it is a much smaller list than your total dependency count and a much higher-privilege one:

grep -r '"files"' vendor/*/*/composer.json | head -40

Most legitimate uses are helper functions and polyfills. Anything there is code that runs before your application does.

The part that has not changed

Composer itself remains a program that executes code from the internet on your build machines, and that is inherent to how package managers work rather than a bug anyone can fix. Install scripts run. Autoloaded files run. Plugins run with considerable privilege.

Two Composer vulnerabilities disclosed this year, CVE-2026-40176 and CVE-2026-40261, made that point from a different direction, and we covered them in Composer's hidden attack surface. The platform changes described here reduce the chance of a malicious package reaching you. They do not change what a malicious package can do once it arrives.

Which means the boring controls still carry the weight: pin with a lockfile, keep the update surface small, know what runs at autoload, keep composer audit in CI, and give build machines credentials scoped tightly enough that a compromised build cannot reach production. The background on how these packages get in is in PHP supply chain attacks, and the account-takeover angle in fake Laravel packages on Packagist.

StackShield monitors the external surface of your deployed application, which is where the consequences of a compromised dependency usually become visible. Run a free scan to see what yours currently exposes.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.

Frequently Asked Questions

What is Packagist version immutability?

Stable versions published to Packagist.org can no longer be re-pointed to different code after the fact. Previously a maintainer, or an attacker with a maintainer's credentials, could delete and republish a tag so that an existing version number resolved to new content. Immutability closes that, so a version number now refers to one set of code permanently. Branch-based development versions remain mutable by design.

What was the Laravel-Lang supply chain attack?

In late May 2026 an attacker republished hundreds of malicious versions under historical release tags across several community-maintained Laravel localisation libraries. The malicious code was placed in a helpers.php file wired into Composer's autoload.files entry, which means it executed automatically on autoload rather than requiring anything in the application to call it. Reporting at the time put the number of affected package versions above 700.

Does immutability mean composer.lock is now safe?

It removes one specific attack, where the code behind a version you already locked changes underneath you. It does nothing about a malicious new version being published, which is still the more common path, and nothing about a package that was already compromised when you locked it. A lockfile plus immutability means what you installed stays what you installed, which is a real improvement and not the same as safety.

Should I enable MFA on my Packagist account?

Yes, and now rather than later. Packagist has said it intends to publish maintainer MFA status publicly and that mandatory MFA is planned, so this is going to happen either at a time you choose or at a time you do not. Nearly every recent PHP supply chain incident traces back to compromised maintainer credentials rather than to a technical flaw in Composer itself.

What is a release-age or cooldown policy for dependencies?

A rule that refuses to install package versions published within the last few days, on the reasoning that most malicious releases are detected and pulled within hours. It trades a short delay in receiving legitimate updates for avoiding the window in which compromised releases are most dangerous. Packagist has signalled minimum release-age policies as a coming feature, and some teams already approximate it with a private mirror.

Stay Updated on Laravel Security

Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.