Composer 2.11.0 Adds Cooldown Policy to Combat Malicious Package Releases
Composer 2.11.0 introduces an optional cooldown policy to delay the installation of new packages, helping developers mitigate risks from malicious releases.
Composer 2.11.0 adds a cooldown dependency policy. It holds back newly published package versions for a set period before Composer will install them. The aim is to narrow the window in which a malicious release can reach projects before anyone flags it. The release also includes several security fixes, removes the dist-to-source fallback, and speeds up dependency resolution.
Packagist's maintainers say most malicious releases seen on Packagist.org in recent months came from attackers who briefly took over a legitimate package, for example through a compromised GitHub account. The malware policy added in Composer 2.10 blocks such versions once they are flagged, but a gap remains between publication and flagging. Automated scanners can raise flags within minutes, so even a one-hour cooldown gives them a chance to act. Manually reviewed reports take longer, and a delay of a few hours up to a day improves the odds of catching those. The maintainers say no cooldown can guarantee a bad release is flagged in time.
How the cooldown works
The policy is off by default. It is set under config.policy with a period value, such as "1 day" or "6 hours". Accepted units are second, minute, hour, day and week, or an integer number of seconds. The setting can also go in the global Composer configuration or in the COMPOSER_POLICY_COOLDOWN_PERIOD environment variable, which suits CI.
The policy applies when Composer resolves dependencies, which means update and require. composer install still installs exactly what is in composer.lock. require, init and create-project skip withheld versions when choosing a version. outdated and show --latest report the newest version the policy allows and note releases still in cooldown. Dev versions such as dev-main and platform packages are never withheld. A full composer update re-evaluates every package, so Composer may swap a locked version still in its cooldown for an older one. A partial update leaves locked packages that are not being updated alone.
Security fixes are held back too. Composer does not exempt them automatically, because an attacker with a package's repository access could publish an advisory or release a malicious version after a real one. Developers can instead pass the new --no-cooldown option to update, require, remove or create-project. It skips only the cooldown, and blocking for security advisories, malware and other policies stays on. An ignore option exempts packages permanently, such as a company's own, and supports reasons, version constraints and wildcards. The maintainers suggest a period of a few hours to a day, since longer delays also postpone fixes for vulnerabilities a team is not tracking.
Why it took until 2.11
Composer 2.10 introduced dependency policies, but its metadata had no reliable timestamp for a cooldown. The existing time field comes from the tag's commit date or from the package's composer.json, and the publisher controls both. An attacker could backdate a malicious release by weeks and pass straight through. Packagist.org now records a published-time for each version, based on when the site first made it available. If another repository does not report one, the command fails and names the package. For trusted repositories, require-published-time can be set to false to fall back to time. VCS, path and artifact repositories only have time.
Private Packagist already supports the policy. Organization admins can set a cooldown for every project using the organization's Composer repository, whatever Composer version those projects run. If a version's code changes, for example through a force-pushed tag, its cooldown restarts.
Security fixes and other changes
The release fixes several security issues:
- Package names in
installed.jsoncan no longer escape the vendor directory during install and reinstall (GHSA-c968-33g3-wh7p). - Inline HTTP headers such as API tokens are no longer written into
composer.lockorinstalled.json(GHSA-fm2w-xgf2-f9fh). - TLS connections presenting the distrusted, compromised eMudhra intermediate certificates are rejected.
- Bin proxies no longer embed package-controlled paths and shebangs unescaped, and bin paths with shell metacharacters are rejected.
secure-httpnow catches insecure URLs that are not strictly valid or that use git's<transport>::<address>syntax.- Repository options such as custom headers are no longer sent to other hosts whose names merely start with the repository host's name.
Composer 2.11 also removes the temporary source-fallback option from 2.10, so failed dist downloads no longer fall back to source checkouts. Source installs remain available through preferred-install or --prefer-source. Custom policies can now use block-scope set to all to block matching versions during composer install. Composer now marks the lock file as outdated when the policy config changes, so projects with a policy config will see a lock file warning after upgrading until they update it.
Other changes include faster dependency resolution with lower memory use, and no more HTTP/3 requests, which added delay to new connections with recent curl versions. composer outdated and show --latest now load metadata in parallel, and composer audit gains a --direct (-D) option. Composer now retries after HTTP 429 responses with a Retry-After header and supports Bitbucket Cloud's ssh.bitbucket.org hostname. Repositories can restrict loaded branches with only-branches, and composer install now fails on a content-hash merge conflict in composer.lock. Users can upgrade by running composer self-update.
