Composer 2.11: what I would change after GHSA-c968

Composer 2.11: what I would change after GHSA-c968

Composer 2.11.0 shipped 29 Oct 2026 for GHSA-c968-33g3-wh7p. Ubuntu 24.04 archive composer still prints 2.7.1 and self-update is not defined. I would install the 2.11.1 phar, not composer update laravel/framework. This is not a Laravel-core CVE.

· 8 min read #php #security #composer #laravel

 GHSA-c968 is the Composer binary, not Laravel core

Caption: composer --version first. Then dpkg-query. Then which -a composer. Illuminate does not ship this advisory. composer update laravel/framework does not patch it.

I did not paste a crafted vendor/composer/installed.json at anything tonight. I opened Ubuntu 24.04.5 and installed archive php8.3-cli plus archive composer. php -v printed PHP 8.3.6, built 19 Oct 2026. PHP_VERSION_ID is 80306. dpkg-query -W composer printed 2.7.1-2. composer --version printed Composer version 2.7.1 2024-02-09 15:26:28. Then I typed the command every changelog tells me to run:

Command "self-update" is not defined.

That is the wall. Either I treat composer update in a Laravel tree as the patch, or I run composer self-update on a distro wrapper that is not a phar and tell myself I am on 2.11 because Packagist is green. This is a lab note. I did not write a remote exploit. I did not paste a .. package name.

What changed

getcomposer.org dates Composer 2.11.0 to 29 Oct 2026. GitHub tagged it at 2026-10-29T09:35:10Z. Same-day LTS: 2.2.31 at 2026-10-29T09:35:02Z. GHSA-c968-33g3-wh7p published the same day. Package: composer/composer, the tool. High 7.1 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H). CWE-22. Local. The advisory has no CVE id. I will not invent one. Debian still tracks it as TEMP-0000000-4A8BFB.

What I will treat as locked:

  • Composer trusted vendor/composer/installed.json without validating package names. A crafted name with .. segments made composer install delete outside vendor/, and composer reinstall also write files there. Lock and repository names were already rejected. The attacker has to write that JSON on the machine running Composer. Publishing a Packagist package cannot trigger it. A trusted local composer install is out of scope. The realistic case is a CI vendor/ cache that fork PRs can also write to.
  • Affected: >= 2.3.0 < 2.11.0 and >= 1.0 < 2.2.31. Fixed in 2.11.0 and 2.2.31.
  • 2.11.1 is what I would actually install. getcomposer.org dates 2.11.1 to 30 Oct 2026. GitHub 2026-10-30T14:17:07Z. #13184 opened 30 Oct 10:28 UTC: an extension whose reported name is not a valid platform package made every composer update abort with a SecurityException on 2.11.0 / 2.2.31. I downloaded the official 2.11.1 phar. sha256 6f1b891e1d4927d62740276c540fb490e47689e19e102c49e8da849794882f8c matched the download page (retrieved 6 Nov 2026, still latest). It printed Composer version 2.11.1 2026-10-30 14:52:10.
  • Ubuntu 24.04 archive Composer is still 2.7.1-2. packages.ubuntu.com/noble lists that tag (Launchpad uploaded 13 Feb 2024). apt-cache policy composer here: candidate 2.7.1-2, only noble/universe. I will not invent a USN.
  • Sibling, same tags: GHSA-fm2w-xgf2-f9fh. Moderate 5.3. Inline options.http.header was copied into composer.lock. auth.json / COMPOSER_AUTH were never written there. After the bump, composer update --lock. Do not paste tokens.

What I will treat as rumor: a Laravel-core CVE; composer audit as the check (audit reads the lock, not /usr/bin/composer); CVE-2026-84361 as this week’s bug (GHSA-rvx4-ffvw-m9q3, fixed 2.10.3 / 2.2.30 on 27 Aug 2026); 2.11.2 as a tag I can install.

What I would change in a real Laravel/PHP app this week

Three edits. None of them is composer update laravel/framework. None of them is a crafted installed.json.

1. Prove the binary. Then prove it is a phar you can replace.

php -v
composer --version --no-ansi
which -a composer
file "$(command -v composer)"
dpkg-query -W -f='${Package} ${Version}\n' composer
apt-cache policy composer
composer self-update --dry-run

This lab printed:

PHP 8.3.6 (cli) (built: Oct 19 2026)
Composer version 2.7.1 2024-02-09 15:26:28
/usr/bin/composer: PHP script, ASCII text executable
composer 2.7.1-2
Command "self-update" is not defined.

composer diagnose warned Could not find Composer's installed.json. That is the Debian split of the tool, not the project file in this advisory.

2. Stop trusting a version string I have not parsed. 2.2 LTS is a different floor.

function composerVersionFromCli(): string
{
    $out = [];
    exec('composer --version --no-ansi 2>/dev/null', $out);
    if (!preg_match('/Composer version ([0-9]+\.[0-9]+\.[0-9]+)/', $out[0] ?? '', $m)) {
        return '';
    }

    return $m[1];
}

function composerGhsaC968FloorMet(string $version): bool
{
    if ($version === '') {
        return false;
    }
    // 2.2 LTS never reaches 2.11. 2.3–2.10 need 2.11.0+.
    if (str_starts_with($version, '2.2.')) {
        return version_compare($version, '2.2.31', '>=');
    }

    return version_compare($version, '2.11.0', '>=');
}

// Before — Ubuntu archive 2.7.1 fails. 2.10.3 fails too.
// composerGhsaC968FloorMet('2.7.1'); // false

I ran that helper here. 2.7.1 → false. 2.10.3 → false. 2.2.6 → false. 2.11.0 / 2.11.1 / 2.2.31 → true.

$version = composerVersionFromCli();
if (! composerGhsaC968FloorMet($version)) {
    fwrite(STDERR, "Composer {$version} is below the GHSA-c968-33g3-wh7p floor.\n");
    exit(1);
}

That exit 1 is a deploy brake, not the patch. The patch is the phar. I would still install 2.11.1, not stop at 2.11.0, because of the SecurityException on odd extension names.

3. Install the official phar. Verify the hash. Put it ahead of /usr/bin/composer.

curl -fsSL -o composer.phar https://getcomposer.org/download/2.11.1/composer.phar
echo '6f1b891e1d4927d62740276c540fb490e47689e19e102c49e8da849794882f8c  composer.phar' | sha256sum -c
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer
hash -r
which -a composer
composer --version --no-ansi

On Ubuntu 22.04 archive (2.2.6-2ubuntu4), I would pin 2.2.31, not jump the major. If the project used inline options.http.header in global config.json, I would then run composer update --lock. I would not grep a token into chat.

What would bite on a self-hosted VPS

 Version check, distro wrapper, then official phar

Caption: composer --version first. Distro 2.7.1-2 has no self-update. Official 2.11.1 phar, hash-checked. which -a catches /usr/bin/composer still winning. I did not paste a .. package name.

First bite: self-update is not defined. /usr/bin/composer on noble is a 3 KB wrapper that requires /usr/share/php/Composer/autoload.php. It is not a phar. apt upgrade composer stays on 2.7.1-2.

Second bite: PATH. I drop 2.11.1 into /usr/local/bin/composer and forget hash -r. The shell still runs /usr/bin/composer. which -a composer is the check.

Third bite: a shared vendor/ cache. GHSA-c968 needs write access to vendor/composer/installed.json. A Laravel VPS that only runs composer install from its own lock on that box is the case the advisory calls not affected. A CI cache that fork PRs can poison is the case it names. Shared hosting that will not replace /usr/bin/composer has no polyfill in app/.

composer-plugin-api 2.11.0 can fail an old plugin. source-fallback is gone. Those are 2.11 surprises, not the GHSA. I would rather hit them on 2.11.1 than stay on 2.7.1.

Eight days after the tag

 Advisory timeline from 29 Oct 2.11.0 to this 6 Nov lab note

Caption: 2.11.0 and 2.2.31 on 29 Oct. 2.11.1 on 30 Oct for the SecurityException. I am writing 6 Nov. getcomposer.org still lists 2.11.1 as latest. Ubuntu 24.04 still prints 2.7.1. I did not invent a CVE id.

Debian sid already has 2.11.1-1. bookworm and trixie are still listed vulnerable for TEMP-0000000-4A8BFB. Honest target on a Laravel 13 VPS this week: Composer 2.11.1 as a hash-checked phar, CLI and CI the same binary, vendor/ only from a trusted install on that machine. I would not wait for noble to grow a USN. I would not invent 2.11.2 today.

Are you on this in production?

If composer --version already prints 2.11.1 (or 2.11.0 on the security floor, or 2.2.31 on LTS) on the same binary CI and the VPS use, you are on this. If Ubuntu 24.04 dpkg-query still shows 2.7.1-2 — or which composer is /usr/bin/composer after you thought you dropped a phar in /usr/local/bin — you are not. If the laptop is 2.11.1 and the deploy image still apt install composer, you are not. Shared hosting that will not replace the distro wrapper is the last camp. I did not flip a client cluster tonight. I want to know which of those you are in.

Did you hit the same wall?

I would get stuck on Command "self-update" is not defined on Ubuntu archive Composer 2.7.1, or on /usr/bin/composer still winning after a 2.11.1 phar. Did you hit the same thing, or a different one — a SecurityException after stopping at 2.11.0, a CI cache of vendor/, a host that will not let you replace the binary? Tell me in the comments. I read them.

If the blocker is the VPS itself (distro Composer, the wrong binary on PATH, a panel that will not let you install a phar), I do this class of bump for a living: Hire for deploy.

References

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.