Enhance JWT signing key validation and improve FfmpegBumperRenderer label handling: add checks for signing key length and placeholder values in DependencyInjection, and refactor label processing to read from files in FfmpegBumperRenderer. Update MediaProcessingBackgroundService to ensure slot release on claim failure.
build / backend (push) Successful in 2m24s
build / frontend (push) Successful in 54s
tests / backend-tests (push) Successful in 2m40s

This commit is contained in:
Leonid Pershin
2026-07-25 21:07:38 +03:00
parent 5bc6e144d2
commit 53e0eeb776
4 changed files with 158 additions and 10 deletions
+88
View File
@@ -0,0 +1,88 @@
# Ревью TeleWave — план исправлений
Статус чекбоксов: `[ ]` не сделано · `[x]` исправлено · `[~]` в работе.
Дата ревью: 2026-07-25. Область: backend (C#/.NET 10), frontend (React 19), инфра.
Сборка чистая, 144 теста зелёные, typecheck чистый.
---
## 🔴 Критичное
- [x] **C1. Утечка слота семафора → медиа-обработка навсегда встаёт**
`MediaProcessingBackgroundService.cs:52` — слот берётся до `ClaimNextAsync`; бросок из claim
уводит во внешний `catch` без `Release()`. После N ошибок диспетчер зависает навсегда.
_Fix: try/finally вокруг claim либо Release() в ветке ошибки._
- [x] **C2. Слабый JWT-ключ по умолчанию без fail-fast**
`appsettings.json:8` + `DependencyInjection.cs:65` — placeholder-ключ, нет проверки на
переопределение и длину; тот же ключ у stream-токенов. Забыли → обход авторизации.
_Fix: на старте кидать, если ключ = placeholder или < 32 байт._
- [x] **C3. Инъекция/поломка ffmpeg-фильтра из пользовательских подписей**
`FfmpegBumperRenderer.cs:306``EscapeText` не экранирует `,` `;` `[` `]` `%` перевод строки.
Подпись с запятой рвёт filtergraph; `[`/`]` — инъекция звеньев. Валидатор проверяет только длину.
_Fix: экранировать полный набор метасимволов filtergraph._
---
## 🟠 Среднее
### Безопасность
- [ ] **M1. Cookie без `Secure` за TLS-прокси**`AuthEndpoints.cs:207`, `StreamingEndpoints.cs:63`.
- [ ] **M2. Ключи TMDb/OMDb в логах**`TmdbMetadataProvider.cs:27`, `OmdbMetadataProvider.cs:25` (`System.Net.Http` не приглушён в Serilog).
- [ ] **M3. Stream-токен неотзывной, TTL 6ч, игнорирует userId/блокировку**`StreamTokenService.cs:15`, `StreamingEndpoints.cs:98,123`.
- [ ] **M4. Rate-limiter глобальный (не партиционирован), только на `/api/auth`**`Program.cs:66`.
### Архитектура / транзакции
- [ ] **M5. `ExecuteDeleteAsync` ломает границу UnitOfWork + файлы удаляются до коммита**`DeleteShowMediaCommandHandler.cs:36`, `ClearAllMediaCommandHandler.cs`, `DeleteAllShowsCommandHandler.cs`.
- [ ] **M6. Query с побочным эффектом на ФС**`RenderBumperPreviewQueryHandler.cs:18` (рендер файлов под видом запроса).
- [ ] **M7. Файловый/HTTP I/O до коммита** — Delete/Register/RefreshEpisodes хендлеры (orphan-файлы при откате).
### Планировщик / медиа
- [ ] **M8. Нет таймаута на ffmpeg/ffprobe**`ProcessRunner.cs:63` (зависший процесс держит слот).
- [ ] **M9. Гонка при конкурентной генерации расписания канала**`ScheduleGenerator.cs:62` (тик + regenerate → дубли записей).
- [ ] **M10. Рендер заставок синхронно внутри тика планировщика**`ScheduleBumperResolver.cs:174`.
- [ ] **M11. Override через полночь не работает**`SchedulePlannerModels.cs:40` (`EndMinute <= StartMinute` → пустое окно).
### Фронтенд
- [ ] **M12. Молчаливое проглатывание ошибки → вечный скелетон**`AirPage.tsx:43`.
- [ ] **M13. Повторный 401 после refresh не разлогинивает**`client.ts:80`.
- [ ] **M14. Клиентская пагинация поверх усечённого ответа**`ShowDetail.tsx:42` (`pageSize:500`), `ChannelDetail.tsx:32` (`pageSize:100`).
- [ ] **M15. Дедуп загрузок только по имени файла**`upload-store.ts:143`.
---
## 🟡 Низкое / прочее
- [ ] **L1. Синхронный I/O без CancellationToken в портах удаления** (`IMediaStorage`/`IImageStore`/`IBumperTemplateStorage`).
- [ ] **L2. `ValidationBehavior` вызывает `Validate` вместо `ValidateAsync`**`ValidationBehavior.cs:24`.
- [ ] **L3. Инвариант «Single = 1 серия» проверяется в хендлере, а не в агрегате**`Show.AddEpisode`.
- [ ] **L4. Двойной `SaveChanges` в командном пути ScheduleGenerator**`ScheduleGenerator.cs:79,132`.
- [ ] **L5. HLS-плеер не восстанавливается после fatal network/media error**`ChannelPlayer.tsx:106`.
- [ ] **L6. Бесконечная повторная регистрация «падающего» файла из inbox**`InboxScannerBackgroundService.cs:92`.
- [ ] **L7. Возможное переполнение int в весах шоу**`SchedulePlanner.cs:256,265`.
- [ ] **L8. OpenAPI/Scalar мапятся всегда, без гейта по окружению**`Program.cs:108`.
- [ ] **L9. `AllowedHosts: "*"` и dev-креды БД в appsettings.json**.
- [ ] **L10. SSRF-поверхность в ImageDownloader** (без allowlist схемы/хоста) — `ImageDownloader.cs:16`.
- [ ] **L11. Документация (CLAUDE.md/README) отстала**: заявлено «каталог каналов и раздача видео не реализованы», хотя реализованы.
- [ ] **L12. Пробелы в тестах**: фоновые сервисы, ScheduleGenerator-оркестрация, media-конвейер, эндпоинты без тестов.
---
## Журнал исправлений
### 2026-07-25 — критические (C1–C3)
- **C1** — `MediaProcessingBackgroundService.cs`: `ClaimNextAsync` обёрнут в `try/catch`, слот
освобождается (`slots.Release()`) при любом сбое захвата перед `throw`. Теперь транзиентная
ошибка БД не «съедает» слот семафора — диспетчер не зависает.
- **C2** — `Infrastructure/DependencyInjection.cs`: после загрузки `JwtOptions` добавлен fail-fast:
старт падает с понятным сообщением, если `Jwt:SigningKey` короче 32 байт или содержит `change-me`
(значение-заглушка). Тот же ключ подписывает stream-токены, поэтому это закрывает и их.
- **C3** — `FfmpegBumperRenderer.cs`: подписи «Сейчас/Далее» (`NowLabel`/`NextLabel`) переведены с
инлайнового `text=` на `textfile=` (`nowlabel.txt`/`nextlabel.txt`, `expansion=none`), как уже
сделано для названий шоу. Метасимволы filtergraph в подписи больше не ломают/не инъектируют
цепочку. Неиспользуемый `EscapeText` удалён. Файлы чистятся в `finally`.
Проверка: `dotnet build` — 0 warnings/0 errors (при `TreatWarningsAsErrors`); тесты 95 + 49 зелёные.