Eine weiße Stempeluhr mit großem schwarzem Zifferblatt steht im Vordergrund auf einer Ablage, unter dem Zifferblatt sitzt ein Schlüsselschloss. Dahinter hängen unscharf Reihen von Stempelkarten in ihren Fächern an der Wand. Eine bildliche Metapher für eine verschlossene Uhr, die niemand verstellen kann, der an ihr stempelt.

Im Juli habe ich Composer und Packagist mit fünf anderen Ökosystemen verglichen und dabei mehrere Lücken gefunden. Eine davon war schnell benannt: pnpm, Yarn, Bun, npm, uv, pip und Poetry halten frisch veröffentlichte Releases auf Wunsch zurück, pnpm 11 sogar standardmäßig. Composer tat das nicht. Die Roadmap vom 27. Mai nannte minimum-release-age als erste Policy, die auf das Dependency Policy Framework von Composer 2.10 folgen sollte, und die unveränderlichen Versionen auf Packagist.org, seit dem 7. Juli in Betrieb, räumten die letzte Voraussetzung aus dem Weg.

Composer 2.11, am 29. Oktober 2026 erschienen, liefert diese Policy unter anderem Namen aus: cooldown. Knapp vier Monate von der Voraussetzung bis zum Release sind schnell für eine Änderung, die den Resolver, die Lock-Datei und jeden Befehl berührt, der eine Version auswählt.

Direkt darunter steht im Changelog eine zweite Änderung. Eigene Dependency Policies, Teil des Policy-Modells, das Andrew Nesbitt mit uBlock Origin verglichen hat, können jetzt auch composer install stoppen, nicht nur composer update. Für Organisationen, die eigene Listen pflegen, reicht die Policy damit bis zu dem Schritt, der in die Produktion ausliefert.

Warten verschafft anderen Zeit zum Hinsehen

Jeder Cooldown beruht auf einer Beobachtung: Die meisten bösartigen Releases werden innerhalb von Stunden oder Tagen entdeckt und entfernt. Eine Version, die eine Woche öffentlich überstanden hat, hat das Zeitfenster überstanden, in dem die meisten Angriffe auffliegen. Andrew Nesbitts Bestandsaufnahme der Cooldowns verschiedener Paketmanager zitiert eine Analyse von William Woodruff, nach der acht der zehn von ihm untersuchten Supply Chain-Angriffe ein Zeitfenster von weniger als einer Woche hatten.

Der Cooldown selbst entdeckt nichts. Er verschafft denen Zeit, die etwas entdecken: Aikido, Socket, Sicherheitsforscherinnen und -forschern sowie Maintainerinnen und Maintainern, denen ein Release auffällt, das sie nie veröffentlicht haben. Nils Adermanns Release-Post setzt die Zahlen niedriger an als Woodruff: Schon eine Stunde gibt automatisierten Scannern Zeit zu reagieren, eine manuelle Prüfung kann einige Stunden bis zu einem Tag brauchen. Nichts davon garantiert ein Cooldown. Er erhöht nur die Wahrscheinlichkeit.

In Composer 2.11 sitzt der Cooldown unter config.policy, neben advisories, malware und abandoned:

{
    "config": {
        "policy": {
            "cooldown": {
                "period": "7 days"
            }
        }
    }
}

period erwartet eine Zahl und eine Einheit (second, minute, hour, day oder week, im Singular oder Plural, kleingeschrieben) oder eine ganze Zahl von Sekunden. Relative Angaben wie "tomorrow" weist Composer zurück. Der Default ist null, und ohne Zeitraum tut die Policy nichts. block steht standardmäßig auf true. ignore verwendet dasselbe Format wie die anderen Policies, sodass jede Ausnahme eine Begründung und einen Versions-Constraint tragen kann. Setzt du audit auf report oder fail, listet composer audit auch installierte Versionen auf, die noch im Cooldown stecken; der Default ist ignore.

Für einen einzelnen Aufruf haben ein Flag und zwei Umgebungsvariablen Vorrang. --no-cooldown bei update, require, remove und create-project hebt nur den Cooldown auf und lässt, anders als --no-blocking, das Blockieren durch Advisories, Malware und eigene Listen bestehen. COMPOSER_POLICY_COOLDOWN_PERIOD überschreibt den Zeitraum, 0 schaltet ihn ab, und COMPOSER_POLICY_COOLDOWN_BLOCK überschreibt block.

Ein Detail der Umgebungsvariable verdient die Aufmerksamkeit von Plattform-Teams. Ein Wert ungleich null in COMPOSER_POLICY_COOLDOWN_PERIOD schaltet den Cooldown auch dann ein, wenn die composer.json eines Projekts cooldown auf false setzt. Erst "policy": false schaltet ihn wieder ab. Eine CI-Plattform kann also für jedes Projekt einen Cooldown einschalten, ohne eine einzige composer.json anzufassen. Ein Projekt kann sich trotzdem entziehen, mit "block": false, breiten ignore-Regeln oder --no-cooldown.

Im Juli hatte ich vermutet, dass diese Policy über denselben Mechanismus kommen würde wie die Malware-Filterliste. Damit lag ich halb richtig. Der Cooldown teilt das Konfigurationsgerüst der Listen, block, audit und ignore, bekommt aber einen eigenen Pool-Filter, weil er das Alter beurteilt und nichts abrufen muss. Der Name minimum-release-age bleibt auf der Liste der reservierten Policy-Namen.

Die Implementierung begann als Pull Request #12692 von Viktor Djupsjöbacka und wurde am 1. Oktober 2026 gemergt, mit Jordi Boggiano als Co-Autor. Jordi überarbeitete danach die Fehlermeldungen sowie outdated und show --latest und ergänzte am Tag des Release Candidates das Flag --no-cooldown.

Die Uhr zählt mehr als die Dauer

Ein Cooldown kann nur so vertrauenswürdig sein wie der Zeitstempel, von dem aus er zählt. Composer-Pakete haben seit langem ein Feld time. Gesetzt wird es von der Autorin oder dem Autor des Pakets, entweder ausdrücklich in der composer.json oder implizit über das Datum des getaggten Commits, und Git akzeptiert jedes Commit-Datum, das du ihm gibst. Wer ein Repository übernommen hat, kann ein bösartiges Release um einen Monat zurückdatieren, und ein Cooldown, der time vertraut, winkt es durch.

Composer 2.11 zählt stattdessen ab published-time: dem Zeitpunkt, zu dem Packagist.org die Version erstmals bereitgestellt hat, ein Feld, das Packagist.org eigens dafür seinen Metadaten hinzugefügt hat. Darauf haben Paketautorinnen und -autoren keinen Einfluss. Composer besteht auch darauf. Ist ein Cooldown konfiguriert, stoppt eine Version aus einem Composer-Repository ohne published-time den Befehl, und die Fehlermeldung nennt das Paket. Für ein Repository, das dieses Feld nicht liefern kann, etwa eines, das mit Satis erzeugt wurde, kannst du mit "require-published-time": false darauf verzichten. Composer greift dann auf time zurück, und seine Dokumentation sagt deutlich, dass der Schutz damit schwächer ist. Pakete aus VCS-, Path- und Artifact-Repositories greifen immer auf time zurück, und Composer gibt eine Warnung aus, wenn es Versionen nach einem Datum beurteilt hat, für das niemand bürgt.

Wie weit die beiden Uhren auseinanderlaufen können, zeigt Composers eigene composer.lock zum Release-Tag 2.11.0. Composer 2.11 schreibt published-time neben time in die Lock-Datei, und bei 21 der 36 Pakete liegen die beiden weniger als eine Stunde auseinander. Die übrigen 15 stammen alle von Symfony. Für composer/ca-bundle 1.6.0 steht in time 20:14 Uhr UTC am 20. Oktober, in published-time 20:17 Uhr. Dann ist da symfony/filesystem 5.4.54: time nennt den 30. August 2026, published-time den 15. Oktober 2026. Bei symfony/polyfill-php73 1.37.0 beträgt der Abstand mehr als anderthalb Jahre.

Dahinter steckt nichts Böses. Symfony taggt bei jedem Release alle seine Split-Repositories, auch die, deren Code sich nicht geändert hat, sodass der Tag auf einen alten Commit zeigt. Es zeigt aber, was time misst: das Alter eines Commits, nicht das Alter eines Releases. Gemessen an time hätte Version 5.4.54 von symfony/filesystem einen siebentägigen Cooldown schon 39 Tage vor dem Zeitpunkt hinter sich gehabt, ab dem sie überhaupt installiert werden konnte. Nesbitt beschreibt denselben Fehler beim Cooldown von Dependabot für GitHub Actions, der ab dem Commit-Datum des Tags zählt.

Eine vertrauenswürdige Uhr allein reicht allerdings nicht. published-time gehört zu einer Version, und eine Version ordnet einer Nummer eine Git-Referenz zu. Könnte sich diese Referenz noch verschieben, behielte eine Version ihr altes Datum, während sich der Code darunter änderte. Das erklärt, warum das Feature auf Packagist.org warten musste. In Issue #12633 beschrieb Nils Adermann es als blockiert durch Änderungen an den Release-Metadaten auf Packagist.org, und sein Release-Post nennt published-time als den Baustein, der die Policy aus 2.10 herausgehalten hat. Seit dem 7. Juli kann sich die Referenz einer stabilen Version auf Packagist.org nicht mehr ändern. Erst unveränderliche Versionen und published-time zusammen machen den Cooldown belastbar. Private Packagist, das auch private und gespiegelte Pakete ausliefert, deren Tags sich noch verschieben können, geht noch einen Schritt weiter: Laut Release-Post beginnt der Cooldown neu, wenn sich der Code hinter einer Version ändert, etwa nach einem per Force-Push verschobenen Tag.

Der Cooldown schützt das Update, nicht die Installation

Der Cooldown greift, wenn Composer Versionen auswählt: bei composer update, require und remove. composer require und init überspringen zurückgehaltene Versionen auch, wenn sie einen Constraint schreiben, und create-project tut dasselbe, wenn es die Projektversion auswählt; composer require schreibt also kein ^2.0 in deine composer.json, wenn 2.0.0 gestern erschienen ist. Solange eine Lock-Datei existiert, ignoriert composer install den Cooldown. Es reproduziert eine Entscheidung, die die Lock-Datei bereits festhält.

Manche Versionen warten nie:

  • Dev-Versionen wie dev-main, weil sie ohnehin einem Branch folgen, der sich bewegt
  • In der Lock-Datei festgeschriebene Pakete, die ein partielles Update wie composer update vendor/package unangetastet lässt
  • Plattformpakete: PHP, Extensions und Bibliotheken
  • Versionen ganz ohne Datum, etwa ein Eintrag in einem Inline-package-Repository ohne time; Composer kann ihr Alter nicht bestimmen und lässt sie durch, statt sie für immer zu blockieren

Die zweite Ausnahme hat eine Kehrseite, die manche Teams überraschen wird. Ein vollständiges composer update bewertet jedes Paket neu. Enthält deine Lock-Datei eine Version, die noch im Cooldown steckt, vielleicht weil jemand sie mit --no-cooldown hereingeholt hat, ersetzt das nächste vollständige Update sie durch ein älteres Release, oder es schlägt fehl, wenn kein älteres Release deine Constraints erfüllt. Composer löst strikt auf: Passt nichts, das alt genug ist, bricht das Update ab. Es weicht nicht stillschweigend auf eine Version aus, die zu neu ist.

composer outdated und composer show --latest erzählen jetzt dieselbe Geschichte wie composer update. Sie melden die neueste Version, die der Cooldown zulässt, und zeigen daneben ein neueres Release, das noch wartet, mit der verbleibenden Zeit, etwa „6d 4h“. Gibt es kein installierbares Update, tritt das wartende Release an die Stelle der neuesten Version, markiert mit c, und die JSON-Ausgabe meldet "latest-status": "cooldown". Wartende Releases zählen für --strict nicht als veraltet, und --cooldown-period 0 zeigt die neuesten Releases ungeachtet des Cooldowns. Als der Cooldown zum ersten Mal im Hauptzweig landete, empfahl outdated noch Versionen, die update nicht installieren wollte; Pull Request #13102 hat das behoben, bevor ein Release erschien.

Zwei Änderungen erreichen deine Lock-Datei. Composer 2.11 hält published-time für jedes Paket fest, das von Packagist.org kommt. Und Pull Request #13149 macht config.policy zum Teil des Content-Hashs der Lock-Datei, sodass eine Änderung am Zeitraum des Cooldowns oder an seinen Ausnahmen die Lock-Datei als veraltet markiert. Hat dein Projekt bereits eine policy-Konfiguration, meldet Composer die Lock-Datei nach dem Upgrade als veraltet, bis du sie aktualisierst, zum Beispiel mit composer update --lock.

Composers eigene Ausnahmen kamen nach wenigen Tagen

Am 19. Oktober fügte Jordi Boggiano Composers eigener composer.json einen siebentägigen Cooldown hinzu. Zwei Tage später brauchte Composer ein Release, das weniger als einen Tag alt war. Pull Request #13146 sorgt dafür, dass Composer TLS-Verbindungen ablehnt, die die kompromittierten Zwischenzertifikate von eMudhra vorlegen, selbst wenn der CA-Store des Systems deren Wurzelzertifikaten noch vertraut. Die Prüfung stützt sich auf eine Denylist in composer/ca-bundle, und derselbe Pull Request aktualisierte dieses Paket auf 1.6.0, das am Abend zuvor auf Packagist.org erschienen war.

Am selben Abend hielt Pull Request #13149 die Ausnahme fest. Composers composer.json zum Release-Tag 2.11.0 enthält:

"policy": {
    "cooldown": {
        "period": "7days",
        "ignore": {
            "composer/ca-bundle": {"constraint": "1.6.0", "reason": "Required and approved"}
        }
    }
}

Die Ausnahme nennt eine Version, nicht das Paket. Version 1.6.1 wird ihre sieben Tage warten wie jedes andere Release. Die Begründung steht in der Historie des Repositorys, wo sie im Review hinterfragt werden kann. Dieses Modell bietet Composer 2.11 für dringende Korrekturen an: eine ausdrückliche Entscheidung, auf eine Version beschränkt und schriftlich festgehalten.

Das Release selbst zeigt den anderen Weg. Composers Lock-Datei zum Release-Tag 2.11.0 enthält composer/semver 3.6.0. Packagist.org hat diese Version am 29. Oktober um 09:14 Uhr UTC bereitgestellt. Sechs Minuten später kam sie zusammen mit einer Performance-Änderung am Resolver, Pull Request #13174, in die Lock-Datei, und um 09:23 Uhr wurde Composer 2.11.0 getaggt. Das Release wird mit einer Abhängigkeit ausgeliefert, die neun Minuten alt war. Version 3.5.2 war sechs Tage früher in die Lock-Datei gekommen, weniger als einen Tag nach ihrer Veröffentlichung. Keine der beiden Versionen steht unter ignore. Ob mit --no-cooldown, über die Umgebungsvariable oder mit einem Composer, der die Policy noch nicht kennt: In der Konfiguration ist davon nichts festgehalten.

Für Nachlässigkeit halte ich das nicht. composer/semver stammt vom selben Team, und den eigenen Paketen zu vertrauen ist genau das, wofür es die Option ignore gibt. Es zeigt aber den Preis der einmaligen Umgehung: Sie hinterlässt keine Spur. Eine ignore-Regel für die eigenen Pakete des Teams hätte dieselbe Entscheidung festgehalten.

Andere Werkzeuge automatisieren die Ausnahme für Sicherheitskorrekturen. Laut Nesbitt hebt Hex seinen Cooldown für ein Paket auf, dessen Version in der Lock-Datei zurückgezogen wurde oder ein Advisory hat, und Dependabot überspringt seinen Cooldown bei Sicherheitsupdates. Frühere Fassungen von Pull Request #12692 schlugen für Composer etwas Ähnliches vor: Ein Release, das nach einem passenden Advisory veröffentlicht wurde und von diesem nicht betroffen ist, hätte den Cooldown übersprungen. Composer 2.11.0 enthält keine solche Regel, und der Release-Post sagt, warum: „Composer cannot safely let security fixes skip the cooldown policy automatically.“ Wer ein bösartiges Release veröffentlichen kann, kann meist auch ein Advisory veröffentlichen, und „any rule that exempts releases based on advisory data would give attackers a way around the cooldown period.“

Ich würde einen zweiten Grund ergänzen, und der geht auf den April zurück. GitHub änderte die betroffenen Versionen eines Advisorys, das ich für PHPUnit veröffentlicht hatte, GHSA-qrr6-mg7r-m243, ohne mich zu fragen. Composer 2.9 blockierte daraufhin sämtliche Versionen von PHPUnit 11, obwohl keine von ihnen die Schwachstelle je enthalten hatte. Ich habe die Episode damals beschrieben. Advisory-Daten können falsch sein, ohne dass jemand sie angreift, und wenn sie es sind, lässt eine automatische Ausnahme die falsche Version an der Warteschlange vorbei.

Auch das manuelle Modell hat seinen Preis. Es kostet Zeit, gerade wenn Zeit am meisten schmerzt, und es hängt davon ab, dass jemand bemerkt, dass die benötigte Korrektur im Wartezimmer sitzt. composer outdated zeigt das wenigstens an, samt verbleibender Zeit.

Pull Request #13146 zeigt einen zweiten Zielkonflikt. Composers CI setzt COMPOSER_POLICY_COOLDOWN_PERIOD jetzt für die Jobs auf 0, die Abhängigkeiten aktualisieren, darunter die, die mit den höchsten und den niedrigsten Versionen der Abhängigkeiten testen. Composers Dokumentation für Bibliotheksautorinnen und -autoren empfiehlt gerade für solche Jobs einen Cooldown. Beide Positionen sind gut begründet: Ein Job, der dazu da ist, Brüche durch neue Releases früh zu erkennen, will diese Releases früh. Ein solcher Job verdient dann die Behandlung, die dieselbe Dokumentation vorschreibt: keine Secrets, keine Schreibrechte und kein composer update in der Nähe eines Build- oder Release-Schritts.

Starke Defaults enden beim Cooldown

Composer 2.9 blockiert standardmäßig Versionen mit bekannten Security-Advisories. Composer 2.10 blockiert standardmäßig Malware, sogar bei composer install. Composer 2.11 wartet nur, wenn du es darum bittest. Ohne period tut der Cooldown nichts.

Im Juli habe ich geschrieben, dass Opt-in-Schutz nur die erreicht, die ihn kennen und aktivieren. Das gilt weiterhin, auch hier. Die Teams, die Release Notes lesen und Cooldowns in Renovate konfigurieren, werden ihrer composer.json ein paar Zeilen hinzufügen. Das Projekt, das jemand vor drei Jahren aufgesetzt hat und das in einem nächtlichen Job composer update ausführt, wird das nicht.

Die Frage kam früh auf. In Issue #12633 erwog Jordi Boggiano, den Cooldown standardmäßig mit einem niedrigen Wert von sechs bis zwölf Stunden einzuschalten, und wog den Sicherheitsgewinn gegen die Störung bestehender Arbeitsabläufe ab. pnpm hat diese Entscheidung mit Version 11 getroffen: Seit pnpm 11 steht minimumReleaseAge standardmäßig auf 1.440 Minuten, also einen Tag. pnpm hat die Änderung allerdings abgefedert. Sein eingebauter Default löst nicht strikt auf und weicht auf eine zu neue Version aus, wenn nichts anderes passt.

So wie ich es lese, würde Composers Design einen Default schwieriger machen. Sein Cooldown löst strikt auf. Ein Composer-Repository ohne published-time stoppt den Befehl. Ein Default hätte deshalb Updates gegen jedes Satis-Repository und jede private Registry gebrochen, die noch keine Veröffentlichungszeitpunkte liefert, und zwar an dem Tag, an dem jemand Composer aktualisiert.

Organisationen, die Private Packagist nutzen, haben bereits eine Art Default. Laut Release-Post können Administratorinnen und Administratoren einen Cooldown für jedes Projekt erzwingen, das das Composer-Repository der Organisation nutzt, gleichgültig, was in der composer.json eines Projekts steht und welche Composer-Version es verwendet. Private Packagist liefert außerdem Veröffentlichungszeitpunkte für gespiegelte und private Pakete, womit das Hindernis aus dem vorigen Absatz entfällt. Die Projekte, um die sich niemand mehr kümmert, haben allerdings selten ein Private Packagist-Abonnement.

Ich verstehe die Entscheidung und hoffe trotzdem, dass sie nicht das letzte Wort ist. Der Release-Post lässt die Frage offen. Ein Default von wenigen Stunden, wie er in #12633 diskutiert wurde, würde genau diese Projekte schützen, und die brauchen den Schutz am meisten.

Wo ich anfangen würde

Die Länge der Wartezeit wägt eine Uhr gegen eine andere ab: die Zeit, die es braucht, ein bösartiges Release zu finden, und die Zeit, die eine Sicherheitskorrektur im Wartezimmer verbringt. Der Release-Post empfiehlt „a period of a few hours to a day“. Composers eigenes Repository wartet sieben Tage, Dependabot standardmäßig drei, Snyk 21.

Für eine Anwendung, deren Team Advisories im Blick behält und --no-cooldown kennt, würde ich dort anfangen, wo Composer selbst angefangen hat: bei sieben Tagen. Ohne eine solche Routine entscheidet der Cooldown auch darüber, wie lange eine Sicherheitskorrektur wartet, und ein Tag ist die bessere Wahl.

{
    "config": {
        "policy": {
            "cooldown": {
                "period": "7 days",
                "audit": "report",
                "ignore": {
                    "example/*": "Our own packages, released through our own pipeline"
                }
            }
        }
    }
}

Sieben Tage entsprechen der Untergrenze, für die Woodruff argumentiert, und zizmor bemängelt Dependabot-Konfigurationen, die kürzer warten. Mit "audit": "report" listet composer audit installierte Versionen auf, die noch im Cooldown stecken, und zeigt dir so, wenn jemand ein frisches Release am Cooldown vorbeigeschoben hat. Die Ausnahme für deinen eigenen Namespace setzt voraus, dass deine eigenen Pakete über eine Pipeline in deine Registry gelangen, der du vertraust. Ist das nicht der Fall, lass sie weg. Ist es der Fall, hält die Regel fest, was bei den Updates von composer/semver in Composer selbst nicht festgehalten wurde.

Ein Cooldown für Renovate oder Dependabot ersetzt das nicht. Er deckt die Pull Requests des Bots ab. Er deckt nicht die Entwicklerinnen und Entwickler ab, die am Freitagnachmittag composer update ausführen, und auch nicht den Coding Agent, der es in ihrem Auftrag tut. Ein Cooldown in der composer.json deckt jedes Update ab, wo auch immer es läuft. Entwicklerinnen und Entwickler können einen Schritt weiter gehen und ihn mit composer config --global policy.cooldown.period "7 days" für jedes Projekt auf ihrem Rechner setzen, und Plattform-Teams können COMPOSER_POLICY_COOLDOWN_PERIOD in ihren CI-Images setzen.

Cooldown und Malware-Liste arbeiten als Paar. Die Liste kann nur blockieren, was bereits jemand gemeldet hat; der Cooldown gibt den Meldenden die Tage, die sie dafür brauchen. Composer 2.10 hat die Liste für alle eingeschaltet. Mit 2.11 kostet die andere Hälfte ein paar Zeilen Konfiguration.

Deine eigenen Listen reichen jetzt bis in die Produktion

Die zweite Änderung in 2.11 ist vor allem für Organisationen wichtig, die eigene Regeln für Abhängigkeiten haben. Composer 2.10 hat eigene Dependency Policies eingeführt: benannte Listen, ausgeliefert von einem HTTPS-Endpunkt oder einem Composer-Repository, die Paketversionen auf dieselbe Weise blockieren wie die Malware-Liste. Composer schickt die Package URLs der infrage kommenden Pakete an jede Quelle und bekommt die zu blockierenden Versionen zurück. Eine Lücke blieb. Eine eigene Liste konnte Versionen bei composer update, require und remove blockieren, aber nie bei composer install. Das konnte nur die eingebaute Malware-Liste.

Stephan Vocks Pull Request #12990 nennt den Fall, der die Änderung ausgelöst hat. Policies ändern sich mit der Zeit, sodass eine bestehende Lock-Datei plötzlich Pakete enthalten kann, die die Organisation nicht mehr zulässt. Composers Dokumentation ergänzt die andere Hälfte: In CI und beim Deployment läuft oft nichts außer composer install, und eine Sperre, die nur beim Update greift, schlägt dort nie an.

Composer 2.11 ergänzt eigene Policies um block-scope, mit denselben drei Werten, die die Malware-Policy schon kannte:

  • update blockiert bei update, require und remove, wie bisher
  • install blockiert nur bei install
  • all blockiert in beiden Fällen
{
    "config": {
        "policy": {
            "legal-hold": {
                "sources": [
                    {"type": "url", "url": "https://policies.example.com/composer/legal-hold"}
                ],
                "block-scope": "all"
            }
        }
    }
}

Der Default für eigene Policies bleibt update, für bestehende Konfigurationen ändert sich also nichts. Das Blockieren bei install musst du ausdrücklich einschalten. Die Malware-Policy behält ihren Default all.

Die Arbeitsteilung, die sich daraus ergibt, halte ich für schlüssig. composer update fragt, welche Version genommen werden soll; der Cooldown und die Advisories beantworten diese Frage, und ihr Blockieren endet dort. composer install fragt, ob du ausliefern darfst, was ausgewählt wurde. Diese Frage können die Malware-Liste und ab 2.11 auch deine eigenen Listen beantworten.

Zwei Folgen solltest du bedenken, bevor du block-scope auf all stellst. Erstens kontaktiert jetzt jede Umgebung, in der composer install läuft, die Quellen der Liste und schickt ihnen die Namen deiner Abhängigkeiten. Deine Build- und Deployment-Umgebungen brauchen Netzwerkzugriff auf diese Quellen, und wer eine Liste als Drittanbieter betreibt, erfährt, was du auslieferst.

Zweitens lassen die Listen im Fehlerfall standardmäßig alles durch. Die Einstellung ignore-unreachable steht standardmäßig auf ["update", "install"]: Antwortet eine Quelle nicht oder liefert sie etwas anderes als einen Status 200, überspringt Composer sie bei Updates und Installationen mit nicht mehr als einer Warnung. Nur composer audit schlägt fehl. Hat deine Liste rechtliches oder sicherheitsrelevantes Gewicht, setze ignore-unreachable auf false. Die Einstellung gilt allerdings für jedes Repository und jede Policy-Quelle, einschließlich der Malware-Liste, die Packagist.org bereitstellt, nicht nur für deine eigene Liste. Eine nicht erreichbare Quelle stoppt dann das Deployment, statt alles durchzuwinken, und die Verfügbarkeit all dieser Quellen wird Teil der Verfügbarkeit deines Deployments. Welchen der beiden Fehlschläge du vorziehst, kannst nur du entscheiden, aber du solltest es bewusst tun.

Fazit

Im Juli habe ich mit der Frage geendet, wie schnell die PHP-Welt ihre Lücken schließen kann. Für den Cooldown lautet die Antwort: knapp vier Monate, nachdem seine Voraussetzung in Betrieb ging. Das Design nimmt den schwierigen Teil ernst. Ein Cooldown, der ab time zählt, wäre leicht zu bauen und leicht zu täuschen gewesen. Composer zählt ab einem Zeitstempel, den nur Packagist.org kontrolliert, weigert sich zu raten, wenn ein Composer-Repository diesen nicht liefern kann, und macht dauerhafte Ausnahmen zu ausdrücklichen Entscheidungen, die in der Versionskontrolle landen. block-scope gibt den Listen, die Organisationen für sich selbst schreiben, dieselbe Reichweite, die die Malware-Liste schon hatte.

Viktor Djupsjöbacka hat den Cooldown begonnen, Jordi Boggiano hat ihn fertiggestellt und verfeinert, und Stephan Vock hat block-scope gebaut, begleitet von Reviews durch Nils Adermann und Christophe Coevoet. Seit dem 30. Juli bietet das Composer & Packagist Sponsorship Program Unternehmen einen formalen Weg, für diese Art von Arbeit zu bezahlen.

Der Default bleibt offen. Composers Abwehr bekannter Bedrohungen schützt alle, auch die Projekte, um die sich niemand mehr kümmert. Seine Abwehr von Bedrohungen, die noch niemand kennt, schützt diejenigen, die Release Notes lesen.

Die wichtigsten Quellen für diesen Artikel sind Nils Adermanns Release-Post zu Composer 2.11 (29. Oktober 2026), das Changelog und die Dokumentation zum Release-Tag 2.11.0, die Pull Requests #12692, #12990, #13102 und #13153 sowie Andrew Nesbitts „Package Managers Need to Cool Down“. Alle anderen Quellen sind dort verlinkt, wo der Text sie verwendet.