Code Audit 2026-06-30 — Новые находки
Контекст: Полный аудит кодовой базы после исправления предыдущего техдолга. Часть проблем исправлена, но обнаружены новые.
Суть
Аудит выявил 10 новых проблем (1 security, 3 reliability, 6 code quality) в дополнение к уже известным из [[architecture-overview]].
Найденные проблемы
🔴 Security
- Эскалация привилегий через AdminUpdateUser —
handlers/users.go:166-186
- Модератор может повысить роль любого пользователя до
superadmin (кроме своей). Ограничение только на собственную роль.
- Надо добавить проверку: модератор не может устанавливать роль
superadmin.
🟡 Reliability
Отсутствует статус по умолчанию при создании пользователя — repository/users.go:22-29
- INSERT не задаёт
status, в БД нет DEFAULT. У нового пользователя Status = "".
- Миграция должна задавать
status DEFAULT 'active', либо код должен явно проставлять.
BookingRepo.Create не проверяет статус места — repository/bookings.go:25-63
- Можно забронировать место в статусе
pending_moderation, draft или rejected. Проверка только на deleted_at IS NULL.
- Нужна проверка
status = 'published'.
RoleMiddleware type assertion без проверки — middleware/auth.go:54
r.Context().Value(UserRoleKey).(string) запаникует, если в контексте не строка.
- В отличие от безопасного
GetUserRole(), эта функция делает unsafe type assertion.
🔵 Code Quality
N+1 в GetByID для services — handlers/services.go:80-85
- Сначала
GetByID, потом отдельно GetTags. Можно объединить в один JOIN-запрос.
- Аналогично было с places, но там уже исправлено.
DeleteTag: странный парсинг тела DELETE — handlers/tags.go:79-84
- Сначала JSON body, при ошибке — query param. Это не RESTful и путает.
- DELETE с body — редкая практика, лучше только query.
broadcastVisitors двойной lock без необходимости — handlers/websocket.go:91-127
- Сначала под mutex собирает список, отпускает, потом снова берёт для рассылки.
- Между сбором и рассылкой список клиентов может измениться.
logout на фронте подавляет ошибки — hooks/useAuth.tsx:65
catch {} скрывает ошибки сети при logout.
- Пользователь не видит, что выход не удался.
fetchPlaces подавляет ошибку — components/MapView.tsx:37-39
- Ошибка загрузки мест игнорируется, пользователь не видит уведомления.
next.config.js: только cdn.photoplaces.ru и localhost — next.config.js:5-8
- В production на тестовом сервере S3/MinIO раздаётся через
api.{DOMAIN}/s3/, что не добавлено в remotePatterns.
- Изображения могут не загружаться через Next.js Image Optimization.
Что было проверено и признано корректным
- Валидация на всех эндпоинтах (go-playground/validator)
- Безопасность паролей (bcrypt)
- Защита от SQL-инъекций (pgx параметризованные запросы)
- Refresh token rotation + SHA-256 хранение
- Rate limiter с fail-open/closed поддержкой
- Проверка сигнатуры файлов при загрузке (magic bytes)
- CORS настроен
- Мягкое удаление (soft delete)
- Graceful shutdown
- Консистентность координат (lat, lng)
Рекомендации
- Срочно (P0): Добавить проверку в
AdminUpdateUser — модератор не может ставить роль superadmin
- Высоко (P1): Добавить DEFAULT 'active' для status в users (миграция 000012)
- Высоко (P1): Добавить проверку
status = 'published' при создании брони
- Средне (P2): Исправить RoleMiddleware, N+1 в services, broadcastVisitors lock, next.config.js
Связанные заметки
- [[architecture-overview]]
- [[atomic-toctou-race-registration]]
- [[atomic-mutex-defer-panic]]
- [[atomic-error-swallowing-frontend]]
Теги
#code-review #security #backend #frontend #bug #2026-06