The Allow-Plugins Security Prompt Is Composer's Best Feature and Its Most Annoying One

Somewhere in the middle of upgrading this site from Drupal 10 to 11, composer require drupal/core-recommended:^11 stopped and refused to install anything at all, not because of a version conflict this time, but because one of the packages it needed to pull in, symfony/runtime, ships a Composer plugin, and Composer plugins don't get to run just because they're in the dependency tree.

What a Composer plugin is

Most packages Composer installs are just code that ends up in vendor/, inert until your application chooses to call it. A Composer plugin is different: it's a package that hooks into Composer itself and runs during the install/update process, before your application ever gets a chance to run anything. That's a legitimate and sometimes necessary thing for a package to do. cweagans/composer-patches, already allowed in this project, needs it to apply patches during install. php-http/discovery, also already allowed, needs it to pick the right HTTP client implementation automatically. It's also, structurally, the exact mechanism a malicious package would use for a supply-chain attack: install-time code execution, with whatever permissions the user running composer install already has, no separate confirmation step beyond "this package is in your lock file."

Composer's answer to that is the allow-plugins block in composer.json. Every package that wants to run plugin code has to be explicitly listed there as true before Composer will let it execute; anything not listed gets its plugin functionality silently skipped, with a warning, rather than blocking the whole install outright by default in recent Composer versions. Here, though, it blocked the install outright mid-upgrade. Before this upgrade, this project's list read:

  • drupal/core-composer-scaffold
  • drupal/core-project-message
  • dealerdirect/phpcodesniffer-composer-installer
  • cweagans/composer-patches
  • php-http/discovery

Why symfony/runtime needed adding

Drupal 11's own scaffolding changed how the front controller bootstraps: index.php now goes through Symfony Runtime rather than Drupal's own older bootstrap sequence, part of the standard scaffold regeneration that comes with the upgrade, not custom code. symfony/runtime being a transitive dependency of that meant its Composer plugin needed to run during install for the package to set itself up correctly, and Composer stopped and asked, in its usual blunt way, whether that was wanted.

The honest answer required stopping to think about it rather than reflexively saying yes to make the install continue: is this specific package trustworthy enough to run arbitrary code on my machine during every future composer install? For symfony/runtime, yes: it's an official package from the Symfony project, which Drupal 11 itself now depends on directly for its own bootstrap process, maintained by the same organization responsible for a framework this entire CMS is partly built on. Adding it to allow-plugins as true was the correct call, but it was a call, not a formality. The same prompt for a plugin from an unfamiliar maintainer, freshly published, with no track record, deserves a very different answer, and Composer has no way of knowing the difference. That judgment call is entirely on whoever's running the install.

Why it's the right kind of annoying

The frustrating part, in the moment, is that this prompt interrupts a routine core upgrade with a security decision that has nothing to do with what you set out to accomplish: you wanted a version bump, and instead you're evaluating the trustworthiness of a transitive dependency's maintainer. That friction is also exactly the point. The alternative, Composer plugins running automatically the moment they show up anywhere in a dependency tree however many layers deep, means a compromised or malicious package doesn't need to be something you directly required; it just needs to be something you required eventually pulled in, and its install-time code runs with zero further scrutiny. allow-plugins forces that scrutiny back to the surface, one package at a time, at the exact moment a new plugin first tries to run, which in practice means it interrupts you rarely, but interrupts you specifically when there's something new worth looking at.

The list staying short (five entries before this upgrade, six after, across what's now a fairly large Drupal dependency tree) is itself evidence the mechanism is doing its job. Most of what's in vendor/ here has no reason to ever touch the install process directly, and doesn't ask to. The packages that do ask are worth looking at individually, every time, rather than building a habit of answering yes without reading who's asking.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please share this article on your favorite website or platform.