Composer Security Hardening

Published On12 Feb 2021

Composer security Hardening Composer is a dependency manager for PHP, and is the de facto one. It is a great software developed by brilliant people, and Composer has many security precautions in place to prevent several threat models such as supply chain attacks, HTTPS downgrade attacks, and offers features to further tighten security.

Composer 2, released October 2020, has even more security features in place to further limit the attack surface even at a worst-case scenario. Recent news such as an attacker injecting malicious code to event-stream dependencies, and the more recent news of a security researcher executing arbitrary code at several companies including Microsoft and Apple are making people aware on how important dependency managers have to be, and Composer is standing strong on that front.

Composer gets many things right when it comes to a dependency manager. Composer is a relatively new dependency manager compared to NPM and PIP, that gave Composer some inspiration on both good lessons and things to avoid when building a dependency manager for one of the most popular and widely used programming languages in the world.

Composer Packages are always vendor-namespaced

All Composer packages must be vendor-namespaced. This means Composer only allows packages such as "symfony/finder" and "nette/finder", but never stand-alone package names such as "finder". This sets Composer far ahead of the competition such as NPM and PIP.

"Dependency confusion" attacks, such as the ones that affected Microsoft and Apple would not have happened with Composer because all packages must be vendor-namespaced, and Packagist (the online repository for packages) never allows a different account to claim a vendor name once a package is published under a vendor name.

symfony/ vendor name, for example, is reserved for the Symfony project, and nobody else can publish a Composer package with name symfony/* to Packagist.

Packagist does not allow cross-vendor packages

Security Precautions built-in to Composer/Packagist

Composer and Packagist provide many security precautions built-in. This includes the essentials such as requiring HTTPS, 2-factor authentication in Packagist, and entirety of Composer source code being open-source.

Lock files (composer.lock) store absolute URLs to all packages used in a dependency tree, and guaranteed reproducible vendor directories.

Composer Packages are hosted on a VCS

Unlike NPM that hosts the actual code of versions, Packagist never stores the actual code. It merely stores the target URL and meta information about packages. Almost entirety of public composer packages submitted to Packagist are hosted on GitHub, GitLab, or a similar version controlling system.

This provides transparency, because the actual package contents can be reproduced and verified anytime.

Composer self-updates are cryptographically verified

Every time Composer is installed, or updated through composer self-update command, the downloaded executable file is cryptographically verified for its integrity. If the integrity is not proved to be untampered with, Composer downright refuses to accept the downloaded Phar file.

Phive provides signature-verified Phar installations and updates for tools such as Composer, PHPUnit, Psalm, Phan, and several more.

Root-user warnings

Composer 1 had a feature that it detects if Composer is run as the root user of a Linux system, and emits a warning that it's a bad idea.

In Composer 2, it is leveled up to a prompt that the root user is warned, and must continue manually before the rest of the Composer command is executed.

Composer warning when used by root user

Composer Repositories are Canonical by default

One of the most important changes in Composer 2 is that it considers a repository to be canonical; When using multiple repositories, if a package is found at a repository, Composer will not look for newer versions in the rest of the declared repositories.

This mitigates the threat when an attacker publishes a higher version of a given package and trick Composer to download the package from a repository the attacker potentially controls.

Additional Security Hardening

Using code from a third party implies trust and well-intent. It not only applies to Composer, but also pretty much every software being used on a system. Composer provides features such as vendor namespaces that provides some level of a trust chain, a secure channel (such as HTTPS and commit-hashes), and secure verified updates to Composer itself.

There are some additional security hardening that tighten the attack surface and provide safe-guards to a Composer project.

Block packages with known vulnerabilities

roave/security-advisories is a composer meta-package that has no functional code by itself, but has a composer.json file that prevents packages and package versions with known vulnerabilities from being installed.

composer require --dev roave/security-advisories:dev-latest

Running above, or adding roave/security-advisories to require-dev section in the composer.json file in a project does the trick. Once installed, any packages with known vulnerabilities will be considered a conflict package version, and Composer will refuse to install the vulnerable package or the vulnerable version of one.

A Drupal-specific advisories list (that can be used in addition to roave/security-advisories) and a WordPress list are also available.

Limit Repository Scopes

Composer supports configuring additional repositories to source packages. For example, Drupal, WordPress, Yii, and Magento are some example open-source projects that provide their own repository of packages. Composer would look for a given package name in all of these repositories, and then finally at Packagist.org looking for a package with a given name.

In Composer 2, canonical repository feature is introduced, that by default, Composer stops looking for package versions in other repositories if it finds the package in a repository. Packagist.com, from the very creators of Composer, provides a feature similar to canonical repositories for Composer version 1.x by acting as a mirror to packagist.org.

When using a repository for an open-source project, or a private repository such as from GitLab, it is possible to optionally limit the packages the repositories may host.

Following is a composer.json snippet that adds Drupal and WordPress repositories.

{
    "repositories":[
        {
            "type":"composer",
            "url":"https://packages.drupal.org/8",
            "only": ["drupal/*"]
        },
        {
            "type":"composer",
            "url":"https://wpackagist.org",
            "only": ["wpackagist-plugin/*", "wpackagist-theme/*"]
        }
    ],
}

The only clause tells Composer to only look for drupal/* packages in Drupal repository, and wpackagist* packages in the WPackagist repository.

With the only clause, Composer will not attempt to look for other packages (such as symfony/finder) in those repositories. Even if an attacker manages to host a package in those repositories, Composer will not look for symfony/finder in them.

Further, it is possible to exclude certain packages from a repository. The example below shows a repository in https://example.com, that Composer can look for all packages except example/outdated-package.

{
    "repositories":[
        {
            "type":"composer",
            "url":"https://example.com",
            "exclude": ["example/outdated-package"]
        }
    ],
}

Composer Scripts

By default, Composer runs scripts declared in the package being installed. This essentially means that when a package is installed, it is given a chance to immediately execute arbitrary code.

Laravel, for example has a scripts section in its composer.json file to complete the installation of Laravel to a usable state.

It is a good idea to inspect the packages being installed with a dry-run, or with scripts disabled unless it's absolutely necessary.

Dry-run composer require

composer require laravel/laravel --dry-run

A dry-run will not make any changes, but show the package and dependency list being installed. --dry-run option is supported require, install, update, and remove commands.

Run Composer with scripts disabled Composer provides a --no-scripts option in require, install, update, remove, create-project, and dump-autoload commands that prevents any scripts declared in the package's composer.json file from being run.

Implicit Autoloads

It is worth noting that any installed, despite not being used, it not necessarily safe. Development tools such as PHPUnit might not be required for a production web site, and it is easy to forget to not use those development packages (require-dev packages) in production.

Composer's autoloader is feature-rich, and it supports not only PSR-0 and PSR-4, but also classmap and files options. symfony/polyfill for example uses the files option to always include files that it polyfills. If the package makes it to the autoloader, all files declared under autoload.files section of all packages' composer.json files are automatically included, whether they are used or not.

When deploying to production, make sure to not include require-dev packages with the --no-dev option.

composer install --no-dev This command installs a Composer project using an existing composer.lock file, but does not fetch or include autoload entries from require-dev packages.

composer dump-autoload --no-dev It is also possible to generate the autoloader without including the require-dev packages' autoload entries. It will not remove the packages, but simply exclude them from the autoloader.


Composer is a powerful tool for PHP dependency management, and it tries to provide the essentials to do its work securely. However, the chain of trust always has to root somewhere by the nature of secure software. Using a Composer package of a third party automatically implies trust on that vendor.

The steps above will provide protection against malicious third parties that might try to tamper the code at its manifest lookup or transit.

Recent Articles on PHP.Watch

All ArticlesFeed 
How to fix `mysql_native_password` not loaded errors on MySQL 8.4

How to fix mysql_native_password not loaded errors on MySQL 8.4

How to fix the SQLSTATE[HY000] [1524] Plugin 'mysql_native_password' is not loaded errors caused in MySQL 8.4 no longer enabling the mysql_native_password plugin by default.
How to fix PHP Curl HTTPS Certificate Authority issues on Windows

How to fix PHP Curl HTTPS Certificate Authority issues on Windows

On Windows, HTTPS requests made with the Curl extension can fail because Curl has no root certificate list to validate the server certificates. This article discusses the secure and effective solutions, and highlights bad advice that can leave PHP applications insecure.
AEGIS Encryption with PHP Sodium Extension

AEGIS Encryption with PHP Sodium Extension

The Sodium extension in PHP 8.4 now supports AEGIS-128L and AEGIS256 Authenticated Encryption ciphers. They are significantly faster than AES-GCM and CHACHA20-POLY1305. This article benchmarks them and explains how to securely encrypt and decrypt data using AEGIS-128L and AEGIS256 on PHP.
Subscribe to PHP.Watch newsletter for monthly updates

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.

Support PHP.Watch — If you find the articles, version information, Codex, and other PHP.Watch contributions useful, consider supporting through GitHub Sponsors. Your sponsorship helps dedicate more time to creating valuable content and improving the PHP community. Together, we can keep the momentum going — thank you for your support!

Thanks to the highest tier sponsor: @TomasVotruba for your generous support to keep PHP.Watch moving 💜