Сегодня Telegram-бот для продажи VPN собирается за вечер. Открываешь Cursor, пишешь «сделай бота с оплатой в Stars и панелью Marzban», и через пару часов он уже принимает деньги. Только в config.py лежит токен бота, в docker-compose.yml открыт Postgres на 0.0.0.0:5432, а в истории git остался .env, который «потом удалили».

Мне стало интересно, насколько всё плохо на самом деле. Я написал свой SAST-сканер для кода, сгенерированного ИИ, и прогнал его по 3 800 публичным репозиториям: от проектов с тремя звёздами до платформ с десятками тысяч.

Цифры коротко:

Репозиториев просканировано

~3 800

Боевых Telegram-ботов (VPN, магазины, Stars)

471

Сырых находок high/critical на этих ботах в первой версии

16 378

Из них оказались настоящими утечками

~30

Правил в сканере

21

Регрессионных тестов

193

Внешних зависимостей

0

Чужих ключей, которые я проверил или использовал

0

Последняя строка самая важная, к ней я ещё вернусь. А разница между 16 378 и 30 — это, по сути, вся история проекта: как научить сканер находить настоящие дыры и молчать про всё остальное.

Что такое AigisSAST

AigisSAST — консольный сканер безопасности для кода, написанного с ИИ. Около 3 000 строк на чистом Python, без единой зависимости и без облака: код никуда не уходит.

pip install aigis-sast
aigis scan

Внутри:

  • Python разбирается по AST, а не grep’ом. Поэтому cursor.execute(f"... {user_id}") отличается от безобидной f-строки в логгере.

  • 21 правило под типичные ошибки ИИ: токены (OpenAI, Telegram, GitHub, AWS, Stripe и др.), пароли, строки подключения к базам, закоммиченный .env, SQL- и shell-инъекции, eval, pickle, JWT без проверки подписи, CORS *, debug=True, verify=False, токены через random, FastAPI-ручки без авторизации, XSS, секреты в VITE_*/NEXT_PUBLIC_*, root в Docker, открытые порты баз.

  • Каждая находка объясняется по-человечески: чем опасно, как исправить, готовый код.

  • Секреты маскируются (7312************), чтобы ключ не утёк второй раз через логи CI.

  • Русский и английский: язык берётся из системы, переключается через --lang en.

▌ bot/config.py
   CRITICAL  AIG001  Токен или API-ключ прямо в коде (Telegram bot token)
           bot/config.py:12
           ↗ https://github.com/you/bot/blob/3f2c1ab.../bot/config.py#L12
           │ BOT_TOKEN = "7312************"
           чем опасно:
             Любой, кто увидит репозиторий, получит доступ к твоему боту...
           как исправить:
             Отзови токен у @BotFather прямо сейчас и вынеси его в .env...
────────────────────────────────────────────────────────────────
  ■ critical 1  ■ high 3  □ medium 0  ■ low 2
  оценка безопасности: 43/100   уровень  D
  начни с: AIG001 bot/config.py:12

У каждой находки есть кликабельная ссылка на файл и строку на GitHub, GitLab или Bitbucket, так что до нужного места один клик.

Он ещё и чинит

aigis fix --dry-run   # показать diff
aigis fix             # применить

Выносит ключи в .env и .env.example, дописывает .env в .gitignore, меняет verify=False на True, yaml.load на safe_load, random на secrets для токенов, выключает debug, прячет порты баз за 127.0.0.1 и сам добавляет нужные import. SQL-инъекции и JWT автоматически не трогает: там без понимания логики приложения можно только сломать.

«Я же удалил ключ»

Самая частая история: человек заметил токен, удалил его следующим коммитом и успокоился. Но старая версия файла живёт в истории git, и её видит каждый, кто сделал git clone.

$ aigis history
  [CRITICAL] AIG001  7312************  (Telegram bot token)
      bot.py  ·  коммит 402c92b от 2026-09-14  ·  удалён, но остался в истории
      ↗ https://github.com/you/bot/commit/402c92b...

В CI подключается одной командой aigis init: находки попадают во вкладку Security → Code scanning и прямо в diff пулл-реквеста.

Раунд 1: 126 вайбкод-проектов и 2 012 «паролей»

Начал я с репозиториев с явными следами ИИ: .cursorrules, CLAUDE.md, «vibe coded», «built with Cursor/Bolt/Lovable». Медиана три звезды, обычные pet-проекты.

Первый прогон получился позорным: 2 012 захардкоженных паролей. Я сел разбирать их руками, и почти всё оказалось мусором: тестовые пароли, хеши в lock-файлах, your-key-here, ${DB_PASSWORD}, переменные вроде hashed_password и ACCESS_TOKEN_EXPIRE_MINUTES, переводы, где password значит «Пароль».

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

Раунд 2: 49 гигантов open source

Потом я пошёл к взрослым: 49 популярных ИИ-платформ, low-code-сервисов и админок с тысячами звёзд.

Правило

Было

Стало

Все находки

3 709

1 422

AIG002 пароли

1 525

392

AIG003 URL базы с паролем

276

4

AIG021 FastAPI без авторизации

299

71

AIG001 токены известных форматов

164

28

Почти все 28 оставшихся «токенов» оказались тестовыми и примерными. Крупные проекты за секретами следят. Отсюда же вылезла проблема оценки: в огромной кодовой базе даже аккуратный проект набирает сотни находок и получает F. Пересчёт с поправкой на размер стоит у меня в планах.

Раунд 3: 2 292 маленьких проекта — боты, ИИ, VPN

Дальше были маленькие свежие проекты: Telegram-боты, ИИ-приложения, сайты, VPN-боты и панели (Marzban, 3x-ui, WireGuard, Xray). Сканировались и текущий код, и вся история git.

После ручного отсева осталось около 50 проектов с доступами, похожими на настоящие: ключ Google service account вместе с .env, пароли от облачных MongoDB и TiDB, закоммиченные .env у VPN-ботов, десятки токенов ботов прямо в коде, OpenAI- и Google-ключи.

Раунд 4: 471 бот, который приносит деньги

Учебные проекты мне были уже неинтересны. Я искал ботов, которыми реально пользуются: VPN-магазины, магазины цифровых товаров, боты с оплатой в Stars, CryptoBot и ЮKassa. Фильтр: не форк, коммиты за последний год, минимум два признака живого использования (ссылка на работающего бота, подключённая оплата, деплой, админка), никаких tutorial/template.

Шаг

Репозиториев

Кандидатов из 41 поискового запроса

1 514

Прошли фильтр «боевой бот»

473

Просканировано (код + история git)

471

Похожие на настоящие утечки после ручного разбора

31

— секрет лежит в коде прямо сейчас

10

— только в истории git

22

Что нашлось: токены ботов прямо в коде, токен CryptoBot вместе с адресом и паролем панели 3X-UI в «примере» .env, GitHub-токен в истории, дамп конфига VPN-панели с приватными ключами. И популярная open-source панель, где токен бота зашит в исходники, поэтому он уезжает каждому, кто её ставит.

Самое ценное в этом раунде было не про чужих ботов, а про мой сканер. 16 378 находок, из них настоящих около 30. Шум давали:

  • переводы в VPN-панелях: "newPassword" = "Nueva Contraseña" — это подпись к полю;

  • вендорный vue.js, где emptySlotScopeToken = "_empty_" похож на токен только по имени;

  • example-значения в OpenAPI ("password": "secret");

  • тесты, шаблоны .env.j2, тестовые PEM-ключи, postgres://postgres:postgres@localhost.

Нашёлся и настоящий баг: aigis history держал весь git log -p в памяти, падал по OOM на больших историях и при этом молча писал пустой отчёт с кодом 0. Для сканера безопасности хуже бага не придумаешь: семь репозиториев чуть не записались в «чистые».

Я исправил всё. История теперь читается потоком, а при сбое git сканер честно падает с кодом 2. На одной популярной VPN-панели было 37 находок, стало 3, на другой было 29, стало 7. Настоящий зашитый токен бота при этом в отчёте остался, ради этого всё и делалось.

Раунд 5: 879 проектов вслепую

Последняя проверка самая честная: я прогнал свежую версию ещё по 879 проектам, которые сканер раньше не видел и под которые я его не подгонял. Находок снова хватило: токены ботов и API-ключи в коде и в истории git, закоммиченные .env, строки подключения к базам с паролем.

Названий проектов в статье нет ни в одном раунде, и это сделано специально.

Почему я не проверял ни одного ключа

Меня постоянно спрашивали: «Почему бы не дёрнуть getMe у Telegram и не узнать, живой ли токен?»

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

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

Что я понял

  1. ИИ ошибается не случайно, а по шаблону. Секреты в коде, f-строки в SQL, CORS *, debug=True, ручки без Depends(...) — одни и те же ошибки в тысячах проектов.

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

  3. Удалённый ключ — не удалённый ключ. Попал в публичный репо, значит отзывай, а не удаляй коммитом.

  4. Тихий сбой хуже ложной тревоги. Сканер, который упал и сказал «всё чисто», опаснее, чем отсутствие сканера.

Попробовать

Всё описанное выше есть в версии 0.5.0:

pip install aigis-sast
aigis help      # шпаргалка по всем командам
aigis scan      # текущий код
aigis history   # секреты в истории git
aigis fix       # автоисправление
aigis init      # подключить к GitHub Actions

Код открыт под MIT: https://github.com/jasperBLCK/AigisSAST. Если сканер ошибся на вашем проекте в любую сторону, заведите issue с примером: каждое ложное срабатывание у меня превращается в регрессионный тест.

Проект я буду развивать дальше: новые правила, меньше ложных срабатываний, поддержка других языков. Буду рад любой поддержке: звезда на GitHub, issue, PR или просто отзыв.

И вопрос к вам: насколько полезной показалась разработка и поставили бы вы её в свой CI? Напишите в комментариях, мне правда важно.