29 жовтня вийшов Composer 2.11.0, а наступного дня - 2.11.1 з першими виправленнями. Головна новинка - політика cooldown: Composer може притримувати щойно опубліковані версії пакетів, доки не мине заданий час. Для будь-якого Laravel-проєкту це найдешевший захист від атак на ланцюжок постачання, який можна увімкнути одним рядком у composer.json.
Навіщо затримка
Як пише Нільс Адерман в анонсі випуску, більшість шкідливих версій, які за останні місяці з'являлися на Packagist.org, опублікували зловмисники, що ненадовго отримали контроль над легітимним пакетом, наприклад через зламаний акаунт GitHub. Політика malware з Composer 2.10 блокує таку версію, щойно її позначили, але між публікацією і позначкою завжди є вікно. Якщо в цей момент хтось запускає composer update, шкідливий код потрапляє в composer.lock.
Cooldown (його ще називають мінімальним віком випуску) це вікно звужує. Автоматичні сканери ловлять перші проблеми за лічені хвилини, тож навіть година затримки дає їм шанс. Звіти, які потребують ручної перевірки, йдуть довше, і для них автори радять від кількох годин до доби. Гарантії немає, але кожна година затримки підвищує шанс, що шкідливу версію позначать раніше, ніж вона дійде до вас.
Минулого разу ми писали про Laravel Vet, який показує зміни в коді пакетів перед оновленням. Cooldown його доповнює: Vet - це перегляд коду, cooldown - часова затримка, і одне не замінює іншого.
Як увімкнути
Політика налаштовується в config.policy, як і інші політики залежностей, і за замовчуванням вимкнена:
{
"config": {
"policy": {
"cooldown": {
"period": "1 day",
"ignore": {
"mycompany/*": "We trust the code in our own packages"
}
}
}
}
}
period приймає рядок на кшталт "6 hours" з одиницями second, minute, hour, day, week або ціле число секунд;
ignore назавжди виключає пакети з-під затримки, підтримує пояснення, обмеження версій і wildcard - зручно для власних пакетів компанії;
- ту саму настройку можна прописати в глобальній конфігурації Composer або задати змінною
COMPOSER_POLICY_COOLDOWN_PERIOD, наприклад у CI.
Як cooldown поводиться в командах
- Політика діє, коли Composer розв'язує залежності, тобто на
update і require. composer install і далі ставить рівно те, що лежить у composer.lock.
require, init і create-project пропускають притримані версії, коли обирають версію пакета.
outdated і show --latest показують найновішу дозволену версію і окремо згадують випуски, що ще чекають.
- Dev-версії на кшталт
dev-main і платформні пакети ніколи не притримуються.
- Частковий
composer update vendor/package не чіпає інших заблокованих пакетів. А от повний composer update переоцінює все, тож якщо в lock-файлі вже є версія, яка ще в cooldown, Composer може замінити її старішою.
Виправлення безпеки теж чекатимуть
Затримка стосується кожної нової версії, включно з тими, що закривають вразливості. Автоматично пропускати їх Composer не може: зловмисник із доступом до репозиторію пакета має ті самі права, що й мейнтейнер, і сам опублікує advisory, щоб обійти затримку.
Тому рішення лишається за вами. Щоб поставити виправлення одразу, є нова опція --no-cooldown для update, require, remove і create-project:
composer update --no-cooldown vendor/package
Вона вимикає лише cooldown. Блокування версій з відомими вразливостями, malware і ваші власні політики працюють і далі.
Звідси практичний висновок: що довша затримка, то довше до вас ідуть виправлення вразливостей, яких ви не відстежуєте вручну. Кілька годин або доба - розумний компроміс.
Звідки Composer знає час публікації
Cooldown не з'явився ще в 2.10, бо в метаданих не було надійної дати. Поле time Packagist.org бере з дати коміту тега або з composer.json пакета, тобто його контролює той, хто публікує. Зловмисник може «зістарити» шкідливу версію на кілька тижнів, і затримка за полем time пропустить її одразу.
Тепер Packagist.org віддає для кожної версії published-time - момент, коли він сам уперше зробив версію доступною (composer/packagist#1765). Якщо інший репозиторій такого поля не віддає, команда падає і називає пакет, тож відсутня позначка часу ніколи тихо не вимикає захист. Для репозиторіїв, яким ви довіряєте, можна поставити require-published-time у false і повернутися до поля time. VCS-, path- і artifact-репозиторії мають лише time. Усі деталі - в документації конфігурації.
Виправлення безпеки
У 2.11.0 закрито кілька проблем:
- GHSA-c968-33g3-wh7p: імена пакетів у
vendor/composer/installed.json могли вивести запис за межі каталогу vendor під час встановлення і перевстановлення;
- GHSA-fm2w-xgf2-f9fh: HTTP-заголовки, прописані прямо в конфігурації репозиторію (наприклад, API-токени), записувалися в
composer.lock і installed.json;
- TLS-з'єднання з недовіреними скомпрометованими проміжними сертифікатами eMudhra тепер відхиляються, навіть якщо системне сховище CA довіряє їхнім кореневим;
- додаткові аргументи
@composer-скриптів тепер екрануються;
- bin-проксі більше не вбудовують шляхи і shebang з пакета без екранування, а шляхи з метасимволами оболонки відхиляються;
secure-http ловить і некоректні URL, і URL із синтаксисом <transport>::<address>;
- власні HTTP-заголовки репозиторію більше не надсилаються на dist-адреси інших хостів, чиє ім'я лише починається так само.
Друга вразливість вимагає дії, а не лише оновлення. Якщо ви коли-небудь прописували токен у headers репозиторію в composer.json, перевірте вже закомічені composer.lock: оновлений Composer не прибере з історії git те, що туди вже потрапило. Знайшли токен - відкличте його і випустіть новий. Надалі облікові дані краще тримати в auth.json або COMPOSER_AUTH.
Ті самі виправлення отримала гілка LTS: Composer 2.2.31 для проєктів, які досі живуть на старому PHP.
Що може зламатися в CI
- Немає переходу з dist на source. Composer 2.10 оголосив його застарілим і залишив тимчасову опцію
source-fallback; у 2.11 її прибрали. Якщо dist-архів не завантажився, встановлення падає замість тихого git clone. Source можна попросити явно через preferred-install або --prefer-source.
- Конфлікт злиття в
content-hash. composer install тепер падає, якщо в composer.lock лишився нерозв'язаний конфлікт у content-hash.
- Lock-файл застаріває від
policy. Зміни config.policy тепер позначають lock-файл застарілим. Якщо у проєкті є такий конфіг, після оновлення ви побачите попередження, доки не оновите lock-файл. Зробіть це окремим комітом.
- Власні політики можуть блокувати
install. Нова опція block-scope: all для власних політик блокує відповідні версії і під час install, тож версія, додана до вашого списку після створення lock-файлу, зупинить наступне встановлення.
Інше у випуску
- швидше розв'язання залежностей і менше пам'яті;
composer audit --direct (-D) перевіряє лише прямі залежності;
outdated і show --latest завантажують метадані паралельно;
- на відповідь 429 із
Retry-After Composer чекає і повторює запит;
- Composer більше не запитує HTTP/3, який додавав затримку до кожного нового з'єднання з новими версіями curl;
- нова опція
only-branches для VCS-репозиторіїв і конфіг strict-psr-autoloader.
Composer 2.11.1 виправляє автентифікацію через GitLab CI job token у реєстрі пакетів GitLab, попередження PHP 8.6 про депрекейт з composer/ca-bundle і падіння кожного update із SecurityException, коли розширення PHP має невалідне ім'я платформного пакета.
Що зробити
Оновіться і на локальній машині, і в CI-образах:
composer self-update
Далі - три кроки. Перевірте історію composer.lock на токени, якщо колись прописували їх у headers. Прогоніть CI і подивіться, чи не тримався він на переході з dist на source. І додайте "cooldown": {"period": "1 day"} хоча б у проєкти, де composer update запускає бот: саме автоматичні оновлення найчастіше підтягують шкідливу версію в перші години.