Composer 2.11.2 and 2.2.32 (the long-term support line) were released on 10 November 2026 with seven security fixes. Several of them involve a crafted composer.lock or vendor/composer/installed.json, which matters because those files often arrive through pull requests. This guide explains what was fixed in plain English, how to upgrade on laptops, CI and Docker images, and the new options that came with the release.
Why this release matters
Most PHP teams run composer install automatically in CI on every pull request. If a lock file can trigger command execution or leak credentials, then anyone who can open a pull request against your repository may be able to attack your CI runner. That is the main risk behind several of the fixes, so treat this as a priority upgrade, especially for open source projects that build external contributions.
The security fixes, explained
From the official 2.11.2 release notes:
- Command execution via a transport option (GHSA-jhfp-4ppm-crmm). The
prevent_url_access_callabletransport option could be loaded fromcomposer.lockorinstalled.json, allowing arbitrary command execution. - Zip symlink path escape (GHSA-w6hj-6587-3jh5). A malicious zip archive could write files outside the package directory through symlinked entries when extraction fell back from
unzipto PHP’sZipArchive. - Credentials sent to other hosts (GHSA-x82q-x274-r6pq). Inline repository HTTP credentials could be sent to dist mirrors on other hosts supplied by a crafted lock file.
- Bundled symfony/filesystem (GHSA-6prc-h956-hxjq). The copy shipped with 2.11.1 had a disclosed vulnerability; it is updated.
- Headers on cross-origin redirects.
AuthorizationandCookieheaders set in request options (for example via a repository’shttp.headeroption) were resent when a redirect went to another origin. - Config read from the wrong places. Non-auth settings in a project’s
auth.jsonor theCOMPOSER_AUTHvariable, andhomein a project’scomposer.json, are now ignored with a warning. - IP blocking timing. IPs blocked by
prevent_ip_access_callablewere only rejected after the request had been sent; in-flight downloads are now stopped.
Step 1: check your version
composer --version
You want 2.11.2 or later on the 2.x mainline, or 2.2.32 if you deliberately stay on the 2.2 LTS line (usually for very old PHP versions).
Step 2: upgrade
For a normal global install:
composer self-update
# or pin explicitly
composer self-update 2.11.2
# LTS users
composer self-update --2.2
If Composer was installed by a package manager (Homebrew, apt, Chocolatey), update it through that manager instead, then confirm with composer --version. Distribution packages can lag behind, so check the version rather than assuming.
Step 3: fix your Docker images
Many Dockerfiles copy Composer from the official image. A floating tag like composer:2 picks up the fix on the next rebuild, but only if you actually rebuild without a stale cache:
# Dockerfile
FROM php:8.5-cli
COPY --from=composer:2.11.2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --prefer-dist --no-scripts
COPY . .
Pinning the exact version makes the upgrade visible in code review. Remember to rebuild base images that bake Composer in.
Step 4: check CI
Hosted CI images and setup actions may ship their own Composer. Add a guard step so builds fail if an old version sneaks back in (this assumes the 2.11 line; adjust it if you use the 2.2 LTS):
# GitHub Actions example step
- name: Check Composer version
run: |
v=$(composer --version --no-ansi | awk '{print $3}')
echo "Composer $v"
printf '%s\n' "2.11.2" "$v" | sort -V -C || { echo "Composer too old"; exit 1; }
If you use shivammathur/setup-php, you can request a specific Composer version with tools: composer:v2.11.2.
Step 5: harden how CI handles untrusted pull requests
Upgrading closes the known holes, but the general lesson is that lock files are code. Good habits:
- Run jobs for pull requests from forks without secrets, and never with deploy credentials.
- Use
--no-scriptswhen you only need dependencies installed for static analysis. - Review
composer.lockdiffs in pull requests, especially new repositories, URLs ortransport-options. - Run
composer auditto check your installed packages against known advisories.
composer audit --locked
New features in 2.11.2
The release is not only security fixes. It also adds:
- A download stall timeout. Downloads from a server that accepts the connection but never responds are now aborted and retried after 30 seconds, instead of hanging for 300. Configure or disable it with
COMPOSER_HTTP_STALL_TIMEOUT. exclude-branchesfor VCS repositories, to skip branches matching patterns, for example bot-managed branches.allow-git-fallback: falsefor GitLab, Bitbucket and Forgejo repositories, to fail on API errors instead of quietly falling back to a git clone.- Performance work: faster security advisory lookups (one API query per package name), faster VCS updates when the mirror is current, and lower memory use in dependency resolution.
Here is how the new VCS options might look in composer.json:
{
"repositories": [
{
"type": "vcs",
"url": "https://gitlab.example.com/acme/billing-sdk.git",
"exclude-branches": ["renovate/*", "dependabot/*"],
"allow-git-fallback": false
}
]
}
And for a slow mirror in CI:
COMPOSER_HTTP_STALL_TIMEOUT=60 composer install --no-interaction
Bug fixes you might notice
composer configno longer corruptscomposer.jsonby turning nested{}into[]when adding or removing dotted keys.config --unset,removeandrequireno longer leave or duplicate keys set tonull.- A curl 8.23+ URL parsing failure now shows the real error instead of an undefined array key warning.
Quick upgrade checklist
- Run
composer --versionon every developer machine, CI image and Docker base image. - Upgrade to 2.11.2 (or 2.2.32 on LTS).
- Pin the version in Dockerfiles and CI setup.
- Remove secrets from jobs that build untrusted pull requests.
- Run
composer auditand review recent lock file changes.
Takeaway
Composer 2.11.2 fixes real attack paths that start with a crafted lock file, which is exactly what an outside pull request can contain. The upgrade is a one-line composer self-update, but the important part is making sure every CI runner and container image actually gets it. While you are there, try the new stall timeout and VCS options; they make installs faster and failures clearer.
