Правила код-ревью по безопасности для команд разработки: чек-листы по языкам и типовым ошибкам

Изображение: recraft
Код-ревью (от англ. code review – обзор кода) – это процесс проверки исходного кода программного обеспечения другими разработчиками (не автором) с целью поиска ошибок, приведения кода к единым стандартам и обмена опытом. Хотя код-ревью обычно считают средством улучшения стиля и качества кода, сегодня это один из мощнейших способов предотвращения угроз. Системный анализ безопасности на этапе проверки позволяет устранить большинство критических уязвимостей еще до того, как код попадет в рабочую среду.
Почему обычного код-ревью недостаточно?
Во многих командах ревью ограничивается проверкой логики, читаемости и производительности. Безопасность при этом рассматривается либо фрагментарно, либо полностью перекладывается на автоматические сканеры. Такой подход приводит к типовым проблемам:
- уязвимости бизнес-логики не обнаруживаются SAST-инструментами;
- небезопасные паттерны использования сторонних библиотек проходят ревью как «рабочие»;
- ошибки конфигурации и обработки данных остаются незамеченными.
Эффективное код-ревью по безопасности требует формализованных правил и чек-листов, адаптированных под конкретный язык и стек технологий.
Универсальный чек-лист безопасности
Независимо от языка разработки, при ревью кода следует проверять:
Работу с входными/выходными данными:
- происходит ли приведение данных к безопасному, единому формату (нормализация) с последующей валидацией;
- используются ли «белые списки» (allow-list) вместо «черных списков» (black-list);
- отсутствует ли слепое доверие данным клиента;
- безопасна ли десериализация структурированных данных (JSON/XML/YAML) на предмет выполнения кода на сервере;
- применяются ли prepared statements и плейсхолдеры для предотвращения SQL-инъекций при работе с БД;
- происходит ли экранирование данных перед их выводом в интерфейс пользователя с целью предотвращения XSS.
>> Любые данные, приходящие извне, по умолчанию считаются опасными и ненадежными.
Аутентификацию и авторизацию:
- проверяются ли права доступа на стороне сервера при каждом запросе, независимо от логики интерфейса;
- исключается ли доверие к ролям и правам, передаваемым в параметрах или флагах со стороны клиента;
- четко ли разграничены процессы идентификации, аутентификации и авторизации;
- ограничивается ли срок жизни сессий и токенов и защищены ли они от подделки и кражи из браузера;
- блокируются ли попытки массового перебора паролей и выдает ли система единую ошибку при неудачном входе;
- происходит ли полная и немедленная инвалидация серверной сессии при выходе пользователя.
>> Никогда не верь клиенту в вопросах доступа: личность должна быть подтверждена, а каждое действие – санкционировано сервером.
Обработку ошибок и логирование:
- не раскрываются ли детали реализации (стек-трейсы, версии библиотек, структура БД) в сообщениях об ошибках;
- исключается ли попадание в логи секретов и конфиденциальных данных (паролей, токенов, ключей и ПДн);
- используется ли централизованная обработка исключений для обеспечения единообразия ответов и предотвращения необработанных сбоев;
- соблюдается ли достаточный уровень логирования событий безопасности (входы, отказы в доступе, изменение прав);
- проводится ли очистка (маскирование) данных в логах при необходимости зафиксировать сам факт передачи объекта, содержащего чувствительную информацию.
>> Пиши в логи только то, что поможет восстановить ход событий, не раскрывая секретов и деталей защиты.
Работу с секретами:
- отсутствуют ли в исходном коде любые жестко заданные (hardcoded) пароли, API-ключи, токены или сертификаты;
- загружаются ли секреты из специализированных хранилищ (vault) или защищенных переменных окружения;
- удалены ли из истории репозитория любые «временные» секреты, тестовые учетные данные и отладочные ключи доступа;
- используются ли файлы-игнорирования (например, .gitignore), чтобы исключить случайное попадание локальных файлов с секретами в общую ветку;
- предусмотрена ли возможность оперативной замены секретов без необходимости внесения изменений в сам программный код.
>> Пиши код так, будто завтра он окажется в открытом доступе, и в нем не останется ни одного ключа от целевой системы.
Типовые ошибки по языкам и платформам
Java / Kotlin
- использование небезопасных десериализаторов (ObjectInputStream, старые версии Jackson/Fastjson);
- игнорирование проверки цепочки сертификатов и использование самоподписанных TLS-сертификатов;
- сборка запросов через конкатенацию строк;
- использование предсказуемых генераторов случайных чисел (java.util.Random) в криптографии.
C / C++
- выход за границы буфера (buffer overflow) при работе с массивами;
- использование небезопасных функций без контроля длины: strcpy(), sprintf(), gets();
- отсутствие проверок возвращаемых значений системных вызовов и функций выделения памяти;
- использование после освобождения (use-after-free) и двойное освобождение памяти;
- целочисленное переполнение (integer overflow), приводящее к некорректному выделению буфера.
Python
- использование eval(), exec() и ast.literal_eval() для обработки данных пользователя;
- небезопасная загрузка сериализованных объектов через pickle, marshal или shelve;
- формирование команд shell через f-строки или конкатенацию (с использованием os.system, subprocess);
- отключение проверки TLS-сертификатов «для удобства» в библиотеках типа requests;
- выход за пределы директории (path traversal) при открытии файлов через open() без валидации пути.
JavaScript / TypeScript
- прямая вставка данных в DOM через innerHTML или dangerouslySetInnerHTML;
- отсутствие проверки схем JSON-ответов и входных данных в runtime;
- хранение секретов и JWT-токенов в localStorage;
- неправильная настройка CORS (Access-Control-Allow-Origin: * для приватных API).
Go
- игнорирование возвращаемых ошибок (if err != nil);
- утечки горутин (goroutine) из-за отсутствия контекста (context.Context) или таймаутов;
- доступ к общим ресурсам без использования мьютексов;
- чрезмерное использование пакета unsafe;
- использование io.ReadAll() на внешних потоках данных без ограничения размера.
Что сделать для внедрения код-ревью безопасности?
Введите минимальный чек-лист, обязательный для каждого ревью.
Назначьте Security Champion’а в команде, отвечающего за качество ревью в разрезе безопасности.
Интегрируйте SAST и SCA для автоматизации и возможности сконцентрироваться на логике и угрозах.
Используйте каждую выявленную проблему как повод для актуализации внутреннего чек-листа и проведения командного разбора.
Чисто там, где не сорят
Безопасность кода – не скучный надзор «безопасников», а общее дело всей команды. Когда правила проверки понятны, а под рукой есть удобный чек-лист, поиск уязвимостей превращается из рутины в полезную привычку. Чем раньше мы заметим «слабое звено», тем меньше правок придется вносить в последний момент. В конечном итоге, качественный и защищенный код – это прежде всего наш спокойный сон и доверие пользователей.
Развивайтесь, и пусть ваши билды всегда выдают exit code 0!
Автор: Лариса Карпан, старший специалист по безопасной разработке Angara Security.
