Files
ros_control/docs/reviews/2026-09-28-1243-codebase-review.md
T

79 lines
7.8 KiB
Markdown
Raw Normal View History

# Ревью кодовой базы ros_control — 2026-09-28 12:43 MSK
Повторное ревью. Состояние на коммит `c407972`. Тесты: 28/28 проходят.
Предыдущее ревью: [`2026-09-27-codebase-review.md`](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 в проекте нет.
## Рекомендуемый порядок
1. **Изменение 022:** п. 11 (`HEALTHCHECK`, базовая настройка логов), остаток п. 9, п. 13 (копирование БД в README), п. 15.
2. П. 16 — параметризовать порт в `docker-compose.yml` (`${HTTP_PORT:-8000}`) или освободить порт 8000 на хосте.
3. П. 12 — разбить тесты по модулям отдельным изменением (только структура тестов).
4. П. 14 — при появлении reverse-proxy: доверенные прокси и реальный IP клиента.