Ревью кодовой базы ros_control — 2026-09-28 12:43 MSK
Повторное ревью. Состояние на коммит c407972. Тесты: 28/28 проходят.
Предыдущее ревью: 2026-09-27-codebase-review.md (коммит 26dd1f8).
Итог
Из 12 замечаний предыдущего ревью закрыто 10 (одно — частично), открыто 2. Закрыты разделы «Безопасность»,
«Корректность и согласованность», «Производительность и масштабирование»; раздел «Эксплуатация» не менялся.
| Изменение |
Коммит |
Пункты |
018-correctness-consistency |
123b5ab |
5, 6, 7 |
019-performance-scaling |
ba8ac7b |
8, 9 (частично), 10 |
020-custom-select-menus |
6b5c521 |
— (доработка UI по запросу) |
021-security-hardening |
c407972 |
1, 2, 3, 4 |
Статус замечаний предыдущего ревью
Безопасность
| № |
Замечание |
Приоритет |
Статус |
Подтверждение в коде |
| 1 |
Небезопасные значения секретов по умолчанию |
Высокий |
✅ 021 |
app/config.py::insecure_settings; проверка первым шагом в app/main.py::lifespan — при пустых, change-me или коротких секретах (API_TOKEN, SESSION_SECRET ≥ 32, ADMIN_PASSWORD ≥ 12) и невалидном SECRET_KEY приложение не стартует |
| 2 |
Вход в UI без защиты от перебора |
Высокий |
✅ 021 |
app/ui/routes.py::login: 5 неверных за 10 минут → IP заблокирован на 10 минут (429, пароль не проверяется); auth.locked пишется один раз |
| 3 |
Сессионная cookie без флага Secure |
Средний |
✅ 021 |
SESSION_COOKIE_SECURE → SessionMiddleware(https_only=…); по умолчанию выключен, за TLS — включить |
| 4 |
Открытый редирект в /ui/move |
Низкий |
✅ 021 |
app/ui/routes.py::_safe_next — только локальный путь; применён в /ui/move и групповом удалении бэкапов |
Корректность и согласованность
| № |
Замечание |
Приоритет |
Статус |
Подтверждение в коде |
| 5 |
Одиночное удаление бэкапа в обход сервиса |
Средний |
✅ 018 |
app/ui/routes.py::backup_delete → backups.delete_many([key]) |
| 6 |
Две системы миграций |
Низкий |
✅ 018 |
db._migrate удалён; колонки старой схемы — migrations._add_legacy_columns при user_version < 1 |
| 7 |
Синхронная групповая смена канала |
Средний |
✅ 018 |
Задачи set_channel; PUT /api/v1/batch/channel → 202 {"job_ids"} |
Производительность и масштабирование
| № |
Замечание |
Приоритет |
Статус |
Подтверждение в коде |
| 8 |
Каждое открытие «Бэкапов» перечитывает весь бакет |
Средний |
✅ 019 |
app/services/backups.py::bucket_objects — кэш на BACKUPS_CACHE_TTL, счётчик поколений, invalidate() после бэкапа и удаления, refresh=1 |
| 9 |
Синхронная БД в async-обработчиках |
Низкий |
🟡 019, частично |
44 обработчика стали def; фоновые записи — asyncio.to_thread; SQLite WAL + busy_timeout. Остаток — см. «Открытые замечания», п. 9 |
| 10 |
Состояние в памяти процесса |
Низкий |
✅ 019 |
app/process_lock.py — flock на <файл БД>.lock в lifespan; README «Ограничения» |
Эксплуатация
| № |
Замечание |
Приоритет |
Статус |
Подтверждение в коде |
| 11 |
Нет healthcheck, логирование не настроено |
Низкий |
❌ Открыт |
Нет HEALTHCHECK в Dockerfile и docker-compose.yml; логирование нигде не настраивается |
| 12 |
Тесты в одном файле |
Низкий |
❌ Открыт, растёт |
tests/test_app.py: 570 → 753 строки, 20 → 28 тестов |
Открытые замечания
| № |
Замечание |
Где |
Приоритет |
| 9 |
Остаток синхронной работы с БД в async-коде: groups.list_groups() и devices.list_devices() в async-обработчике страницы «Бэкапы»; devices.get_conn() (чтение и расшифровка пароля) |
app/services/backups.py::search, app/services/ops.py::run_backup |
Низкий |
| 11 |
Нет healthcheck; логирование не настроено |
Dockerfile, docker-compose.yml, app/main.py |
Низкий |
| 12 |
Тесты в одном файле, файл растёт |
tests/test_app.py |
Низкий |
Новые замечания
| № |
Замечание |
Где |
Приоритет |
| 13 |
Копирование БД в режиме WAL не описано: копия файла БД без -wal может быть неполной; нужен sqlite3 .backup или копирование вместе с -wal |
README.md |
Средний |
| 14 |
За reverse-proxy блокировка входа видит один IP прокси (нет настройки доверенных прокси и разбора X-Forwarded-For); ограничение описано в README |
app/ui/routes.py::login |
Низкий (прокси сейчас нет) |
| 15 |
refresh=1 остаётся в адресе страницы «Бэкапы» после «Обновить список» — повторные перезагрузки идут мимо кэша |
app/ui/routes.py::backups_page |
Низкий |
| 16 |
Стенд запускается на порту 8001 через override-файл вне репозитория: порт 8000 на хосте занят посторонним процессом (telemetry_web); штатный docker-compose.yml без override не стартует. Порт не параметризован |
docker-compose.yml, окружение хоста |
Низкий (окружение) |
Проверка UI
Ручная проверка интерфейса пользователем по изменениям 019–021 не подтверждена (отмечено в summary.md и сообщениях коммитов).
Особенно важно для 020 (выпадающие списки): автотестов JS в проекте нет.
Рекомендуемый порядок
- Изменение 022: п. 11 (
HEALTHCHECK, базовая настройка логов), остаток п. 9, п. 13 (копирование БД в README), п. 15.
- П. 16 — параметризовать порт в
docker-compose.yml (${HTTP_PORT:-8000}) или освободить порт 8000 на хосте.
- П. 12 — разбить тесты по модулям отдельным изменением (только структура тестов).
- П. 14 — при появлении reverse-proxy: доверенные прокси и реальный IP клиента.