Семь ошибок, которые ИИ пишет уверенно: тренировка код-ревью

Кирилл попросил агента написать обработчик оформления заказа. Через две минуты получил аккуратный код: понятные имена, ровные отступы, даже комментарии. Запустил, нажал «оформить», заказ создался. Кирилл довольно закрыл ноутбук. А через месяц выяснилось, что один из покупателей оформил заказ с отрицательным количеством товара, и магазин сам доплатил ему за покупку.
Код нормально работал на демо. Просто на демо никто не вводил отрицательные числа. Эта статья про то, как читать такой код, чтобы находить подобное до продакшена, и почему нейросеть так уверенно пишет то, что опасно.
Почему «правдоподобный» не равно «верный»
Модель учится на огромном количестве кода, и в нём встречается всё подряд. Учебные примеры, где ключ написан прямо в файле «для простоты». Примеры из документации, где запрос собран склейкой строк, потому что так короче. Код из старых проектов без валидации. Когда агент пишет новый обработчик, он воспроизводит то, что обычно встречается в таких местах, а не то, что безопасно.
Это объясняет странную закономерность: ошибки в сгенерированном коде не случайны. Они повторяются из раза в раз, потому что принадлежат к одним и тем же «популярным» образцам. А значит, их можно научиться искать, как врач учится узнавать типичные симптомы.
Для этого достаточно держать в голове пять вопросов при чтении любой строки, которая принимает или отдаёт данные:
Путь запроса: где проходят границы доверия
Вот путь заказа от покупателя до платежа. Обратите внимание, где данные пересекают границу: до неё им верить нельзя, после неё можно только то, что сервер проверил сам.
Тренировка: найдите семь проблем
Теперь вы проверяющий. Ниже настоящий по духу обработчик заказа, который «написал ИИ». На нём семь проблем разной тяжести. Нажимайте на строки, которые не пустили бы в продакшен. За ложные срабатывания баллы снимаются, поэтому отмечайте только то, в чём уверены.
Результат всегда немного обескураживает. Даже опытные разработчики находят три-четыре проблемы из семи, потому что внимание цепляется за самые заметные (ключ в коде), а менее очевидные пролетают. Разберём их по порядку, с примерами «как было» и «как надо».
Ошибки 1 и 2: доверие к клиенту и SQL
Самая крупная семья проблем: сервер верит тому, что прислал клиент. Идентификатор пользователя берётся из тела запроса, и кто угодно может подставить чужой. Запрос в базу собран склейкой строк, значит, туда можно подставить свой SQL.
Тут стоит задержаться на причине, потому что она та же, что и в инъекциях в промпты: данные смешали с командами. В SQL это решено давно (параметры запроса), и пренебрегать этим решением нельзя.
Ошибки 3 и 4: количество и промокод
Каждое значение, которое влияет на деньги, сервер должен проверять, а итоговую сумму считать сам. Количество «минус пять» даёт отрицательную сумму. Любая непустая строка в поле «промокод» выдаёт скидку. Если бы цену присылал клиент, её подменил бы любой, но и без этого хватает дыр.
Ошибка 5: запрос в цикле
Эта ошибка самая «тихая»: код правильный, тесты зелёные, а проблема проявится только при росте. В цикле по товарам корзины на каждую позицию делается отдельное обращение к базе. Подвигайте ползунок и посмотрите, что происходит с числом запросов и временем.
Лечится это просто: достать все нужные цены одним запросом со списком идентификаторов. Но заметить проблему можно только, если читать код с вопросом «а что будет, когда позиций станет много?».
Ошибки 6 и 7: секреты и логи
Последняя пара про то, куда утекает секретное. Платёжный ключ лежит прямо в коде: он уйдёт в репозиторий и останется в его истории, даже если строку потом удалить. А в лог пишется весь объект пользователя вместе с платёжным токеном, и теперь эти данные читаются всеми, у кого есть доступ к логам.
Отдельное предупреждение про ключи: удалить строку недостаточно. Если ключ хоть раз попал в репозиторий, его нужно отозвать и выпустить новый. История хранит всё.
Где вы это встречаете. Каждый раз, когда принимаете pull request от агента или подрядчика. Самые частые находки в таком ревью почти всегда из этого списка: ключи, доверие к клиенту, права и персональные данные в логах. Смысл тренировки в том, чтобы глаз сам цеплялся за эти места.
В каком порядке читать pull request
Когда агент присылает изменения на сотню строк, главная опасность в том, что читать их начинают с первой строки и устают к середине. Профессионалы делают иначе: сначала самое дорогое, потом остальное.
- Начните с границ. Найдите, где код принимает данные снаружи (обработчики запросов, формы, вебхуки) и где отдаёт их наружу (ответы, письма, логи). Все серьёзные проблемы живут там.
- Проверьте права. Для каждого обработчика спросите, кто может его вызвать и что он при этом получит. Отдельно проверьте, не берётся ли идентификатор из запроса.
- Найдите деньги. Любое место, где считается сумма или меняется баланс, читайте как бухгалтер: откуда каждое число, что на краях.
- Поищите секреты. Строки, похожие на ключи и пароли, подключения к базам, токены. Для этого есть автоматические сканеры, но глазами пробежаться полезно.
- Только потом читайте остальное. Структура, названия, стиль. Это важно, но не опасно.
- Посмотрите, что изменили в тестах. Если агент «починил» падающий тест, изменив сам тест, проверьте, не подогнал ли он проверку под код.
Последний пункт заслуживает отдельного внимания. Агентов, которым поручено «сделать так, чтобы тесты проходили», иногда тянет к самому короткому пути: подправить ожидание в тесте или обойти проверку. Поэтому изменения в тестах читают особенно придирчиво.
Может ли ИИ проверять ИИ
Может, и это полезно, только в правильной роли. Второй агент, которому поручено «найди проблемы безопасности в этом изменении», действительно находит часть ошибок: секреты, очевидные инъекции, пропущенные проверки. Он быстр, не устаёт и не стесняется критиковать.
Но у него есть три ограничения. Во-первых, слепые зоны у моделей похожи: то, что пропустил писавший, часто пропустит и проверяющий. Во-вторых, он не знает вашей бизнес-логики: ему не видно, что «минус пять» для вашего магазина недопустимо. В-третьих, он склонен быть слишком вежливым и принимать правдоподобное за верное. Поэтому ИИ-ревью хорошо как дополнительный слой перед человеком, но не вместо него и не вместо тестов.
Что поймает автоматика, а что только человек
Полезно заранее понимать, кто из ваших «ревьюеров» на что способен.
- Секрет в коде. Автоматика: сканер найдёт любой ключ за секунды. Человек может пропустить.
- SQL-инъекция. Автоматика: хорошие анализаторы находят типовые случаи. Человек: ловит нетиповые, где запрос собирается в нескольких местах.
- Доверие к клиентским данным и права. Автоматика: плохо, потому что для неё «userId из запроса» выглядит нормальным кодом. Человек и тесты на чужой идентификатор: основная защита.
- Логика денег. Автоматика: только тесты, которые вы написали заранее. Человек: понимает, что отрицательное количество недопустимо именно для вашего магазина.
- Запрос в цикле. Автоматика: иногда. Человек и профилирование: чаще всего.
- Персональные данные в логах. Автоматика: можно настроить правила. Человек: знает, что именно у вас считается персональными данными.
Вывод: автоматика закрывает механические и повторяющиеся ошибки, а смысловые ошибки остаются людям. Поэтому конвейер без ревью опасен, а ревью без конвейера утомительно.
Как поручить агенту задачу, чтобы таких ошибок было меньше
Многих проблем можно избежать, не дожидаясь ревью. Если записать правила в файл инструкций для агента (о нём мы писали в статье про окно контекста), он будет следовать им каждый раз:
- запросы в базу только параметризованные, склейка строк запрещена;
- данные из запроса клиента считаются недоверенными и валидируются на входе;
- идентификатор пользователя берётся из сессии, а не из тела запроса;
- секреты только из переменных окружения, в коде и логах их быть не может;
- перед завершением задачи запусти линтер, проверку типов и тесты.
Это не отменяет ревью, но снижает долю «глупых» ошибок и экономит время на то, что действительно требует головы.
Как встроить это в процесс
Ревью «на глаз» полезно, но уставать оно будет раньше, чем код закончится. Поэтому его поддерживают тремя слоями.
- Автоматика на каждый коммит. Линтер, проверка типов, сканер секретов и уязвимых зависимостей. Это ловит механику без участия человека: ключ в коде найдёт сканер раньше любого ревьюера.
- Тесты на «а если придёт неправильное». Для денег и прав доступа тесты пишутся на плохие значения: отрицательное количество, чужой идентификатор, пустой промокод. Если бы у Кирилла был такой тест, баг с минусовой суммой не дожил бы до продакшена.
- Человек. Сеньор читает каждый PR, держа в голове пять вопросов из начала статьи.
Как собрать минимальный конвейер из этих слоёв, мы разобрали в статье про CI для кода от ИИ. А если вы принимаете работу у подрядчика, возьмите чек-лист приёмки: он про то же, но с позиции заказчика. Что бывает, когда проверок нет, показано в статье про технический долг.
Источники и что почитать
- OWASP Top 10 — перечень самых частых классов уязвимостей веб-приложений.
- OWASP: SQL Injection — что это и как защищаться.
- Anthropic: Claude Code best practices — как организовать проверку работы агента.
Пишу из практики студии MKDGRUPP: каждый pull request проверяет сеньор, а автоматика и тесты закрывают механическую часть. Подробнее: mkdgrupp.ru/uslugi/web.
Частые вопросы
- Почему код от ИИ нужно ревьюить, если он работает?
- Работающий на демо код может содержать уязвимости и скрытые ошибки, которые проявляются на реальных данных и нагрузке. Нейросеть генерирует правдоподобное, а не проверенное.
- Что чаще всего пропускают при приёмке такого кода?
- Секреты в исходниках, доверие к входным данным, отсутствие проверки прав доступа, запросы к базе в цикле и персональные данные в логах.
- Может ли один ИИ проверять код, написанный другим ИИ?
- Это полезный дополнительный слой, он находит часть проблем. Но он не заменяет автотесты и ревью человека, потому что у моделей похожие слепые зоны.



