Зависимости наших зависимостей или несколько слов об уязвимости наших проектов
- пятница, 15 декабря 2017 г. в 03:13:37
Эта история началась 30 ноября, утром. Когда вполне обычный билд на Test environment внезапно упал. Наверное, какой-то линтер отвалился, не проснувшись подумал я и был не прав. Кому интересно чем закончилась эта история и на какие мысли навела – прошу под кат.
Открыв билд-лог, я увидел, что упал npm install. Странно подумал, я. Еще вчера вечером все работало отлично. После некоторого изучения логов, была найдена подозрительная строка:
node --eval 'if (require("./package.json").name === "coffee-script") { var red, yellow, cyan, reset; red = yellow = cyan = reset = ""; if (!process.env.NODE_DISABLE_COLORS) { red = "\x1b[31m"; yellow = "\x1b[33m"; cyan = "\x1b[36m"; reset = "\x1b[0m"; } console.warn(red + "CoffeeScript has moved!" + reset + " Please update references to " + yellow + "\"coffee-script\"" + reset + " to use " + yellow + "\"coffeescript\"" + reset + " (no hyphen) instead."); console.warn("Also, a new major version has been released under the " + yellow + "coffeescript" + reset + " name on NPM. This new release targets modern JavaScript, with minimal breaking changes. Learn more at " + cyan + "http://coffeescript.org" + reset + "."); console.warn(""); }
Здесь я опять удивился. Опять же, еще вчера мы coffee-script на проекте не использовали и за ночь вряд ли что-то сильно изменилось. Быстрый просмотр package.json подтвердил, что никакой супостат ничего нового туда не добавлял. Значит, наверное, у нас обновилась какая-то зависимость, которая использует coffee-script. Но против этой идеи говорило то, что уже довольно давно я выставил строгие версии для всех зависимостей проекта и, как мне казалось, такого случиться не могло. Бесплодно поискав похожую проблему в интернете, я опять вернулся к мысли об обновившейся зависимости. Поэтому на коленке был набросан скрипт который обошел все package.json-ы в node_modules в поисках coffee-script. Таких зависимостей оказалось около 5-6 штук. Это еще больше укрепило мои подозрения, и не сильно долго размышляя я снес весь node_modules, а заодно и все dependencies кроме одной в локальном репозитории и запустил npm install снова. Процесс прошел успешно. Дальше, шаг за шагом была найдена та зависимость, которая и валила install.
Это оказалась karma-typescript у которой в транзитивной зависимости оказался "pad", который в свою очередь зависел от coffee-script. И тут я снова приуныл. Вариантов было немного. Или временно отключать тесты, или ждать фикса, или делать форк и чинить самому (причем не очень понятно, что же конкретно нужно чинить). Без особой надежды я отправился на Github создавать issue. Каково же было мое удивление, когда мне ответили буквально через 20 минут. Оказалось, что некоторый товарищ, решил обновить coffee-script пакет в npm и вместо того, чтобы объявить старый пакет устаревшим он просто сделал ему unpublish.
На мое счастье, пользователь @ondrejbase уже предложил костыль временное решение которое мне помогло. И вся эта история закончилась относительно дешево. Но могло быть совсем не так.
Это была присказка. А теперь я предлагаю поговорить о зависимостях наших проектов и проблемах, которые они нам приносят.
Давайте начнём с простого.
Недавно я в очередной раз наткнулся на статью, для новичков которая начиналась со строки
npm install -g typescript
И не выдержал. По моему мнению это один из самых плохих советов который вы можете дать начинающему разработчику. Я серьезно. Вот проблемы, к которым приводит этот совет:
И все это происходит потому, большинство пособий начинается с npm install abc -g
. Хотя можно поставить все локально и подключить в package.json как ./node_modules/.bin/tsc
Если вы соберетесь писать свой гайд, пожалуйста, вспомните эту статью и спасите мои нервы, а также нервы всех тех, кто будет ее использовать на практике.
N.B. Глобально устанавливать генераторы кода (create-react-app, create-angular-app) это нормально. Они один раз отработают и все. Кроме того, вам не понадобится ставить их заново, когда вы решите создать следующий репозиторий.
Давайте идти дальше. Установим create-react-app, и создадим базовое приложение. Заходим в package.json и что мы там видим?
"react": "^16.2.0"
Все (или почти все) знают, что значит символ ^. Он значит, что npm может установить любую версию старше или равной указанной, в пределах мажорного релиза. И что в этом плохого? Не хочется быть категоричным, но, как по мне этот подход тоже "не очень", и вот почему:
Давайте начнем с того, почему у нас сломался билд. Все зависимости были заданы жестко и тем не менее мы свалились. Несмотря на то, что версии наших основных зависимостей оставались прежними (напомню, никаких ^, ~ в package.json) их зависимости были не такими строгими. Мы не контролировали зависимости наших зависимостей, хотя и пытались. И, что самое неприятное такое поведение поощряется по умолчанию. Я не знаю кто и зачем это сделал, но он подложил большую свинью всем нам, а особенно тем, кто практикует continuous-integration.
Конечно, конкретно эту проблему исправить легко. Достаточно создать lock-file (например, с помощью команды npm shrinkwrap) или использовать yarn — пакетный менеджер который по умолчанию фиксирует все зависимости вашего проекта.
Однако это решает только часть проблемы. Остается еще одна, гораздо более опасная ее часть и имя ей unpublish. Если вы не сталкивались с этой проблемой до этого, то вот тут прекрасная статья которая показывает всю уязвимость современной веб разработки. В любой момент, умышленно или по неосторожности, ваш проект может перестать собираться только потому, что кто-то удалил свой пакет из npm. И сделать это отнюдь не сложно. Достаточно просто ввести команду unpublish. С этой бедой можно бороться. Но давайте будем честны перед собой? У кого из нас стоит свой локальный npm? А кто хотя бы задумывался об этом? Боюсь, что не так много. И я лишь надеюсь, что теперь вы предупреждены.
Кстати, если вы зайдете в мой репозиторий (не делайте этого, прошу), то увидите, что почти все мои проекты нарушают все, что было сказано выше. И это еще одно доказательство того, насколько проблема распространена.
На этом у меня все, надеюсь было интересно и полезно. По странной традиции здесь должны быть какая-то реклама, но ее у меня нет, поэтому я просто передам спасибо коллегам, которые вместе со мной тушили этот пожарчик.
И приношу свои извинения за английские слова, но в русском эквиваленте они сильно режут глаз.