dimhold.by
← Статьи

В январе colors и faker сломал их собственный автор. В декабре оба имени по-прежнему числятся за тем же аккаунтом

8 января автор colors опубликовал версию 1.4.44-liberty-2. Она печатала “LIBERTY LIBERTY LIBERTY”, флаг из символов ASCII и потом мусор в бесконечном цикле. За 3 дня до этого вышел faker 6.6.6, в котором кода не было совсем. В тредах после этого спорили, имел ли автор на это право. Мой ответ на такие истории всегда был стандартный: коммитить lockfile и пинить версии. Но сколько проектов это на самом деле задело, я понятия не имел.

В ноябре 2020 года тот же автор открыл на GitHub issue под названием “No more free work from Marak”. Компаниям там предлагалось 2 варианта: годовой контракт с автором или форк, “fork the project and have someone else work on it”. Пока год не кончился, я решил проверить, как далеко дотягивается один мейнтейнер, который ломает собственный пакет. И какой из 2 вариантов в итоге случился.

Первая версия, которую удалось прочитать

Сломанных версий в npm больше нет. Реестр ещё показывает 1.4.44-liberty-2, 1.4.1 и 1.4.2 с временем публикации, но тарболы удалены. Ещё 4 места, которые я проверил, в том числе npmmirror и jsDelivr, отвечают 404. Поэтому я начал с истории git в Marak/colors.js. За 3 минуты до коммита, который поднимает версию до liberty-2, другой коммит добавляет 10 строк в lib/index.js. Они даже не парсятся. Коммит “Fix bug” через 2 минуты убирает лишнюю точку с запятой. После правки:

/* remove this line after testing */
let am = require('../lib/custom/american');
am();
for (let i = 666; i < Infinity; i++) {
  if (i % 333) {
    // console.log('testing'.zalgo.rainbow)
  }
  console.log('testing testing testing testing testing testing testing'.zalgo)
}

Я запустил это на Node 16.13.1, актуальном LTS на 8 января. require('colors') напечатал около 60 МБ, пока его не убил мой таймаут в 3 секунды. До следующей строки моего скрипта он так и не дошёл. require('colors/safe'), точка входа, которая не трогает String.prototype, отработал за 0,1 секунды и ничего не напечатал. Причина в том, что safe.js грузит lib/colors.js напрямую, а цикл стоит только в lib/index.js.

Дальше я проверил, кто вообще поставил бы liberty-2. Это пререлиз, а диапазон вроде ^1.4.0 пререлизы не берёт никогда. Я проверял через npm-pick-manifest 6.1.1, то есть тот код, которым npm 8.1 выбирает версию. Если latest указывает на пререлиз, ^1.4.0 всё равно даёт 1.4.0. Сломанную версию дают только latest и *. У Live Server в package.json стояло "colors": "latest". Первый комментарий в issue о поломке оставил как раз его пользователь.

Для списка пакетов я взял npm-high-impact 1.1.0, а из него часть, где не меньше 1 миллиона скачиваний в неделю: 4366 пакетов npm. Список ноябрьский, то есть это топ на конец года, а не на январь. У 31 из них colors в какой-то момент был в зависимостях. На 8 января напрямую он был у 9, вместе у них был 21 миллион скачиваний за первую неделю января. Ни один из 9 liberty-2 не получил бы. 8 из них грузят только colors/safe. Только cli-progress грузит обычный colors.

К этому моменту половина поста была написана. В заголовке стояло, что зависнет 1 из 9 топовых пакетов. Я исходил из того, что в 1.4.2 тот же цикл, что в liberty-2, хотя этого не проверял.

1.4.2

1.4.1 вышла тем же вечером, а 1.4.2 через день. Это обычные версии, поэтому при свежей установке без lockfile все диапазоны с ^ уходили на них. 7 из 9 разрешались в 1.4.2. Исключения только cli-table (пин на 1.0.3) и jasmine-spec-reporter (пин на 1.4.0). По сообщению DABH от 11 января (коммиты в colors с 2018 года), npm эти версии удалил. В бюллетене безопасности GitHub от 10 января сказано, что цикл есть во всех 3 версиях, но где именно в коде, не сказано.

10 января Wayback Machine сохранил тарбол 1.4.2. Я сверил его с хешем integrity из lockfile репозитория ngx-ui-scroll. Там 1.4.2 поставили 9 января и в тот же день откатили с сообщением “tests don’t work with 1.4.2”. Хеш совпадает. В lib/index.js тот же цикл. В safe.js он тоже есть, после нового комментария:

/* attempting to patch existing bug */

Выходит, colors/safe был безопасным всего 1 день. Я поставил каждый из 9 пакетов в версии, актуальной на 8 января, через npm 8.1.2. Потом заменил в дереве все копии colors на 1.4.2 и запустил на том же Node. 3 из них виснут на require: logform, cli-progress и тестовый раннер Playwright. Остальные 4 как библиотеки загружаются нормально и виснут, как только подгружается часть, которая работает с цветом. У команд http-server и jscodeshift это сам запуск. karma виснет, когда загружает свой репортер, а cli-table3 на первой таблице с цветным заголовком. 2 пакета с пином работают.

пакет диапазон на 9 янв. с 1.4.2 конец 2022 logform ^1.2.1 1.4.2 виснет на require @colors/colors cli-progress ^1.1.2 1.4.2 виснет на require без colors @playwright/test ^1.4.0 1.4.2 виснет на require без colors cli-table3 ^1.1.2 1.4.2 виснет при вызове @colors/colors karma ^1.4.0 1.4.2 виснет при вызове @colors/colors jscodeshift ^1.1.2 1.4.2 виснет при вызове без colors http-server ^1.4.0 1.4.2 виснет при вызове без colors cli-table 1.0.3 1.0.3 работает пин 1.0.3 jasmine-spec-reporter 1.4.0 1.4.0 работает пин 1.4.0
Версии, актуальные на 8 января 2022 года, диапазоны разрешены так, как это делает npm 8.1, запуск на Node 16.13.1, все копии colors заменены на 1.4.2 из Wayback Machine.

Что делала 1.4.1, я не знаю: её копию я не нашёл нигде, даже в Wayback Machine. В ngx-ui-scroll 1.4.1 пролежала в lockfile 17 часов. За это время там были коммиты с правкой тестов и конфига karma. Именно на неё они сначала и откатились. Тесты у них идут через karma, а она грузит только colors/safe. Это сходилось бы с 1.4.1, у которой цикл только в lib/index.js. Но через 90 минут они перешли на 1.4.0, не объяснив почему. И это 1 репозиторий.

Как быстро ушли

7 из 9 отреагировали за 5 дней. cli-progress выпустил версию без colors 9 января, cli-table3 в тот же день запинил 1.4.0. Ещё 4 подтянулись 10 января, среди них logform и Playwright. karma сделала это 13 января. К концу года 3 из 9 зависят от @colors/colors, форка от DABH, вышедшего в феврале. winston, который получал colors через logform, тоже перешёл на него. DABH к тому же мейнтейнер logform и winston.

У faker тоже появился форк от сообщества, @faker-js/faker, через 5 дней после 6.6.6. Сам faker не был в обычных dependencies ни у одного из 4366. Он живёт в devDependencies, а 6.6.6 это новая мажорная версия, поэтому ^5.5.3 по-прежнему разрешается в 5.5.3.

Имя остаётся

Вот это мне кажется странным. На npmjs.com в декабре у colors стоит версия 1.4.0, у faker 6.6.6. У обоих 1 мейнтейнер, marak. Сломанные версии colors удалили, но имя никому не передали. faker 6.6.6 до сих пор latest, а кода в нём нет. README из одной строки про Аарона Шварца, а в .github/FUNDING.yml всё так же github: marak. npm install faker сегодня ставит пакет, у которого require('faker') падает с MODULE_NOT_FOUND.

млн в месяц 0 50 100 colors @colors 0 5 10 faker @faker-js янв. 2021 янв. 2022 нояб. 2022
API скачиваний npm, суммы за месяц. Вертикальная линия — январь 2022.

Скачивания тоже почти не сдвинулись. В ноябре у colors было 88,5 миллиона скачиваний, на 8% меньше, чем в ноябре 2021 года, а у @colors/colors 47,7 миллиона. У faker 11,0 миллиона, на 7% меньше, чем год назад, против 6,6 миллиона у форка. API npm не делит скачивания по версиям, поэтому сколько из 11 миллионов приходится на старую 5.5.3, я сказать не могу.

Из 2 вариантов 2020 года я вижу форк. Топовые пакеты отреагировали за неделю, в основном пином на 1.4.0. Но имя осталось у автора. Большая часть скачиваний до сих пор идёт на это имя. Форк colors выпущен DABH, чьи собственные logform и winston от colors зависели. Подозреваю, что так держится много инфраструктуры, но 9 пакетов слишком мало, чтобы это утверждать.

Чего я не проверял

Транзитивно зависимые пакеты за пределами топа. Скорее всего, большая часть из 88 миллионов скачиваний colors приходит оттуда. Сколько проектов сидело на lockfile и сломанных версий так и не увидели. Код 1.4.1. И точное время, когда npm удалил 3 версии, потому что реестр его не показывает.