Отчёт по безопасности на сорок одну находку выглядел как результат работы платного сканера: категории, уровни критичности, аккуратные формулировки. Разбор занял три часа. Настоящих проблем в нём оказалось семь.
Двадцать две находки были ложными срабатываниями, ещё двенадцать — верными наблюдениями, которые в этом проекте проблемой не являются. Точность семнадцать процентов. Проверяющая задача, поставленная широко, производит работу вместо того, чтобы её экономить.
Почему широкая проверка даёт мусор
Механика та же, что и в задачах на чтение кода, только последствия дороже.
«Найди уязвимости» не имеет условия завершения. Пустой ответ выглядит как плохая работа, поэтому находки будут всегда — вопрос лишь в том, откуда они возьмутся. Берутся они из трёх мест, и все три я видел в одном отчёте.
Отсутствие доказательства принимается за доказательство отсутствия. «Не найдено ограничение частоты запросов на маршруте входа» — оно есть, просто в файле, который не открывали. Для аудита это худший тип ошибки: каждую такую находку приходится проверять с нуля.
Общее знание выдаётся за наблюдение. Пункты про заголовки безопасности и политику содержимого написаны так, будто их проверили. Проверяли не их, а типичный проект на этом стеке.
Придуманные ссылки. Две находки указывали на строки, которых в файлах нет. Одна — на несуществующий метод.
Сложить это в кучу нельзя: сорок одна находка без разметки по достоверности — это сорок одна задача на проверку.
Одна гипотеза за проход
Работает противоположная постановка. Не «найди всё», а «проверь вот это».
Угроза: пользователь одной организации получает доступ к данным другой.
Проверь ТОЛЬКО эту угрозу. Пройди по всем контроллерам в src/Module/*/
и для каждого действия заполни строку таблицы: маршрут, где проверяется
принадлежность к организации (file:line), проверяется ли вообще.
Не предлагай улучшений. Не проверяй другие угрозы.Восемь кандидатов вместо сорока одного, из них три настоящие. Сорок минут разбора вместо трёх часов.
| Постановка | Находок | Настоящих | Точность | Мой разбор |
|---|---|---|---|---|
| «Найди уязвимости» | 41 | 7 | 17% | 3 ч |
| Одна угроза, таблица | 8 | 3 | 38% | 40 мин |
Второй заход искал одну угрозу и в абсолюте нашёл меньше. Зато его результат можно было использовать сразу.
Точность выросла вдвое не из-за формулировки как таковой. Причина в том, что у узкой задачи появляется полнота: пройти все контроллеры и заполнить строку на каждый — проверяемая работа. У широкой задачи полноты нет, есть только объём.
Таблица вместо прозы
Форма ответа меняет содержание сильнее, чем кажется.
Проза скрывает пробелы. В абзаце «в большинстве контроллеров проверка организации выполняется корректно» дыра невидима, и слово «большинство» её маскирует. Таблица со строкой на каждое действие показывает дыру пустой ячейкой.
Побочный эффект: таблица со всеми маршрутами и указанием места проверки оказалась ценнее самих находок. Я её сохранил и использую как карту прав доступа — она отвечает на вопрос «а где вообще проверяется доступ» лучше, чем любая документация, которая у нас была.
Требования к таблице у меня всегда одни и те же: колонка с file:line, колонка со статусом из закрытого списка (есть / нет / не найдено), никаких свободных формулировок в статусе. Свободная формулировка — это способ написать «частично», а «частично» непроверяемо.
Запрет на рекомендации
Отдельная строка, которая экономит половину объёма отчёта: не предлагать улучшений.
Рекомендации в проверяющем отчёте выглядят как забота, а работают как шум. Они не проверены, не привязаны к контексту проекта и удлиняют текст вдвое. Хуже того, они перемешаны с находками, и при чтении приходится каждый раз решать, что перед тобой — обнаруженная проблема или общее соображение о том, как надо.
Разделение простое: сначала факты со ссылками, решения принимаю я. Если по итогам разбора мне понадобится вариант исправления, я попрошу его отдельной задачей, уже зная контекст.
Поиск причины: «объясни» до «почини»
Тот же принцип на другом классе задач — красный тест, красная сборка, странное поведение на проде.
Формулировка «сделай зелёным» опасна тем, что у неё есть простые решения, не связанные с причиной. Ожидание побольше, повтор попытки, ослабленное утверждение, пропуск теста, запись в базовую линию анализа. Формально задача выполнена: сборка зелёная.
Помогает разделить диагностику и починку явно:
Не чини. Объясни, почему падает.
- Что именно ожидалось и что произошло, с выводом теста.
- Какая строка кода отвечает за расхождение (file:line).
- Причина: гонка, порядок тестов, зависимость от времени, общее состояние,
реальный дефект логики.
- Как проверить твою версию, не исправляя код.
Остановись после объяснения.Последний пункт — самый полезный. Ответ «версию можно проверить, запустив только эти два теста в таком порядке» проверяется за минуту и либо подтверждается, либо нет. Без него у меня остаётся правдоподобная история без доказательства.
На трёх падениях подряд диагностика дала три разные причины, и только одна оказалась дефектом логики. Остальные две — общее состояние между тестами и повторяющаяся миграция. Если бы я просил чинить, я бы получил три правки, каждая из которых убирает симптом.
Плавающие тесты
Отдельный случай, где формулировка «почини» гарантированно даёт неправильный результат.
Плавающий тест соблазнительно отключить, и это решение выглядит взрослым: он же нестабильный, он мешает всем, разберёмся потом. «Потом» не наступает. Я проверил на своём проекте — нашёл два пропущенных теста с комментариями двухлетней давности, оба скрывали настоящие гонки.
Постановка для плавающего теста у меня теперь такая: воспроизвести нестабильность (запустить в цикле, поменять порядок, зафиксировать время), назвать источник недетерминированности, и только потом решать. Решение о пропуске принимаю я и записываю причину в код, а не в комментарий вида «иногда падает».
Что осталось
Три приёма переносятся между всеми проверяющими задачами: одна гипотеза за проход, таблица вместо прозы, запрет на рекомендации. Четвёртый — «объясни до того, как чинить» — работает только там, где есть воспроизводимый симптом.
Чего эта постановка не даёт — полноты. Проверка одной угрозы находит одну угрозу. Чтобы закрыть десять, нужно десять проходов, и это честная цена: десять узких проходов дают меньше находок и больше настоящих, чем один широкий.
И то, что не лечится постановкой вообще. Часть проблем не находится чтением кода ни при какой формулировке: всё, что зависит от параллельности, времени, объёма данных и поведения под нагрузкой. В том аудите таких было две из девяти. Двадцать два процента задачи остаются человеку по её природе, и знать это полезнее, чем ещё раз переписать промпт.