Мой бот прислал мне репо-ловушку с 1774 звёздами. Разобрал 321 репо из трендов и собрал два фильтра. deepseek.. deepseek. github.. deepseek. github. llm.. deepseek. github. llm. Open source.. deepseek. github. llm. Open source. python.. deepseek. github. llm. Open source. python. supply chain.. deepseek. github. llm. Open source. python. supply chain. вредоносное по.. deepseek. github. llm. Open source. python. supply chain. вредоносное по. галлюцинации.. deepseek. github. llm. Open source. python. supply chain. вредоносное по. галлюцинации. Информационная безопасность.. deepseek. github. llm. Open source. python. supply chain. вредоносное по. галлюцинации. Информационная безопасность. искусственный интеллект.

У меня есть Telegram-бот, который каждое утро присылает пять трендовых репозиториев GitHub с trendshift.io. Под каждым кнопка: нажал, и бот выдаёт выжимку README, чтобы разобрать идею. Я читаю эту ленту каждый день и иногда ставлю то, что понравилось.

26 сентября в ленте был «Microsoft Activation Scripts»: 1774 звезды, аккуратное описание, теги. Бот оформил его так же, как четыре соседних проекта, с той же кнопкой.

Это была ловушка.

Как она устроена

В репозитории один файл, README. Аккаунт владельца создан за 94 секунды до репозитория. Команда установки выглядит так:

iex(irm(-join"…"[-1..-99]))

Адрес скачивания записан в README задом наперёд. PowerShell сначала переворачивает строку, потом скачивает скрипт и сразу его выполняет. Глазами домен не прочитать, и сканер, который ищет в тексте ссылки, его тоже не видит. Ведёт он на домен, похожий на настоящий.

Настоящие Microsoft Activation Scripts существуют, и у них тоже установка в одну строку через irm | iex. На этом и расчёт: человек видит знакомую команду и вставляет её в PowerShell.

Очевидные фильтры не работают

Первое, что приходит в голову: помечать любую установку вида «скачай и сразу запусти» с сайта не на GitHub. Я прогнал это правило по всему, что бот прислал с 15 июля по 8 октября: 321 репозиторий, 314 из них ещё живы.

Так ставятся 38 из 314 нормальных проектов, среди них codex и opencode, а ещё несколько так же тянут установщики rustup или uv. Фильтр прятал бы каждый восьмой репозиторий из трендов, причём самые полезные.

Второе очевидное правило: «аккаунту владельца меньше 30 дней». Ловушка его прошла бы. В день, когда бот её прислал, аккаунту было уже 34 дня. Выдаёт одноразовый аккаунт не его возраст в момент проверки, а разрыв между созданием аккаунта и созданием репозитория.

Что сработало

Два независимых признака, любого достаточно.

  1. Замаскированная команда установки в README. Строка, перевёрнутая через -join"…"[-1... iex(irm(…)), где адрес вычисляется. base64, который декодируется и сразу выполняется. Скрытый запуск через mshta, certutil, bitsadmin или -WindowStyle Hidden. Перед проверкой текст очищается от невидимых символов и обратных кавычек PowerShell, которыми разбивают i`e`x.

  2. Одноразовый владелец и нет кода. Аккаунт создан меньше чем за 30 дней до репозитория, а в репозитории меньше 10 КБ или только README и LICENSE. Если GitHub не ответил, это «неизвестно», а не «кода нет»: сбой сети не должен прятать нормальный проект.

На тех же 314 репозиториях каждый признак срабатывает ровно один раз, на поддельном активаторе.

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

Чего фильтр не ловит, тоже записал:

  • обычную команду установки на похожий домен, без маскировки;

  • README со ссылкой на архив на старом аккаунте. В той же ленте были ещё шесть репозиториев с именами вроде Total-Commander, Acrobat-Pro-Software и Monero-Miner. GitHub удалил их раньше, чем я успел их измерить. Скорее всего, это был как раз такой тип, но проверять правило не на чем, поэтому правила нет;

  • вредоносный код внутри настоящего проекта. Фильтр читает только README и метаданные.

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

Бот просит DeepSeek пересказать каждый репозиторий одной фразой по-русски. В системном промпте прямо сказано не выдумывать. Я собрал 303 настоящих пересказа и сравнил их с источниками.

Иногда модель всё равно дописывала. Проект, у которого в описании было только meat.dev, она назвала «веб-фреймворком для Ruby». Инструмент для тестов сравнила с Cypress, которого в источнике нет. Всё это звучит правдоподобно, и глазами такое не ловится.

Идею проверки я взял у открытого проекта KKKKhazix/AIHOT (MIT): функция grounded() у них проверяет обзоры недельных и месячных выпусков. Правило простое. Каждое слово латиницей с заглавной буквой или цифрой (Ruby, GPT-5) и каждое число из трёх и больше цифр, с точкой или процентом должно встречаться в источнике. Если нет, бот показывает исходное описание с GitHub.

Первая версия отбраковала 9 пересказов из 303. Четыре из них были разным написанием одного и того же: Open-source и «open source», Wi-Fi и «WiFi», 2234 и «2,234». Я добавил сравнение без дефисов и пробелов и убрал разделители тысяч. Осталось 5 отказов. Три из них настоящие выдумки: Ruby, Cypress и Scratch, которых в источнике нет. Два других по сути верны, но источник их не подтверждает: NVIDIA для репозитория NVlabs и «XIX век» вместо «1800s». Проверка отбрасывает всё, что не может подтвердить, и я считаю это правильным для ленты, где пересказ можно заменить исходным описанием.

В первый же день в бою проверка отклонила правильный пересказ: в источнике Baidu был записан только иероглифами, 百度, а модель написала «Baidu». Теперь есть короткая таблица китайских названий компаний. За следующие четыре дня было 20 пересказов и ни одного отказа.

Ограничения те же, что у любой проверки по словам: она не видит утверждений («вдвое быстрее Redis» пройдёт, если Redis есть в источнике), выдумок строчными буквами и чисел из одной-двух цифр.

Где взять

Оба инструмента лежат отдельными репозиториями. Это один файл на Python, только стандартная библиотека, лицензия MIT, тесты на реальных случаях. Ловушка в тестах лежит в обезвреженном виде, все адреса заменены.

  • lure-check: python3 lure_check.py owner/repo, код возврата 1 для ловушки, удобно в CI;

  • ground-check: python3 ground_check.py --source source.txt --text summary.txt.

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

Автор: Aleksandr_NFA

Источник