# Как остановить галлюцинации пакетов в сгенерированном коде?

> Галлюцинации пакетов могут превратить быструю подсказку ИИ в непроверенную зависимость. Проверьте идентичность, сопровождающих, релизы, скрипты и изменения lockfile.

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

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

## ИИ может придумать зависимость, которую реестр без проблем разрешит

Ассистент для программирования предсказывает текст. Он не обращается к реестру, если процесс специально не дает ему такой инструмент и не требует использовать полученный результат. Попросите его найти малоизвестный парсер, нишевый облачный SDK или удобную оболочку для популярной библиотеки, и он может вернуть имя пакета, которое звучит совершенно правдоподобно. Иногда такого имени не существует. Иногда это опечатка. Иногда его зарегистрировал владелец постороннего пакета. Самый опасный вариант тот, который существует и устанавливается без ошибок.

У такой ошибки есть предсказуемый путь. Инженер просит написать код для преобразования формата файла. Ассистент выдает `import convert from "file-convert-pro"` и добавляет пакет в `package.json`. Рецензент видит код, похожий на рабочий, узнает общую задачу и одобряет изменение, не запустив приложение. У пакета с таким именем теперь появляется аудитория: любой, кто скопирует ответ или примет запрос на слияние.

Злоумышленнику не обязательно взламывать вашу учетную запись в реестре или компрометировать уже популярный пакет. Достаточно пакета, похожего на нужную зависимость. В рекомендациях по угрозам npm прямо упоминаются typosquatting и dependency confusion, когда разработчиков пытаются заставить установить пакет с похожим именем. npm также сообщает, что обнаруживает и блокирует некоторые попытки typosquatting. Ключевое слово здесь «некоторые». Решение о добавлении кода нового издателя все равно принимает ваш процесс проверки.

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

## Имя пакета сообщает об идентичности, а не служит поисковым запросом

Имя пакета отвечает на вопрос «какой код мы установим?». Но оно не отвечает на вопрос «какую библиотеку имел в виду автор?». Это разные вопросы, и команды сталкиваются с проблемами, когда делают вид, что это одно и то же.

Типичная ошибка связана с предвзятостью к сходству. Рецензент видит знакомую часть имени, например `openai`, `stripe`, `react`, `auth` или `logger`, предполагает, что остальная часть обозначает известное расширение, и идет дальше. Именно поэтому работает убедительная опечатка или выдуманная оболочка. В рекомендациях npm по именованию пакетов издателям прямо советуют не выбирать неограниченные имена, похожие на имена других пакетов или создающие путаницу с их авторами. Эта политика признает неоднозначность, но не отменяет необходимости проверить точное имя.

Перед добавлением прямой зависимости ответьте в описании запроса на слияние на четыре вопроса:

- Какие возможности дает этот пакет, которых нет у наших текущих зависимостей?
- Какая официальная документация проекта, репозитория или поставщика привела нас именно к этому имени пакета?
- Почему нам нужен этот пакет, а не прямой вызов базовой библиотеки или сервиса?
- Кто будет отвечать за будущие обновления и уведомления о безопасности?

Второй вопрос особенно важен для кода, созданного ИИ. «Так предложила модель» не является источником. «В README сказано, что пакет оборачивает API» тоже недостаточно. Найдите официальную документацию API или проекта и проверьте, упоминается ли в ней этот клиент. Если пакет заявляет, что это официальный SDK, но поставщик его не признает, считайте его сторонним кодом. Это может быть приемлемо, но стандарт проверки должен сразу стать выше.

К именам с областью действия нужен такой же внимательный подход. Область может создать впечатление официального пакета, но на деле лишь указывает пространство имен в реестре. Она не доказывает, что это пространство принадлежит компании, о которой думает команда. Проверяйте издателя через собственную документацию компании или ее организацию с исходным кодом, а не по визуально привычной строке `@name/package`.

Я также возражаю против популярного сокращенного пути: «Давайте установим и посмотрим, работает ли». Установка это граница выполнения, а не нейтральный предварительный просмотр. Метаданные реестра и содержимое пакета можно изучить до того, как вы допустите его в обычный процесс разработки. Это медленнее, чем бездумно вставить команду, но намного быстрее, чем выяснять, почему сборочный сервер сделал неожиданный сетевой запрос.

## История издателя показывает, чему вы доверяете

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

Начните с метаданных реестра. `npm view` выводит данные пакета из реестра и может запрашивать отдельные поля, не выгружая страницу лишнего текста. npm документирует такие поля, как сопровождающие, сведения о репозитории, теги версий и время публикации конкретных версий.

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

```bash
pkg="example-package"
npm view "$pkg" name version "dist-tags.latest" maintainers repository time dependencies --json
```

Ожидайте JSON примерно такой структуры:

```json
{
  "name": "example-package",
  "version": "1.4.2",
  "dist-tags": {"latest": "1.4.2"},
  "maintainers": [{"name": "publisher-name"}],
  "repository": {"type": "git", "url": "..."},
  "time": {"created": "...", "modified": "..."},
  "dependencies": {"another-package": "^2.0.0"}
}
```

Значения будут различаться, а некоторые поля могут отсутствовать. Само отсутствие тоже дает информацию. Пакет, который называет себя серьезным SDK, но не показывает репозиторий, существует всего один день и никак не объясняет, кому принадлежит, должен остановить процесс, пока человек не сможет объяснить ситуацию.

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

Непрерывность истории издателя важнее одной даты. Проект перешел в новую организацию с ясным объявлением? Давно не обновлявшийся пакет внезапно выпустил версию с большим изменением зависимостей? Сопровождающие сменились прямо перед новым выпуском? Это не доказывает злого умысла, но подсказывает, где стоит потратить больше времени на проверку.

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

Для пакетов, которые поддерживают такую возможность, изучите provenance в реестре и после установки в изолированной среде выполните проверку подписи:

```bash
npm ci --ignore-scripts
npm audit signatures
```

npm описывает `npm audit signatures` как проверку подписей реестра и аттестаций provenance. При отсутствии или недействительности данных команда сообщает об ошибке. Используйте результат как сигнал целостности. Не превращайте успешную проверку в автоматическое разрешение.

## Активность выпусков должна соответствовать назначению пакета

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

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

Задайте простой вопрос, который инженеры часто пропускают: что произойдет с нами, если в следующем месяце этот сопровождающий исчезнет? Если ответ звучит как «мы заменим 40 строк связующего кода», радиус поражения небольшой. Если ответ такой: «перестанут работать вход в систему, развертывание, биллинг или импорт данных», нужна более убедительная история сопровождения и осознанное решение о владельце.

Не требуйте корпоративной поддержки от каждого пакета. Такая привычка отсекает хорошие проекты с открытым исходным кодом и подталкивает команды к слишком крупным поставщикам. Требуйте, чтобы риск зависимости соответствовал имеющимся доказательствам. Небольшая библиотека с прозрачным исходным кодом и без скриптов установки может быть разумным выбором. Малоизвестный пакет, который запускает postinstall-хук, скачивает бинарный файл и находится между сервисом и данными клиентов, требует гораздо более строгой проверки.

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

## Lockfile показывает настоящее изменение, а не лишний сгенерированный шум

`package.json` описывает ваше намерение. `package-lock.json` показывает, что разрешил менеджер пакетов. Второй файл нужно проверять, потому что сборка устанавливает именно это дерево, а не краткую историю из одной строки в запросе на слияние.

npm описывает `package-lock.json` как точное дерево зависимостей, созданное при установке, и говорит, что его фиксация позволяет коллегам, системам развертывания и CI устанавливать одно и то же дерево. GitHub делает такой же практический вывод со стороны проверки: его граф зависимостей анализирует манифесты и lockfile, а lockfile дает самый надежный обзор прямых и косвенных версий.

Прямая зависимость может подтянуть десятки косвенных пакетов. Это не делает запрос на слияние неправильным. Но фраза «я добавил всего один пакет» не является содержательным выводом проверки. Изучите diff по четырем направлениям:

- Прямое имя и запрошенная версия соответствуют согласованному пакету.
- Разрешенные версии и адреса реестра ожидаемы.
- Изменение не заменяет и не обновляет несвязанные пакеты без объяснения.
- Новое дерево не добавляет поведения во время установки, о котором автор не упомянул.

Сначала используйте узкий diff:

```bash
git diff -- package.json package-lock.json
npm ls --all
npm explain example-package
```

`npm explain` помогает проследить, почему пакет оказался в дереве. Это важно, когда рецензент видит неожиданную косвенную зависимость и должен понять, пришла ли она из новой библиотеки или возникла из-за несвязанного изменения разрешения версий.

Затем воспроизведите установку с нуля. `npm ci` требует lockfile, удаляет существующий каталог `node_modules`, отказывается работать при расхождении манифеста и lockfile и не записывает изменения ни в один из этих файлов. Поэтому для проверки согласованности зафиксированного дерева это лучшая команда CI, чем обычный `npm install`.

```bash
rm -rf node_modules
npm ci --ignore-scripts
npm audit signatures
```

Я использую `--ignore-scripts` на первом этапе, потому что npm сообщает: этот параметр не позволяет запускать скрипты, объявленные в манифестах пакетов. Он не делает пакет безопасным и не мешает инженеру позже явно выполнить `npm run`. Но он дает контролируемую точку, где можно изучить дерево зависимостей до запуска хуков жизненного цикла.

Lockfile не защищает от плохого первоначального выбора. Он делает этот выбор воспроизводимым. Для отладки это полезно, но если никто не заметил сам выбор, последствия будут хуже.

## Скрипты установки нужно согласовывать отдельно

Зависимости могут выполнять код до запуска приложения. `preinstall`, `install`, `postinstall` и связанные скрипты жизненного цикла запускаются в процессе работы менеджера пакетов, часто на ноутбуках разработчиков и CI-серверах, где есть учетные данные, исходный код, токены развертывания или доступ к внутренним сервисам.

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

Когда это возможно, до обычной установки изучите архив предложенной зависимости:

```bash
npm pack example-package --dry-run
npm view example-package scripts --json
```

Первая команда показывает, что npm положил бы в tarball. Вторая запрашивает у реестра объявленные скрипты пакета. Если пакету нужен скрипт жизненного цикла, в запросе на слияние должно быть указано, что он запускает, зачем нужен и в каких средах будет выполняться.

В актуальной документации npm описывается политика `allowScripts` для установок проекта и параметр `strict-allow-scripts`, который может превращать непроверенные скрипты установки в ошибку установки. Там же документированы команды `npm install-scripts`, сохраняющие одобрения в `package.json`. Детали важны, поскольку версии npm различаются. Поэтому сначала проверьте это на точной версии CLI, которую использует ваш CI, и только потом делайте правило обязательным условием слияния.

Фрагмент политики может сделать решение проверяемым рядом с зависимостью:

```json
{
  "allowScripts": {
    "approved-native-module@3.2.1": true,
    "example-package": false
  }
}
```

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

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

## Автоматическая проверка может отклонять известные плохие изменения, но не заменяет суждение

Добавьте проверку зависимостей в запросы на слияние, а вопросы, которые автоматизация не решает, оставьте людям. Dependency review от GitHub использует изменения манифеста и lockfile, чтобы показать добавленные, удаленные и обновленные пакеты, включая косвенные изменения, даты выпусков, известные уязвимости, лицензии и сведения об использовании. Его action может завершать workflow с ошибкой при выбранном уровне серьезности уязвимости и применять правила для лицензий.

Минимальный workflow выглядит так:

```yaml
name: Dependency review
on: [pull_request]
permissions:
  contents: read
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high
          fail-on-scopes: runtime
```

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

Не принимайте чистый результат dependency review за доказательство легитимности пакета. Базам уязвимостей нужно время, чтобы обновиться. У совершенно нового вредоносного пакета может не быть уведомления, CVE, репутационного сигнала или известной уязвимой версии. Автоматизация видит опубликованные метаданные, но не может понять, придумал ли ваш ассистент ненужный пакет.

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

## Давайте агентам меньше полномочий, чем людям, сопровождающим код

Безопаснее всего использовать ИИ-ассистента в рамках ограниченного запроса на изменение. Пусть он предлагает код и тесты. Пусть открывает запрос на слияние. Но не позволяйте ему добавлять пакет, запускать менеджер пакетов с широкими учетными данными и принимать собственный результат в одном автоматическом цикле без присмотра.

В репозиториях с агентами я использую четыре правила:

1. Агент может изменять код приложения, тесты и документацию без особых правил проверки.
2. Любое добавление прямой зависимости требует ответственного человека в запросе на слияние.
3. Любое изменение манифеста или lockfile запускает проверку зависимостей и требует письменной причины.
4. Ветка агента не может автоматически принять изменение зависимости, даже если тесты прошли.

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

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

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

## Десять минут проверки зависимости предотвращают долгую работу по реагированию на инцидент

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

1. Подтвердите точное имя пакета в официальной документации проекта или поставщика.
2. Выполните `npm view` и проверьте репозиторий, сопровождающих, историю публикаций и зависимости.
3. Узнайте, объявлены ли у пакета скрипты установки, и выясните зачем.
4. Изучите `package.json` и `package-lock.json` вместе, включая косвенные добавления.
5. Выполните установку с `npm ci --ignore-scripts`, запустите тесты и проверьте подписи там, где это поддерживает реестр.

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

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

В AppMaster.io сокращение инженерной команды не означает передачу агенту неограниченных полномочий. Оно означает создание точек проверки, которые позволяют двум сильным инженерам работать быстро, не доверяя каждой правдоподобной строке сгенерированного кода. Если агенты уже могут менять зависимости, закажите Team & AI Audit, пока это разрешение не превратилось в решение о цепочке поставок без ответственного.

## Политика должна соответствовать команде, которая будет ее соблюдать

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

Для компании на ранней стадии я бы ограничил письменную политику одной страницей. Укажите, что агенты не могут принимать изменения с добавлением зависимостей. Требуйте владельца и пять описанных выше шагов проверки. Сделайте job проверки зависимостей обязательным. Сохраняйте воспроизводимые установки в CI с зафиксированным lockfile. Записывайте исключения в запросе на слияние, а не в отдельной таблице, которая исчезнет через две недели.

Для крупной инженерной группы назначьте code owners для манифестов, конфигурации менеджера пакетов и workflow сборки. Централизованно отслеживайте прямые зависимости и устанавливайте дату окончания временных исключений. Цель не в идеальном инвентаре на бумаге, а в понимании того, кто и почему принял каждую прямую зависимость и почему она все еще нужна.

Не вводите правило, по которому каждый пакет обязан иметь provenance. Иначе вы начнете отклонять полезные пакеты, которые не могут его предоставить, и приучите инженеров скрывать изменения. Требуйте provenance там, где оно есть, особенно для пакетов, собирающих артефакты или затрагивающих чувствительные пути. Отсутствие provenance должно быть частью проверки, а не магической оценкой риска.

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

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