Самое важное из мира AI — в канале MAX AgentLabОтдельные разборы, инструменты и практические схемы Читайте Agent Lab в Telegram Разборы, кейсы и новости о практических AI-агентах без лишней воды.

AI-ревью безопасности: пилот на одном репозитории

AI-ревью безопасности: пилот на одном репозитории
Temporary fallback cover; replace in editorial pass.

Уровень: продвинутый · Чтение: до 12 минут · Опубликовано:

Почти каждая команда, которая пробует AI-анализ безопасности, упирается в один и тот же вопрос: модель действительно нашла уязвимость или просто сгенерировала правдоподобное предупреждение? Ответ «вроде полезно» не годится ни для решения о внедрении, ни для бюджета. Нужен эксперимент с заранее заданными метриками, жёсткими границами и честной разметкой результатов.

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

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

Что именно мы проверяем

Пилот отвечает на один узкий вопрос: окупается ли время инженера, потраченное на разбор AI-находок, на этом конкретном репозитории? Поэтому в рамки пилота не входят:

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

Шаг 1. Выберите репозиторий и зафиксируйте снимок

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

Зафиксируйте конкретный коммит, чтобы все прогоны шли по одному и тому же коду:

# Отдельная рабочая копия только для пилота
git clone --no-tags https://git.example.internal/team/service.git pilot-src
cd pilot-src
git checkout --detach <SHA-зафиксированного-коммита>
git rev-parse HEAD > ../pilot/snapshot.sha

# Проверка, что в рабочей копии нет незакоммиченных изменений
git status --porcelain | wc -l   # ожидается 0

Адрес репозитория в примере условный — подставьте свой. Перед передачей кода агенту прогоните ваш штатный сканер секретов и удалите из копии всё, что не должно покидать периметр (файлы .env, дампы, ключи в фикстурах).

Шаг 2. Сделайте окружение по-настоящему read-only

«Агенту сказали ничего не менять» — это не ограничение. Ограничение — это права файловой системы и отсутствие сети. Минимальный вариант на контейнерах:

docker run --rm -it \
  --network none \
  --read-only \
  --tmpfs /tmp:rw,size=256m \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  -v "$PWD/pilot-src:/src:ro" \
  -v "$PWD/pilot/out:/out:rw" \
  <образ-с-вашим-агентом>

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

Проверьте ограничения до начала пилота, а не после:

# Внутри контейнера — обе команды должны завершиться ошибкой
touch /src/probe.txt
curl -sS --max-time 5 https://example.com

Шаг 3. Зафиксируйте задание и формат ответа

Свободный текст «вот что я нашёл» невозможно посчитать. Задайте агенту строгий формат: одна находка — одна запись. Пример схемы:

{
  "id": "F-001",
  "category": "injection | authz | secrets | crypto | deserialization | other",
  "file": "path/to/file",
  "lines": "120-134",
  "claim": "Что именно уязвимо и почему",
  "exploit_path": "Как внешний вход достигает этого места",
  "severity_claimed": "low | medium | high | critical",
  "regression_test": "Код теста, который падает на текущем коде и проходит после исправления"
}

Поле exploit_path — ключевое. Находка без пути от входа к уязвимому месту почти всегда оказывается теоретической. Требование описать путь заметно упрощает разметку.

Сохраните промпт, версию модели, параметры и конфигурацию агента рядом со снимком коммита. Без этого повторный прогон через месяц ничего не скажет.

pilot/
  snapshot.sha
  prompt.md
  agent-config.json     # модель, температура, лимиты, версия обвязки
  out/
    run-1/findings.jsonl
    run-2/findings.jsonl
  triage.csv

Сделайте как минимум два прогона на одном снимке. Расхождение между ними — самостоятельная метрика: если половина находок появляется только в одном из прогонов, на однократный запуск в CI полагаться нельзя.

Шаг 4. Разметка: правила до первой находки

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

СтатусКритерий
ПодтвержденаВоспроизведена тестом или ручной проверкой; путь от внешнего входа реален
Подтверждена, но не эксплуатируемаДефект есть, но вход контролирует только доверенная сторона; полезна как hardening
Ложное срабатываниеУтверждение неверно: санитизация есть, путь не существует, код недостижим
ДубликатТа же первопричина, что у другой находки
Не определеноНужен эксперт или окружение, которого нет в пилоте; не считается ни в плюс, ни в минус

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

Для каждой записи фиксируйте время проверки — от открытия находки до принятия решения. Формат triage.csv:

id,run,status,reviewer,minutes,severity_confirmed,test_quality,notes
F-001,1,confirmed,rev-a,14,medium,2,"тест падает до фикса"
F-002,1,false_positive,rev-a,6,,0,"есть экранирование выше по стеку"

Строки выше — иллюстрация формата, а не реальные данные.

Шаг 5. Оценка регрессионных тестов

Предложенный тест ценен, только если он ловит именно заявленный дефект. Используйте простую шкалу от 0 до 3:

Тесты запускайте в отдельной копии, а не в read-only снимке, и тоже без сети:

git worktree add ../pilot-tests "$(cat ../pilot/snapshot.sha)"
cd ../pilot-tests
# Положите тест из findings.jsonl в каталог тестов, затем:
docker run --rm --network none -v "$PWD:/w" -w /w <образ-для-тестов> \
  <команда-запуска-одного-теста>
# Ожидаемо: тест ПАДАЕТ на исходном коде

# После ручного минимального исправления — тот же запуск, тест должен ПРОЙТИ
git -C ../pilot-tests diff --stat   # убедитесь, что изменён только нужный файл

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

Шаг 6. Подсчёт метрик

Когда разметка закончена, посчитайте сводку. Пример для triage.csv с помощью стандартных утилит (предполагается, что в полях нет запятых внутри кавычек; для реальных данных лучше использовать скрипт на Python с модулем csv):

# Распределение по статусам
cut -d, -f3 triage.csv | tail -n +2 | sort | uniq -c

# Суммарное время разбора, минуты
awk -F, 'NR>1 {s+=$5} END {print s}' triage.csv

# Средняя оценка тестов среди подтверждённых
awk -F, 'NR>1 && $3=="confirmed" {s+=$7; n++} END {if (n) print s/n}' triage.csv

Из этих чисел складываются показатели, на которые стоит смотреть:

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

Проверка результата

Пилот можно считать корректным, если выполняются все условия:

Типовые ошибки

Ограничения подхода

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

Протокол плохо измеряет пропуски: мы видим, что агент нашёл, но не знаем, что он не нашёл. Частично это компенсируется сравнением с уже известными проблемами из трекера и выводами SAST, но полной полноты (recall) так не получить.

Классы уязвимостей, требующие контекста за пределами репозитория — конфигурация инфраструктуры, бизнес-логика нескольких сервисов, поведение в рантайме, — в read-only анализе одного репозитория почти не видны. Низкий результат по ним — ограничение постановки, а не обязательно слабость модели.

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

Что дальше

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

Больше практических протоколов — в разделе гайдов, определения терминов — в глоссарии.