Recently there has been an increase of cases in which Composer installs a fork of a package instead of the package the user expects. Most frequently these are forks of packages using a “replace” statement in their composer.json. These forks are usually meant for private use only but are still published on Packagist.
Developers: composer update. Automated systems: composer install.
First of all, this behavior is not a security issue in Composer. While the behavior is unintuitive, it will not result in malicious code being used if you use Composer correctly. Most importantly you should only ever run composer update
yourself manually on your development machine. You should read its output, verify it installed and updated packages as expected (use --dry-run
to check what would happen without installing anything). You should then commit the generated composer.lock
file to your version control system. Continous integration, deployment tools and other automated systems should always run composer install
. A composer install
run will always use the exact packages that were installed by composer update
which was used to generate the lock file – no surprises possible.
Replace identifies which original versions a fork is API compliant with
Secondly it is not wrong to publish forks on Packagist. While we discourage the publication of forks intended solely for private use, you are encouraged to publish forks of open source projects which you intend to maintain for others on Packagist. You may use the replace key to specify which versions of the original package your fork is an API compliant replacement for. You should avoid using too generic version constraints (e.g. *
or >=1.0.0
) with replacements as with all other dependencies.
Dependency Resolution with Replace, Conflict & Provide
After downloading the list of available packages Composer tries to find a set of packages that satisfies all version constraints specified by all dependencies and their dependencies recursively. Package dependencies are specified as a tuple of name and version constraint. Only such packages will be considered that match the name and version constraint, or replace or provide the specified name with a version constraint matching the given version constraint, and do not match a name and version constraint of a conflict specified in any other package about to be installed. This means that a conflict resulting in a package being excluded, alternatives which replace or provide that name will be considered.
Debugging unexpected fork installations
If your dependencies lead to a conflict with a package Composer may decide to install a fork instead which does not have the same conflict. If you notice that an unexpected fork is installed when running composer update
you can debug the dependency problem that lead to the fork installation. Use the conflict key in your composer.json to blacklist the fork. Its structure matches that of the require
key. If you run composer update
again you will now receive an error message explaining the problem, as the fork is no longer available as an alternative solution to the dependency issue.
{
"require": {
"my-favorite/framework": "1.0.0",
"cool/library-which-conflicts": "0.0.1"
}
"conflict": {
"fork-of/my-favorite-framework": "*"
}
}
Planned changes to improve usability
We understand that Composer’s behaviour regarding forks and package replacements is unintuitive. So I’ve proposed a number of changes yet to be implemented to the handling of replace & provide on our issue tracker at https://github.com/composer/composer/issues/2690.
TL;DR
Replace is not a bug. Don’t run composer update in automated systems. Forks are allowed on Packagist. Don’t be an idiot when publishing a fork. Got an unexpected fork on update? Your dependencies conflict with the original package. Use conflict (syntax like require) in your composer.json to blacklist the fork and see an explanation of the dependency issue.
Need Composer Consulting? Contact me at composer-consulting@forumatic.com.