bandit: проверка кода Python на уязвимости и запуск в CI

Зависимости проверяет pip-audit, а ошибки в собственном коде ищет статический анализатор. Для Python самый простой вариант — bandit: он разбирает код без запуска и отмечает опасные конструкции (внедрение в SQL и команды, небезопасная десериализация, слабые хэши, отключённая проверка сертификатов, зашитые пароли). Ниже порядок внедрения и результаты проверки на тестовом файле.
Статья опирается на логику направления «Управление уязвимостями» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Проверка выполнена в временном виртуальном окружении (bandit 1.9.4), после неё всё удалено.
Что должно получиться
| Уровень | Что это значит на практике |
|---|---|
| 2. Гигиенический | Ошибки безопасности в собственном коде ищутся автоматически при каждой сборке, находки получают владельцев и сроки |
Критерии сформулированы нами по мотивам методики, это ориентир, а не оценка соответствия.
Тестовый файл с типовыми ошибками
Чтобы увидеть работу анализатора, используем небольшой файл src/app.py со специально допущенными ошибками (токен в нём вымышленный):
import hashlib
import pickle
import subprocess
import sqlite3
import yaml
API_TOKEN = "example-token-not-real"
def find_user(conn, name):
return conn.execute("SELECT * FROM users WHERE name = '%s'" % name).fetchall()
def ping(host):
return subprocess.run("ping -c 1 " + host, shell=True)
def load_config(text):
return yaml.load(text)
def load_session(blob):
return pickle.loads(blob)
def password_hash(p):
return hashlib.md5(p.encode()).hexdigest()
def fetch(requests, url):
return requests.get(url, verify=False)
Установка в отдельное окружение и запуск:
python3 -m venv /tmp/bv
/tmp/bv/bin/pip install bandit
/tmp/bv/bin/bandit -r src -c bandit.yaml
Результат проверки
Всего 18 строк кода и 10 находок: 3 высокой, 4 средней и 3 низкой серьёзности.
| Код | Что нашёл | Серьёзность | Как исправить |
|---|---|---|---|
| B602 | subprocess с shell=True и строкой от пользователя |
Высокая | Передавать список аргументов без shell: ["ping", "-c", "1", host] и проверять host |
| B324 | MD5 для паролей | Высокая | Для паролей: argon2, bcrypt или scrypt |
| B501 | verify=False в запросе |
Высокая | Убрать; при своём центре сертификации указать verify="/путь/ca.pem" |
| B608 | SQL собирается подстановкой строки | Средняя | Параметризованный запрос: execute("... WHERE name = ?", (name,)) |
| B506 | yaml.load без безопасного режима |
Средняя | yaml.safe_load |
| B301 | pickle.loads для данных извне |
Средняя | Использовать JSON или подписывать данные; не распаковывать чужой pickle |
| B113 | Запрос без таймаута | Средняя (уверенность низкая) | Указывать timeout= |
| B105 | Токен в коде | Низкая | Хранить в переменных окружения или хранилище секретов: секреты в git |
| B403, B404 | Сам факт импорта pickle и subprocess |
Низкая | Информационные, обычно шум |
Обратите внимание: зашитый в код токен получил низкую серьёзность, поэтому порог «средняя и выше» его пропустит. Поиск секретов лучше поручить отдельному инструменту, а не рассчитывать на bandit.
Пороги и запуск в конвейере
Если останавливать сборку по любой находке, команда быстро отключит проверку из-за шума. Практичный старт: падать на серьёзности и уверенности не ниже средней. Конфигурация и скрипт:
exclude_dirs: [tests, .venv]
skips: []
#!/usr/bin/env bash
# Проверка кода Python на типовые ошибки безопасности. Падает на находках средней и высокой серьёзности.
set -u
TARGET="${1:-.}"
bandit -r "$TARGET" -c bandit.yaml --severity-level medium --confidence-level medium -f txt
rc=$?
case $rc in
0) echo "bandit: находок средней и высокой серьёзности нет" ;;
1) echo "bandit: есть находки, сборку нужно остановить" ;;
*) echo "bandit: ошибка запуска (код $rc)" ;;
esac
exit $rc
Коды возврата: 0 — находок выше порога нет, 1 — есть находки, другие значения — ошибка запуска. В тесте скрипт вернул 1 и показал шесть находок (B113 с низкой уверенностью и низкие по серьёзности в этот список не попали), а на чистом файле вернул 0.
Подавление ложных срабатываний
Если срабатывание не применимо, добавьте в строку комментарий # nosec B501 с кодом теста. В проверке такая запись скрыла B501, а B113 в той же строке осталась, потому что подавляется только указанный код (в итоговой статистике это строка «skipped due to specifically being disabled»). Комментарий # nosec без кода скрывает всё в строке, так делать не стоит. Каждое подавление должно сопровождаться причиной и проверяться на ревью.
Порядок внедрения
- Запустите на всём проекте без порога, сохраните отчёт как исходное состояние (
-f jsonдля обработки). - Исправьте высокие находки в коде, доступном извне, затем средние.
- Включите порог в CI (средняя серьёзность и уверенность), чтобы новые ошибки не попадали в основную ветку.
- Оформите исключения:
# nosec КОДс комментарием о причине. - Исключите ненужные каталоги (тесты, виртуальное окружение) через
exclude_dirs. - Добавьте проверку зависимостей и секретов: pip-audit и поиск секретов.
- Пересматривайте результаты регулярно: сроки исправления берите из общего процесса.
Диагностика
| Симптом | Причина и что делать |
|---|---|
expected '<document start>' ... Error parsing file |
Ключ -c ждёт файл в формате YAML. Файл .bandit в формате INI так не читается; пишите настройки в bandit.yaml |
| В отчёте сотни находок в чужом коде | Проверяется виртуальное окружение внутри каталога. Укажите целевой каталог с кодом (src) и добавьте exclude_dirs |
| Много B101 (assert) | Это тесты или чужой код. Исключите каталог тестов |
| Секрет в коде не найден на среднем пороге | B105 имеет низкую серьёзность. Используйте отдельный сканер секретов |
Находка осталась после # nosec |
Указан другой код теста или комментарий стоит не в той строке. Сверьте код в отчёте |
| Сборка падает на каждом коммите | Порог слишком строгий для старого проекта. Начните с высоких находок, сохраните исходный отчёт и сравнивайте с ним |
Ограничения
bandit ищет шаблоны и не понимает логику приложения: он не найдёт обход авторизации, ошибки в правах доступа или уязвимость в бизнес-логике. Для этого нужны ревью кода и тестирование: тесты на проникновение. Отсутствие находок не означает, что код безопасен.
Где это в модели зрелости
Материал относится к направлению «Управление уязвимостями» (материалы направления, карта зрелости). Уровень 2 требует процесса с приоритетами и сроками: автоматическая проверка собственного кода в конвейере добавляет к сканированию инфраструктуры вторую группу рисков, ошибки приложений.
Как проверить
- Bandit запускается в конвейере при каждом изменении кода, порог описан в репозитории.
- Тестовая ошибка (например,
shell=Trueсо строкой от пользователя) останавливает сборку. - Исключения оформлены комментарием с кодом и причиной, их список просматривается на ревью.
- Находки высокой серьёзности имеют владельца и срок исправления.
Источники
- Методика оценки уровня зрелости кибербезопасности компании, Группа Сбер: sberbank.ru.
- Документация bandit: bandit.readthedocs.io; исходный код: github.com/PyCQA/bandit.
- OWASP Top 10: A03 Injection, A08 Software and Data Integrity Failures: owasp.org. Международная практика, не российский норматив.






















