amarok, arrow, audacity, cargo, clementine, composer, dask, deluge, dosbox-x, dragonfly. All CWE-407.
4.1 KiB
Composer — CWE-407 Disclosure Brief
2026-04-13 · Patch available — awaiting upstream merge
Finding
Two O(n²) defects in Composer's dependency resolution: one in recursive package filtering and one in recursive dependent-package discovery. Both patched. Patches ready for upstream review.
The Defects
composer-0001 (PATCHED — MEDIUM): src/Composer/Repository/RepositoryUtils.php:34
// In filterRequiredPackages() — fires during dependency resolution:
if (!in_array($candidate, $bucket, true)) {
$bucket[] = $candidate;
$bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket);
}
in_array($candidate, $bucket, true) is O(|bucket|) per candidate. As $bucket grows through recursive calls, each membership test scans the full accumulated list. For a project with P packages and D dependency depth, total cost reaches O(P×D×B) where B = average bucket size.
composer-0002 (PATCHED — MEDIUM): src/Composer/Repository/InstalledRepository.php:86
// In getDependents() — fires during `composer why` / `composer depends`:
if (in_array($link->getTarget(), $packagesInTree)) {
$results[] = [$package, $link, false];
continue;
}
$packagesInTree[] = $link->getTarget();
$dependents = $recurse ? $this->getDependents($link->getTarget(), ..., $packagesInTree) : [];
in_array() on $packagesInTree is O(N) per link. Called recursively with a growing tree. For a project with L links across R packages, total cost reaches O(L×N) where N = accumulated tree size.
Complexity Proof
composer-0001: At P=200 packages, B growing to 200:
- Defective: 200 × 100 (avg bucket) = 20,000 comparisons
- Fixed: 200 × O(1) = 200 lookups (SplObjectStorage)
- ~100× op reduction per dependency resolution.
composer-0002: At R=500 packages, L=2000 links:
- Defective: 2,000 × 250 (avg tree) = 500,000 comparisons
- Fixed: 2,000 × O(1) = 2,000 lookups (isset on associative array)
- ~250× op reduction per
composer whyinvocation.
Impact
Composer is the package manager for PHP — used by virtually every PHP project. filterRequiredPackages runs during dependency resolution, affecting composer install, composer update, and composer require. getDependents powers composer why and composer depends, used to trace why a package appears in the dependency tree.
Large PHP projects (Symfony, Laravel, Drupal applications) with hundreds of packages hit both paths. composer-0001 compounds through recursive resolution; composer-0002 compounds through recursive dependent traversal.
The Fix
composer-0001: Replace in_array() with SplObjectStorage (PHP's hash-backed object set):
// Before
if (!in_array($candidate, $bucket, true)) { ... }
// After
// CWE-407 fix: SplObjectStorage for O(1) membership instead of O(|bucket|) in_array().
$bucketSet = new \SplObjectStorage();
if (!$bucketSet->contains($candidate)) {
$bucketSet->attach($candidate);
$bucket[] = $candidate;
}
composer-0002: Replace in_array() with isset() on a parallel associative array:
// Before
if (in_array($link->getTarget(), $packagesInTree)) { ... }
// After
// CWE-407 fix: isset() on hash set for O(1) instead of O(N) in_array().
if (isset($packagesInTreeSet[$link->getTarget()])) { ... }
$packagesInTreeSet[$link->getTarget()] = true;
Patch
Fix available: defects/composer/patch/composer-0001-filter-splatobjectstorage.patch and defects/composer/patch/composer-0002-dependents-isset.patch
Two-file patch across RepositoryUtils.php and InstalledRepository.php.
composer-0001: ~100× speedup at P=200. composer-0002: ~250× speedup at R=500, L=2000.
What We Ask
A patch is ready for review.
- Confirm receipt and assign an issue reference (composer/composer).
- Assess severity — both defects sit on dependency resolution paths used by every
composer installandcomposer whyinvocation. - Coordinate a disclosure date — we are targeting 90 days from first contact.
- We will credit the Composer team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.