RTP DesportoWarren comanda na Aroeira, portugueses lutam pela subidaPunchSinger Ed Sheeran returns to stage after boycott calls, Palestine rowESPNOregon wins in 84-0 rout despite missing 2 key players on offenseוואלהרוכב אופניים בן 54 נפצע באורח קשה בתאונה עצמית בכביש 44The Jerusalem PostGemini hacked three companies in first known breakout by Google's AICNN TürkHava Durumu (19-09-2026)Bollywood HungamaCONFIRMED: Ajay Devgn-starrer Ranger pushed to 2027; WON’T clash with Akshay Kumar-Vidya Balan’s comic caper, Eetha한겨레한국 남자 테니스, 사상 첫 데이비스컵 8강 진출…볼로냐행 확정 ‘신화 창조’InquirerLow Naga River level pushes Peñafrancia fluvial procession after darkFootball ItaliaItaly and Inter legend Mazzola dies at 83MintBangladesh PM Tarique Rahman to visit India in November? Here's what we know so farIl Sole 24 OreUnical prima fra le università italiane nel Ranking di Shanghai per Computer Science & Engineering
The Daily Newsstand · Free, Always
Saturday, September 19, 2026

Имя вашей внутренней библиотеки может занять кто угодно, и сборка возьмёт его версию

Translate

Сборка внутренней библиотеки внезапно стала долгой: вместо секунд - минуты, и в журнале появились обращения наружу. Своя, все зависимости тоже свои, ходить в интернет ей было незачем.

Первая версия - прокси-репозиторий перестал кешировать. Полез в его настройки, там всё в порядке.

Оказалось, что менеджер пакетов запрашивал наш собственный пакет не только у внутреннего репозитория, но и у публичного. И делал он это по конфигурации: второй источник был прописан в настройках.

Прописали его, кстати, не по глупости. Появляется он там ровно тогда, когда внутренний прокси проксирует не всё: часть внешних зависимостей он не отдаёт, сборка падает, и вместо того чтобы чинить прокси, в конфигурацию дописывают публичный индекс. Дальше это живёт годами.

Имя пакета - это просто имя

В большинстве экосистем пакет идентифицируется строкой. Не подписью, не издателем, не доменом - строкой вроде company-auth-utils. Кто первым занял имя в публичном репозитории, тот им и владеет.

Внутренние библиотеки называют по-человечески: billing-common, auth-client, internal-logger. Ровно эти имена в публичном репозитории обычно свободны - потому что никому не приходило в голову, что их надо занять.

Дальше вступает вторая деталь - то, как менеджер пакетов выбирает между источниками.

У pip индексы, перечисленные в настройках, просто объединяются в один список, и побеждает бо́льший номер версии. Понятия «свой источник важнее» там нет вовсе; именно здесь и была суть проблемы, когда её впервые показали публично.

У npm нескольких реестров для одного имени не бывает - но тот же результат даёт прокси, который на запрос имени, которого нет в его хранилище, подмешивает ответ публичного реестра.

Из этих двух свойств складывается вся конструкция: тот, кто опубликовал в публичном репозитории пакет с вашим внутренним именем и версией 99.0.0, оказывается в вашей сборке.

Это уже проверяли на настоящих компаниях

Механизм показал Алекс Бирсан в феврале 2021 года. Он собрал имена внутренних пакетов из открытых репозиториев и из сообщений об ошибках в чужих сборках, опубликовал под этими именами пакеты с заведомо высокими номерами версий и стал ждать.

Пакеты попали в сборки более чем 35 крупных компаний, среди которых Microsoft, Apple, PayPal, Shopify, Netflix, Tesla и Uber. Внутри был безобидный код, который сообщал автору исследования факт запуска (разбор Snyk).

Дыра при этом не в конкретном менеджере пакетов и не в конкретной компании - а в самой схеме «спрашиваем и там, и там, берём версию побольше».

Почему установка - это уже выполнение кода

Отдельная деталь, без которой картина неполная. Многие считают, что чужой пакет опасен только с момента, когда его код вызвали.

Это не так, и здесь есть тонкость.

В Python код выполняется при сборке пакета из исходного дистрибутива - тогда запускается setup.py. Установка готового собранного пакета (колеса) кода не запускает, а колёсами сегодня отдаётся большинство пакетов. Но выбор между этими двумя вариантами делает не разработчик, а то, что лежит в репозитории: если опубликован только исходный дистрибутив, собирать придётся. Атакующему достаточно опубликовать именно его.

В npm проще: обработчики этапов установки выполняются штатно. В обоих случаях код идёт от имени того, кто ставит пакет, с его правами и в его окружении.

Значит, в сборочном агенте код чужого пакета получает то же, что есть у агента: переменные окружения с токенами, доступ к внутренней сети, ключи для выкладки. Набор такой, что его и перечислять неловко, - и именно туда это приезжает.

Отсюда, кстати, следует, что «мы не выкатили сборку в прод, значит, обошлось» - неверный вывод. Выполнение уже произошло.

Соседний приём: опечатка в имени

Та же механика без всяких внутренних имён. Публикуется пакет с именем, отличающимся на символ или на разделитель от популярного: перепутанные дефис и подчёркивание, лишняя буква, единица вместо l.

Дальше работает не техника, а человек: разработчик набирает имя по памяти, торопится, читает подтверждение установки бегло. На ревью такая строка тоже проскакивает - в манифесте написано что-то очень похожее на правильное, а глаз читает знакомое слово целиком, а не по буквам.

Ловит это автоматика: список разрешённых пакетов, проверка имён из манифеста на близость написания к именам известных пакетов. Плюс сам факт, что новая строка в манифесте - это событие, которое видно в diff и требует объяснения: lock-файл с хешами не даст ей появиться молча.

Что действительно закрывает проблему

Один источник для сборки. Сборка ходит во внутренний прокси-репозиторий и никуда больше. Прокси сам определяет, что тянуть из публичного репозитория и что кешировать. Публичный репозиторий из настроек сборки убирается совсем - это основная мера там, где прокси вообще есть.

Правило приоритета в прокси. Для имён из внутреннего пространства прокси обязан отвечать только из внутреннего хранилища и не подмешивать публичные ответы, каких бы версий они ни были.

Именное пространство. В экосистемах, где есть области видимости - @company/package в npm, - область регистрируется на компанию, и посторонний не может опубликовать в ней пакет. Организация в npm бесплатна, пока пакеты в ней публичные; приватные - платный тариф.

Сама по себе область не спасает: нужно ещё привязать её к вашему реестру (@company:registry=), иначе сборка так и будет спрашивать про @company/* у публичного реестра.

Занять свои имена в публичном репозитории. Для экосистем без областей видимости - опубликовать пустышки под своими внутренними именами. Приём грубый, но работает.

Фиксация с хешами. В сборку идёт lock-файл, где для каждого пакета записана контрольная сумма. Тогда подмена содержимого при том же имени и версии ломает установку.

Отключение скриптов установки там, где это возможно. В npm это --ignore-scripts. Часть пакетов без них не соберётся, и придётся разбираться, но начинать разговор стоит с этого, а не считать выполнение кода при установке неизбежным.

Ограничения

Прокси-репозиторий - это инфраструктура, которую надо поднять, обслуживать и держать доступной: он становится единой точкой отказа для всех сборок. Это настоящая цена, и в маленькой команде она может перевесить.

Если прокси вам не по силам, порядок другой: сначала область видимости с привязкой к реестру, потом lock-файл с хешами, потом занятые имена в публичном репозитории. Это слабее одного источника, но заметно лучше, чем ничего.

Регистрация имён в публичном репозитории защищает от занятия имени, но не от того, что аккаунт настоящего сопровождающего чужого пакета уведут. Это другой сценарий, и против него работают только фиксация версий с хешами и осознанное обновление.

Отключение скриптов установки ломает совместимость с частью экосистемы. Это компромисс, а не бесплатное улучшение.

И ни одна из мер не помогает, если разработчик ставит пакет руками на своём компьютере в обход сборки. Его рабочее место в этой теме - самое слабое звено, и закрывают его не техникой, а тем, что ставить зависимости мимо общего механизма незачем.

Что посмотреть у себя

Найти настройки менеджера пакетов в сборке и посмотреть, сколько источников там указано. Больше одного - это и есть условие, при котором всё описанное работает.

Проверить, есть ли ваши внутренние имена пакетов в публичных репозиториях. Проверяется поиском по имени в самом репозитории.

Посмотреть, попадают ли имена внутренних пакетов в открытый доступ: в опубликованные конфигурации сборки, в сообщения об ошибках на форумах, в файлы, случайно оказавшиеся в публичном репозитории кода. Это исходные данные для всей схемы.

Посмотреть, что лежит в переменных окружения сборочного агента. Список того, что получит любой пакет в момент установки, обычно длиннее ожидаемого.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.