← Blog · Guides

Composer 2.11's cooldown: refuse to be the first to install a new release

November 3, 2026

Composer 2.11.0, released on 29 October 2026, adds a dependency policy that works the other way round from a malware scanner. A scanner tries to spot a bad release. The new cooldown policy refuses to install any package version that was published more recently than a period you choose. In the release notes' words, it was "Added a `cooldown` policy to withhold newly released package versions for a configurable period, reducing exposure to compromised releases".

It is not on by default. Upgrading to 2.11 gives you the option, not the protection: until you set a period, Composer behaves exactly as before. We read the 2.11.0 documentation and ran the policy against real Packagist data, so the behaviour below is what Composer did, not a paraphrase.

Why wait at all

The Packagist team's announcement explains the reasoning. Most malicious releases they have seen on Packagist.org "were published by attackers who briefly gained control of a legitimate package", for example through a compromised GitHub account. Composer 2.10's malware policy blocks a version once it has been flagged, but there is always a gap between publication and flagging. A cooldown keeps new versions out of your composer update during that gap. The announcement is careful about the limit: "A cooldown period cannot guarantee that a malicious release is flagged in time, but every hour of delay increases the chance."

The age of a version is not taken from the package itself. The 2.11.0 documentation says the period "is measured from the publication time reported by the package repository (published-time, which Packagist.org provides), as package authors cannot influence it." An attacker who takes over a package cannot backdate a release to slip under the window.

Turning it on

Add a period under config.policy in composer.json:

{
    "config": {
        "policy": {
            "cooldown": {
                "period": "1 day"
            }
        }
    }
}

The period is a string such as "6 hours", "1 day" or "1 week" (units second, minute, hour, day or week), or an integer number of seconds. Relative phrases like "tomorrow" are rejected.

One practical catch from our test: composer config --json policy.cooldown '{"period":"10 days"}' fails with "is an invalid value for policy.cooldown, expected a boolean". The dotted key works and writes the same JSON as above:

composer config policy.cooldown.period "1 day"

For CI you can set it without touching the file. COMPOSER_POLICY_COOLDOWN_PERIOD="24 hours" overrides the configured period, and COMPOSER_POLICY_COOLDOWN_PERIOD=0 switches it off for that run.

What it did in our test

We used Composer 2.11.0 on PHP 8.5.12 on 3 November 2026 at 23:11 UTC, with guzzlehttp/guzzle as the subject. Packagist reported version 8.3.1 as published on 25 October and 8.3.0 on 18 October. With a ten-day period, 8.3.1 was about 22 hours too new.

  • No cooldown configured: composer require guzzlehttp/guzzle:^8.3 locked 8.3.1.
  • Period "10 days": the same command printed "1 package version(s) withheld by the cooldown policy" and locked 8.3.0. composer outdated then listed 8.3.1 with "(22h left)".
  • Period "10 days" plus --no-cooldown: 8.3.1 again. The flag bypasses the cooldown for one run; the documentation notes that other policies still apply.
  • The environment variable alone, with no config: COMPOSER_POLICY_COOLDOWN_PERIOD="10 days" locked 8.3.0.
  • A constraint only the new version satisfies (^8.3.1): the require failed and reverted composer.json. The error says the version is "still in the cooldown period configured in "policy.cooldown" (available in 22 hours)" and names the three ways out: the ignore list, --no-cooldown, or setting block to false.

The part that matters for deploys: composer install is not checked

The cooldown runs when Composer resolves dependencies, which means update and require. The announcement states it plainly: "composer install continues to install exactly what is in your composer.lock". We confirmed it. A lock file that already contained Guzzle 8.3.1, installed with a ten-day cooldown configured, installed 8.3.1. The only extra output was the warning that the lock file is not up to date with composer.json, because 2.11 now treats a change to the policy config as a lock file change.

So the cooldown protects the moment a new version enters your lock file, not the moment you deploy. If a compromised version is already locked, the cooldown will not remove it on install. A full composer update does re-check every locked version. In our test it downgraded 8.3.1 to 8.3.0, which is what the documentation describes, and it fails if no older version satisfies your constraints.

This is how Laravel deploys work on DeployBase. The deploy runs composer install --no-dev --optimize-autoloader against the composer.lock you committed. The place to configure the cooldown is wherever you run composer update: your machine, or the CI job that refreshes dependencies. That run needs Composer 2.11 or later. Commit the lock file it produces.

What it does not cover

The documentation lists what the cooldown never withholds:

  • Dev versions such as dev-main, because they are mutable branch state.
  • Locked packages that a partial update is not touching. composer update vendor/pkg or composer require keeps every other locked version as it is.
  • Platform packages: PHP itself, extensions and other platform requirements.
  • Versions with no publication date at all, such as an inline package repository entry without a time field. Composer lets these through rather than blocking them forever.

Packages from VCS, path or artifact repositories fall back to the time field in the package's own composer.json, and Composer says so when it withholds one. A Composer repository that does not report published-time makes the command error out while a cooldown is set, unless you set require-published-time to false for that repository.

Some packages you will want immediately, such as a security fix or your own company's packages. The ignore list exempts them, with a reason, and accepts wildcards:

{
    "config": {
        "policy": {
            "cooldown": {
                "period": "7 days",
                "ignore": {
                    "mycompany/*": "We trust the code in our own packages",
                    "symfony/security-bundle": "Security fixes need to be applied immediately"
                }
            }
        }
    }
}

The rest of 2.11.0

The same release carries nine fixes marked "Security:" in the changelog. Two stand out for teams that deploy from a lock file:

  • "Fixed inline repository HTTP headers, such as API tokens, being written into `composer.lock` (GHSA-fm2w-xgf2-f9fh)". If you configure private repositories with inline headers, check your committed lock files for tokens. Moving to 2.11 stops new ones being written; it does not remove ones already committed.
  • "Fixed package names in `vendor/composer/installed.json` being able to escape the vendor directory during install and reinstall (GHSA-c968-33g3-wh7p)".

One more connects to an earlier article of ours. Composer now rejects TLS connections that present the compromised eMudhra intermediate certificates, "even if the system CA store trusts their roots". On 20 October we wrote about the eMudhra certificate compromise and what to check in your container image. Composer 2.11 enforces this one distrust inside the tool itself, whatever your image's CA store says.

Also note one breaking change. The source-fallback option that 2.10 added as a temporary measure is gone, and dist installs no longer fall back to source. The release after it, 2.11.1 on 30 October, is a patch release whose notes do not mention the cooldown.

If you run Laravel on DeployBase, our Laravel deployment guide covers the deploy flow that runs composer install from your lock file.

Sources: composer/composer on GitHub, release 2.11.0 (published 29 October 2026) and release 2.11.1 (30 October 2026); Composer 2.11.0 documentation, doc/06-config.md and doc/03-cli.md at tag 2.11.0; Packagist blog, "Composer 2.11", Nils Adermann, 29 October 2026. All read 4 November 2026. Test runs by DeployBase on 3 November 2026 at 23:11 UTC with Composer 2.11.0 on PHP 8.5.12, using guzzlehttp/guzzle publication times as reported by repo.packagist.org.