[Перевод] Проверили 25 менеджеров пакетов на подмену кода взломанным репозиторием: целостность каждого пакета проверяют только два

Я проверил 25 самых популярных менеджеров пакетов: заметят ли они, что взломанный репозиторий подсунул вредоносный код вместо настоящего пакета. Защитить целостность кода можно криптографическими хешами или подписями, но многие менеджеры пакетов до сих пор уязвимы. Из 25 проверенных меньше половины по умолчанию проверяют lock-файлом, что пакет не подменили, а целостность каждого скачанного пакета проверяют только два.
Атаки на цепочки поставок через взлом разработчиков — уже привычная история. Но представьте, что нынешние меры защиты, в том числе работа Trail of Bits, сработают. Если разработчики популярных пакетов перестанут быть самой лёгкой мишенью, атакующие переключатся на следующее слабое звено: репозитории, которые раздают код. Криптографические ответы на эту проблему тоньше, чем кажется, и многие реализации не дотягивают из-за сознательных проектных решений, нехватки ресурсов или сложности удобных для разработчика схем.
Первый очевидный ответ: положить в lock-файл криптографический хеш содержимого пакета и сверять его после скачивания. Примерно в половине проверенных менеджеров такая проверка включена по умолчанию. В остальных её нужно настраивать, хеши используются криптографически небезопасно или их нет вовсе. Итог — в таблице.
Категория | Менеджеры пакетов |
|---|---|
Проверяют целостность | Go, vcpkg |
Проверяют, если есть lock-файл | Bun, Bundler, Cargo, npm, pnpm, Poetry, SwiftPM, uv, Yarn |
Проверяют с lock-файлом и дополнительными шагами | Conda, Gradle, Pip, NuGet, Pub |
Не проверяют даже с lock-файлом или у них его нет | CocoaPods, Composer, Conan, cpanminus, Gem, Maven, PowerShellGet, PSResourceGet, R |
Таблица 1. Защита lock-файлом при скачивании зависимостей с вредоносного репозитория; менеджеры сгруппированы по тому, сколько усилий нужно разработчику для целостности. Проверено экспериментально на одной конкретной конфигурации, оговорки ниже.
Второй очевидный подход — подписи: не нужно безопасно распространять хеш каждого пакета. Но в моих тестах только Go проверял подписи по умолчанию. В остальных нужна дополнительная настройка, код проверки отсутствует или подписи вообще не поддерживаются.
Категория | Менеджеры пакетов |
|---|---|
Проверяют подписи | Go |
Проверяют после настройки | Bundler, Gem, Gradle, Maven, npm, NuGet |
Проверка отсутствует или неполная | Bun, cpanminus, Pip, pnpm, Poetry, PowerShellGet, PSResourceGet, uv, Yarn |
Подписей для проверки нет | Cargo, CocoaPods, Composer, Conan, Conda, Pub, R, SwiftPM, vcpkg |
Таблица 2. Защита подписями. Тоже проверено экспериментально, оговорки похожие.
Обе таблицы показывают, насколько по-разному устроена криптография в менеджерах пакетов и какой уровень защиты они дают. При этом я нашёл сообщества, которые уже работают над тем, чтобы улучшенные схемы стали реальностью. Дальше разберём, где конкретные решения не дотягивают, почему целостность кода сложнее, чем кажется, и какие схемы могут появиться.
Как сымитировать вредоносный репозиторий
Изучить криптографическое поведение 25 менеджеров пакетов непросто. Я хотел свести ручной анализ к минимуму и при этом быть на 100% уверенным, говоря, что атака со стороны вредоносного репозитория возможна. Для этого я собрал стенд на Docker-контейнерах и mitmproxy, который динамически проверяет менеджеры пакетов против вредоносного сервера.

Рисунок 1. Схема стенда, имитирующего вредоносные репозитории пакетов.
В образах на основе Ubuntu лежат менеджер пакетов, пример кода и, по желанию, lock-файл. Мнения о том, стоит ли коммитить lock-файлы в репозиторий с кодом, расходятся, поэтому я проверил обе конфигурации. Стенд командует менеджеру скачать зависимости из репозитория, и получившийся сетевой запрос идёт через сеть WireGuard к собственному аддону mitmproxy.
Поскольку корневой сертификат mitmproxy установлен в Docker-образ, аддон полностью управляет открытым текстом ответа репозитория и так имитирует взломанный сервер. Я исходил из того, что скомпрометирован может быть любой сервер, доступный из интернета (например, Composer берёт метаданные с Packagist, а пакеты с GitHub), но внутренние ресурсы, в частности приватные ключи подписи, остаются в безопасности. Имитированный репозиторий встраивает вредоносный код в скачиваемые пакеты, и если пример кода его выполняет, атака удалась.
Я старался сделать условия похожими на реальные, но ограничения есть. Я ставил последние версии менеджеров, настраивал пробные проекты внутри контейнера по руководствам для быстрого старта и «заражал» популярные пакеты из публичных репозиториев. Команды запускал с флагами по умолчанию, чтобы понять, нужны ли дополнительные шаги вроде правки манифеста проекта или отдельных команд для проверки целостности. Но я проверял один пакет, одну версию менеджера и одну платформу, а условия теста (например, PowerShell и CocoaPods на Ubuntu) могут не совпадать с типичным использованием. Тест показывает уязвимость разработчика, который следует разумным общепринятым практикам; про всех сразу он ничего не утверждает.
Когда менеджеры установлены и перехват сети работает, видно одно (в основном) обнадёживающее обстоятельство.
HTTPS есть (почти) везде
Стенд перехватывает весь сетевой трафик, поэтому легко проверить, используют ли менеджеры TLS и проверяют ли сертификаты. Почти во всех случаях да.
Исключение — cpanminus, часто рекомендуемый менеджер пакетов для Perl. По умолчанию он ходит в репозиторий Perl по незашифрованному HTTP, и любой активный атакующий в сети может внедрить вредоносный код в скачиваемые пакеты. Эта уязвимость известна годами, но автор cpanminus не перешёл на HTTPS, потому что это сломало бы поведение по умолчанию. (В официальном Docker-образе Perl cpanminus есть, но скрипты сборки образа патчат его на HTTPS по умолчанию.)
Хеши не всегда дают целостность
Меняя ответы репозитория аддоном mitmproxy, я смотрел, как разные менеджеры используют криптографические хеши. Хеш — естественный способ убедиться, что скачанный файл не подменили, но главный вопрос в том, откуда менеджер вообще узнаёт правильный хеш.
Интересный пример — vcpkg от Microsoft, менеджер C/C+±библиотек. Исходники самого менеджера содержат хеши содержимого для тысяч зависимостей. Когда разработчик обновляет библиотеку и хочет зарегистрировать новое содержимое в vcpkg, он пересчитывает хеш пакета и открывает pull request в исходники vcpkg с новым хешем и ссылкой на пакет. При скачивании vcpkg проверяет хеш. Так что пока самому менеджеру можно доверять, эта схема полностью защищает от вредоносного репозитория пакетов. (Оговорка: чтобы получить свежие хеши, vcpkg нужно обновлять через git pull, то есть вопрос смещается от безопасной установки пакетов к безопасному обновлению менеджера. Для единообразия с остальными менеджерами я этот вопрос не рассматривал; это ограничение моей модели угроз.)
Хеши в lock-файлах
Гораздо чаще менеджер узнаёт правильный хеш из lock-файла. Lock-файл обычно создаёт сам менеджер, он лежит рядом с исходниками проекта и ссылается на конкретные версии нужных зависимостей. Если разработчик закоммитил в репозиторий lock-файл с криптографическими хешами, а менеджер у пользователя сверяет скачанные пакеты с этими хешами, зависимости пользователя в точности совпадают с зависимостями разработчика.
Так работает менеджер пакетов Bun для JavaScript. Установка через bun add обновляет lock-файл bun.lock; если закоммитить его в Git и вызвать bun install в другой копии репозитория, хеш из lock-файла сверится с новой копией зависимости. Похоже ведут себя npm, pnpm и Yarn (тоже JavaScript), uv и Poetry (Python), Bundler (Ruby), Cargo (Rust) и SwiftPM (Swift).
Другие менеджеры поддерживают lock-файлы, но с шагами сверх обычной схемы «добавить/восстановить». У Python-овского Pip есть экспериментальная поддержка формата pylock.toml, у Conda — необязательная библиотека Conda lock. NuGet, менеджер для .NET, создаёт lock-файл после добавления свойства RestorePackagesWithLockFile в проект.
У многих менеджеров (Maven, R, PowerShellGet, PSResourceGet, cpanminus) выделенных lock-файлов нет вовсе, а у некоторых они есть, но без хешей: gradle.lockfile у Gradle и Gemfile.lock у Gem. (У Gradle при этом может быть файл verification-metadata.xml с хешами содержимого.)
Хеши есть, но не для целостности
Меня удивило, что в нескольких случаях хеш в lock-файле лежит, а вредоносный сервер всё равно может подменить пакет. Это не обязательно уязвимость: поведение чётко задокументировано. Но видно, что мнений о том, как lock-файл должен быть устроен и как его читать, несколько, и пользователь, привыкший к одному менеджеру, может удивиться поведению другого.
Первый пример — Pub, менеджер пакетов для Dart и Flutter. В этом фрагменте pubspec.lock есть хеш SHA-256 пакета uuid.
uuid:
dependency: "direct main"
description:
name: uuid
sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
url: "https://pub.dev"
source: hosted
version: "4.6.0"
Когда пользователь вызывает flutter pub get, Pub скачивает пакет с сервера pub.dev и считает хеш полученного бинарника. Если хеши не совпали, Pub ошибку замечает, но пакет всё равно устанавливает.
The existing content-hash from pubspec.lock doesn't match contents for:
* uuid-4.6.0 from "https://pub.dev"
This indicates one of:
* The content has changed on the server since you created the pubspec.lock.
* The pubspec.lock has been corrupted.
The content-hashes in pubspec.lock has been updated.
Это документированное поведение, а чтобы заблокировать установку, нужна нестандартная опция --enforce-lockfile. Учтите, что некоторые подкоманды flutter, включая flutter run и flutter test, по умолчанию вызывают flutter pub get, так что и они могут перезаписать pubspec.lock, если не передать --no-pub.
Перезапись lock-файла после несовпадения хеша была поведением по умолчанию и в pnpm — до версии 11.4 мая 2026 года, когда это изменили, чтобы закрыть «дыры в цепочке поставок». Как и у Pub, у pnpm был нестандартный флаг --frozen-lockfile, то есть прежнее поведение с обновлением lock-файла было ожидаемым.
Дальше — фрагмент conan.lock, который использует Conan, менеджер пакетов для C и C++.
"requires": [
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337"
],
Здесь есть имя пакета (zlib), номер версии (1.3.1), ревизия рецепта cac0f6daea041b0ccf42934163defb20 и временная метка. 128-битная ревизия — это MD5-хеш, в который входит исходный код. Но этот хеш не предназначен для криптографической защиты, он служит уникальным идентификатором. Когда Conan ставит зависимости по такому lock-файлу, он по ревизии рецепта скачивает конкретный файл из репозитория ConanCenter, но хеш рецепта заново не считает. Даже если бы считал, у платформенных бинарников есть отдельный хеш ревизии пакета, которого в lock-файле нет, а коллизии MD5 позволили бы атакующему сделать два разных рецепта с разным поведением и одинаковым хешем.
Похожая схема у CocoaPods, менеджера зависимостей для Swift и Objective-C. Вот часть Podfile.lock проекта, использующего популярную зависимость Files.
SPEC CHECKSUMS:
Files: d88e31b84d65e754935053b1c584ea8e2320698a
Эта 160-битная контрольная сумма — SHA-1 от Podspec пакета Files, где есть вот такая ссылка на исходники. По ней CocoaPods скачивает Git-репозиторий.
"source": {
"git": "https://github.com/johnsundell/files.git",
"tag": "4.0.2"
},
Так что lock-файл защищает от подмены GitHub-ссылки в Podspec (если не считать дорогих атак на коллизии SHA-1), но взломанный сервер GitHub может просто отдать вредоносную версию репозитория под тем же тегом.
Наконец, менеджер зависимостей Composer для PHP кладёт в composer.lock хеши коммитов, как в этом фрагменте.
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/b094ded77ee97a6027ad6cf0e8c7b9f88381814c",
"reference": "b094ded77ee97a6027ad6cf0e8c7b9f88381814c",
"shasum": ""
},
Хеш коммита b094ded... однозначно определяет код, но в ZIP-архиве по этой ссылке нет дерева Git, по которому хеш можно было бы проверить. И снова GitHub может отдать вредоносный ZIP, а Composer этого не заметит.
У lock-файлов много ролей, кроме защиты цепочки поставок
Эти результаты показывают, что хеши в lock-файлах часто можно использовать для контроля целостности пакетов, но это не единственное и даже не первоначальное их назначение. Они могут фиксировать зависимости на конкретной функциональности, давать прикладным разработчикам простой способ делить одинаковое окружение или помогать воспроизводимым сборкам. Смотреть на lock-файлы с точки зрения безопасности цепочки поставок — недавняя трактовка, которая может расходиться с исходным замыслом. Пример такого напряжения — недавнее изменение --frozen-lockfile в pnpm.
Другой пример — вопрос, коммитить ли lock-файл в систему контроля версий. Современные рекомендации в основном советуют коммитить lock-файл для приложений: окружение сборки там полностью под контролем. С библиотеками сложнее: менеджеры часто игнорируют lock-файлы библиотек, чтобы не фиксировать конфликтующие транзитивные зависимости. Коммит lock-файла упрощает разработку библиотеки, потому что у всех одинаковое окружение, а без него проще заметить конфликты с разными версиями зависимостей раньше пользователей. Многие менеджеры советуют коммитить lock-файл и для библиотек (например, npm, pnpm, Poetry, uv, Yarn), некоторые рекомендуют не коммитить (Bundler), а некоторые оставляют решение разработчику (Cargo).
Многие из этих вопросов разбирает недавнее исследование Gamage и соавторов. Они изучили дизайн lock-файлов семи менеджеров пакетов, проанализировали опубликованные проекты и провели полуструктурированные интервью с 15 разработчиками. Выяснилось, что практика коммитить lock-файл зависит от менеджера, а участники интервью назвали пять выгод от lock-файлов: детерминированность сборки, целостность, прозрачность косвенных зависимостей, отладка и сканирование безопасности. Некоторые разработчики, наоборот, предпочитали обходиться без lock-файлов: пусть конечные пользователи получают последнюю версию зависимости, где больше исправлений ошибок и уязвимостей.
Из-за разнообразия дизайнов и взглядов на назначение lock-файлов трудно дать одну рекомендацию. Для цепочки поставок обычно, хоть и не всегда, считают, что lock-файл — источник целостности и его надо коммитить вместе с кодом. Если lock-файл менеджера не даёт криптографической целостности, стоит спросить себя, разделяют ли его пользователи такое ожидание. Но и там, где хеши в lock-файле дают целостность, привязка к хешам конкретных пакетов значит, что пользователи не получают следующие обновления и исправления безопасности сразу: у новой версии другой хеш. Отсюда частые «бампы» lock-файлов, чтобы раздать свежие хеши. Желание получать обновления сразу — отдельная задача, не связанная с целостностью, но нынешний распространённый подход с хешами в lock-файлах с ней конфликтует. Итого: хеши в lock-файлах часто, но не всегда обеспечивают криптографическую целостность и работают не во всех ситуациях.
Подписи есть повсюду, проверки нет
Подписи — естественный ответ на эти недостатки, но реализации часто не дотягивают. В теории разработчики могли бы подписывать каждый релиз своим приватным ключом, а менеджер пакетов проверял бы подпись скачанного из репозитория пакета. На практике подписи довольно часто встречаются в разных экосистемах, а вот нормальной их проверки на удивление не хватает. Использовать подписи для целостности кода сложнее, чем кажется, и менеджеры подходят к этому по-разному.
У многих менеджеров подписей нет совсем. Можно считать, что vcpkg они не нужны, ведь хеши всех пакетов поставляются вместе с менеджером, но остаются Cargo, CocoaPods, Composer, Conan, Conda, Pub, R и SwiftPM. (У клиента SwiftPM два основных способа скачивать пакеты: через Git и через собственную спецификацию Swift Package Registry Service. Второй поддерживает подписи, и серверный протокол реализован в частных сервисах вроде JFrog Artifactory и AWS CodeArtifact. Насколько я нашёл, публичного репозитория с этим протоколом нет, поэтому я тестировал формат Git без подписей.) Разработчики Conan об этом знают и ранее решили не чинить, сославшись на ряд трудностей реализации и предложив организациям, где целостность критична, запускать собственный сервер репозитория. Разработчики Cargo рассматривают разные схемы подписи, но работа ещё идёт. Разработчики Composer внедряют много новых защит от атак на цепочки поставок, а целостность артефактов — в планах.
Подпись репозитория
Прямолинейный подход, подпись репозитория: репозиторий подписывает каждый пакет после загрузки, а открытый ключ репозитория поставляется вместе с менеджером. Это не решает проблему полностью скомпрометированного репозитория, ведь вредоносный репозиторий может законно подписать вредоносный пакет, но защищает от недоверенных зеркал и частичного взлома серверной инфраструктуры репозитория.
Подписи репозитория используют npm Registry, NuGet Registry и сервер PAUSE для дистрибуции модулей Perl. В npm Registry подписи передаются в метаданных пакета, а открытые ключи свои у каждого репозитория. В NuGet Registry подписи лежат в файлах .signature.p7s внутри ZIP-пакета, и открытые ключи тоже свои у репозитория. PAUSE идёт иначе: он подписывает своим GPG-ключом сразу пачку хешей нескольких пакетов.
Подпись разработчика
При подписи разработчика закрытый ключ хранит сам разработчик и подписывает пакет перед загрузкой в репозиторий. Репозиторий не может подменить содержимое, и заодно получает способ аутентифицировать того, кто загружает.
Заявить свой открытый ключ можно двумя способами. В первом открытый ключ передаётся отдельным каналом. Так умеют Maven Central (его используют Maven и Gradle) и репозиторий RubyGems (для gem и Bundler): оба поддерживают такую подпись разработчика. Maven Central использует подписи PGP, а ключи разработчиков раздают публичные серверы ключей. RubyGems использует самоподписанные сертификаты, а их распространение оставлено на разработчиков (например, rspec). Главное различие: для Maven Central подпись разработчика обязательна, для RubyGems — нет. В RubyGems она редка из-за отсутствия устоявшейся цепочки доверия, а прежняя попытка ввести центр сертификации провалилась.
Второй способ опирается на цепочку доверия. Подписи разработчика поддерживают NuGet Registry и PowerShell Gallery. Ни там, ни там они не обязательны, но PowerShell Gallery рекомендует их, а NuGet Registry предъявляет строгие требования, если подпись используется. В обоих ключ разработчика должен вести к доверенному центру сертификации, а действующий сертификат подписи кода может стоить сотни долларов в год. Выигрыш в том, что доверенные корневые сертификаты можно предустановить на платформе, и менеджеры смогут проверять открытые ключи.
Недостаток подписи разработчика — управление ключами лежит на нём, и, потеряв доступ к ключу, он теряет возможность обновлять свой код.
Подпись по идентичности
Всё чаще встречается промежуточный вариант — «подпись по идентичности», или «keyless»-подпись от Sigstore. Разработчику не нужно хранить долгоживущие закрытые ключи: он может бесплатно получить свежий сертификат подписи по запросу у центра сертификации Sigstore Fulcio. Чтобы не допустить подмены, разработчик подтверждает личность токеном OIDC — подписанным третьей стороной (Google, Microsoft, GitHub) утверждением об адресе почты, имени пользователя или других данных о разработчике. Проверяющий сверяет имя пользователя и то, что цепочка сертификатов ведёт к корневому сертификату Sigstore Fulcio. Это доказывает, что подпись поставил тот, у кого есть доступ к этому аккаунту.
Keyless-подпись особенно удобна тем, что встраивается в существующий конвейер развёртывания разработчика. Её часто называют аттестацией происхождения (provenance attestation) или trusted publishing. (Trusted publishing может означать и аутентификацию в репозитории пакетов токенами OIDC, и тогда подписи по идентичности может не быть.) GitHub и GitLab выдают токены OIDC для Actions и CI/CD-конвейеров, удостоверяющие сборочный процесс, репозиторий и хеш коммита. Процесс может обменять токен на сертификат подписи в Sigstore, подписать собранный пакет и загрузить подписанный пакет в репозиторий. Так как сертификат содержит хеш коммита и данные процесса, он криптографически связывает собранный артефакт с исходным кодом.
Аттестацию происхождения через Sigstore поддерживают Python-овский PyPI, npm Registry, репозиторий RubyGems и Maven Central. Она не обязательна, но уже лучше принята, чем необязательная подпись разработчика управляемым ключом в RubyGems.
Код проверки пропущен, не настроен или отсутствует
К сожалению, наличие подписей не значит, что менеджеры пакетов их проверяют.
В npm подписи репозитория не проверяются при npm install. Для проверки нужно запустить npm audit signatures: она через Sigstore проверяет открытые ключи реестра, а затем подписи установленных пакетов. В pnpm недавно появилась команда pnpm audit signatures, но она берёт открытые ключи реестра у самого, возможно, вредоносного реестра и обходится без проверки через Sigstore, так что атакующий может просто подложить свой ключ и свою подпись. В Bun и Yarn аналога audit signatures пока нет.
В NuGet значение по умолчанию параметра signatureValidationMode — accept, то есть пакеты с неверной подписью устанавливаются. (От переводчика: по документации NuGet, режим accept в первую очередь пропускает неподписанные пакеты, а изменённое содержимое подписанного пакета блокируется ошибкой NU3008; автор оригинала формулирует жёстче.) Пользователь должен поставить require и настроить список доверенных подписантов, например командой dotnet nuget trust source. Эти настройки (включая открытые ключи репозитория) можно безопасно распространять в файле конфигурации nuget.config, и он будет играть роль lock-файла для доверенных подписантов.
Gem и Bundler у Ruby, Maven и Gradle у Java используют репозитории с подписями разработчиков, но по умолчанию подписи не проверяют. Пользователь должен вручную добавить открытые ключи разработчиков (gem cert --add) или перечислить допустимые доверенные ключи. В Maven для этого есть сторонний плагин pgpverify-maven-plugin с файлом pgp-keys-map.list, где указываются доверенные отпечатки PGP. Gradle предлагает проверку зависимостей, которая работает похоже.
Наконец, в некоторых менеджерах кода проверки, судя по всему, нет вообще. У Pip, uv и Poetry пока нет способа проверять аттестации по идентичности из PEP 740. У cpanminus нет способа проверить целостность подписанного репозиторием файла CHECKSUMS. Раньше был, но после уязвимости с обходом проверки этот код убрали. PowerShell поддерживается и вне Windows, но проверка подписей Authenticode там не работает. (На Windows я PowerShell не тестировал, но по документации политика ExecutionPolicy по умолчанию блокирует скрипты на клиентских Windows и требует действительную подпись на серверных, что даёт целостность. От переводчика: по документации Microsoft, политика RemoteSigned на серверных Windows требует подпись только для скриптов с отметкой об интернет-происхождении, так что гарантия слабее, чем в оригинале.) В какой-то момент в репозитории PowerShell отмечали, что эта проблема «должна быть исправлена в будущем релизе».
Менеджер пакетов Go и журналы прозрачности
Встроенный менеджер пакетов Go — единственный из проверенных, который по умолчанию проверяет подписи, и он вводит ещё одно понятие для криптографической целостности пакетов. Когда go get или go mod download скачивает пакет, он хеширует содержимое и сверяет хеш, имя пакета и версию с базой контрольных сумм. Содержимое базы подписано реестром (аналогично PGP-подписям в PAUSE у Perl), а открытый ключ базы контрольных сумм по умолчанию зашит в исходники Go. Пока закрытый ключ базы используется честно, такая схема гарантирует, что имени пакета и версии достаточно, чтобы однозначно определить бинарное содержимое. Поэтому lock-файл go.mod обеспечивает целостность пакетов без хранения криптографических хешей. (Go называет скачиваемые артефакты «модулями», состоящими из «пакетов»; ради единой терминологии в этом тексте я говорю «пакет» в общем смысле. А go.sum хранит хеши, но не является lock-файлом: это локальный кеш записей из базы контрольных сумм.)
База контрольных сумм Go — пример журнала прозрачности (transparency log): неизменяемого, глобального журнала метаданных, защищённого от подделки. Те же возможности даёт Rekor из Sigstore и журналы прозрачности сертификатов. В менеджерах пакетов он криптографически гарантирует, что устанавливаются только те бинарники, что записаны в журнале. Если все согласны насчёт журнала, все согласны насчёт содержимого пакетов. Это усложняет атакующему избирательную, незаметную атаку на конкретную жертву вредоносным пакетом. Журналы прозрачности всё же могут не сработать, если есть другие уязвимости, если скомпрометирован закрытый ключ журнала или если никто не следит в журнале за вредоносным поведением.
Из всех проверенных менеджеров лучше всего сохранять криптографическую целостность при взломе репозитория или прокси смог Go. Но схема, похожая на подпись реестра, означает, что доверие системы сильно сконцентрировано в одном закрытом ключе.
Предлагаемая криптографическая схема для менеджеров пакетов
Один из вариантов, сочетающий идеи этого текста, — класть информацию о подписи в lock-файл. Вместо хеша конкретного пакета lock-файл фиксировал бы идентичность или открытый ключ разработчика. Это переиспользует значительную часть инфраструктуры хешей в lock-файлах, но разделяет вопросы немедленных обновлений и целостности пакетов.
В такой схеме lock-файл мог бы задавать одну версию зависимости (для воспроизводимости сборки) или нижнюю границу (чтобы сразу получать обновления), а указанная идентичность не даст вредоносному репозиторию или прокси незаметно подменить пакет той версии, на которую разрешится зависимость. Вдобавок журнал прозрачности защитит от избирательных атак на отдельных жертв, опираясь на подпись по идентичности и сервис журнала прозрачности Sigstore. По умолчанию Sigstore обходится без долгоживущих ключей, но поддерживает и собственные ключи, KMS-провайдеры и аппаратные токены. Получается безопасный вариант по умолчанию, который легко внедрить, и гибкость для тех, кто хочет сам управлять своими ключами.
Идея не нова и не моя. Идентичности аттестации входят в спецификацию Python-овского lock-файла pylock.toml, в trusted-keys из метаданных проверки Gradle и в файл pgp-keys-map.list плагина pgpverify-maven-plugin для Maven. Многие менеджеры пакетов уже движутся в эту сторону: растёт внедрение Sigstore в процессы публикации, растёт внимание к безопасности цепочек поставок, а исправления ошибок прямо ссылаются на возможную подмену со стороны злоумышленника. Всё больше людей признаёт проблему, и всё больше желающих её решать.
Будет нелегко. Нужно учесть множество тонкостей, разработать и проаудитировать код проверки подписей, а разработчикам — принять итоговую схему. Во многих из изученных менеджеров я нашёл сообщества людей, которые годами думают о криптографической целостности и подписи пакетов и добиваются их. Они лучше всех знают, что нужно их менеджерам, и заслуживают ресурсов, чтобы воплотить это в жизнь.
Один из самых успешных криптографических сюжетов последнего десятилетия — почти всеобщий переход на HTTPS: сказалось осознание рисков нешифрованного трафика в индустрии и сообществе, а некоммерческий Let’s Encrypt бесплатно дал всем простые в использовании TLS-сертификаты. Подпись пакетов сейчас там же, где был HTTPS десять лет назад: упорные атакующие — огромная проблема, а криптографической защиты почти нет. Всеобщей подписи пакетов пока нет, но она на горизонте. При должном внимании и вложениях мы можем сделать её реальностью.
Автор оригинала благодарит Уильяма Вудраффа за ценные обсуждения lock-файлов, подписи по идентичности и сложностей безопасности менеджеров пакетов. Здесь рассматриваются менеджеры зависимостей для исходного кода; системные менеджеры вроде APT, Chocolatey или Homebrew за рамками. Граница между ними размыта, но сосредоточенность на одной группе лучше показывает характерные сценарии использования и развёртывания. Trail of Bits ведёт несколько текущих и завершённых проектов по укреплению безопасности цепочек поставок в части из этих экосистем.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.