Статус: в работе. Документ — план приведения приложения к состоянию, когда аудит безопасности даёт «зелёный свет». Ведётся по этапам; каждый этап — отдельный согласованный коммит с проверкой. Связано: STATUS.md, deployment.md, находка про утечку секрета — ../contracts/findings.md.
- Приложение однопользовательское, но сетевое (FastAPI/uvicorn).
- Планируется доступ из интернета (порт наружу) → планка строгая.
- Привязка действий к пользователям не нужна; нужен один аккаунт администратора (вход — да; роли/журнал по людям — нет).
- Транспортная модель — как у Nextcloud / qBittorrent: TLS терминирует
внешний reverse-proxy (nginx); приложение слушает обычный
httpна loopback за ним. nginx сам по себе дверь не запирает — вход, защита куков, защита от перебора и заголовки делает приложение.
Интернет ──https──> nginx (TLS, :443) ──http──> uvicorn (127.0.0.1:5000) ──> gateway
| Угроза | Пример | Закрывается этапом |
|---|---|---|
| Чужой заходит и всё делает | удалил серии, прочитал настройки | 1 (вход) |
| Подслушивание трафика | перехват пароля трекера в сети | 2 (TLS+куки) |
| Утечка секретов | пароли в БД/в git/на фронте | 0, 3 |
| Перебор пароля | бот молотит /login из интернета | 1 (rate-limit+бан) |
| Подмена адреса/хоста | обход бана, host-injection | 2 (trusted proxy/hosts) |
| Опасные ручки | очистка таблиц, SSRF, /docs | 4 |
| # | Находка | Severity | Где |
|---|---|---|---|
| S1 | Бэкап БД с реальными секретами в публичном git (app.db.rules-bak) |
critical | git history (a5851ae) |
| S2 | Нет аутентификации ни на одном эндпоинте | high → critical (из интернета) | gateway, все роуты |
| S3 | GET /api/auth отдаёт все учётки на фронт в открытом виде |
high → critical | api_settings.py |
| S4 | Секреты в БД хранятся в открытом виде | high | auth, settings, tracker_sessions |
| S5 | POST /api/database/clear_table (DELETE) без защиты |
medium → high | api_settings.py |
| S6 | SSRF: /api/parse_url ходит по любому URL клиента |
medium | api_settings.py → sources |
| S7 | Публичные /docs и /openapi.json |
low | gateway/module.py |
| S8 | Нет TLS (трафик по http) | high (из интернета) | транспорт |
| S9 | Нет защитных заголовков (HSTS/CSP/…) | low/medium | gateway |
Что уже хорошо (не трогаем): SQL параметризован, subprocess без shell,
имена файлов санитизируются, verify=False нет, eval/pickle нет, CORS не
открыт, текущие .env/app.db вне git.
- Вход: один админ. Логин + пароль; пароль — хэш argon2. После входа — серверная сессия в защищённой куке (Secure, HttpOnly, SameSite=Strict). Единый «замок» (middleware) перед всеми запросами, кроме страницы входа, логина и статики.
- Перебор: лимит неудачных попыток + временный бан по IP (реальный IP берём из заголовка от доверенного nginx).
- Транспорт: TLS на nginx; приложение —
--proxy-headers,forwarded-allow-ips= адрес nginx; проверка хоста (TrustedHost); HSTS. - Секреты: на фронт не уходят никогда (флаг «задано/нет» +
●●●●); в БД — шифрование (Fernet), мастер-ключST_SECRET_KEYв.env(вне git). - Подчистка:
/docsвыкл (или под вход), опасные ручки за замком + allowlist доменов для parse_url, защитные заголовки, без стек-трейсов.
Новые зависимости (бэкенд): argon2-cffi, cryptography, itsdangerous
(или штатный Starlette session). Фронт: экран входа + обработка 401.
.gitignore: добавитьapp.db*и*.bak(текущий*.dbне ловит.rules-bak).- Убрать файл из git (
git rm --cached app.db.rules-bak) + коммит. - Сменить все засветившиеся секреты (см. §6) — обязательно: файл уже был в публичной истории, секреты считаем скомпрометированными.
- Переписать историю git (удалить файл из всех коммитов) +
force-push. Необратимо, затрагивает публичный origin → выполняется по отдельному подтверждению.
- Хранение: логин + argon2-хэш пароля (таблица
admin_user, владелецauth). - Онбординг первого запуска: если админа нет — UI показывает модалку
«Первый запуск» (имя/пароль/повтор, мин. 8) вместо входа;
POST /api/setupсоздаёт админа и сразу логинит, активен только пока админа нет (после — 403)..env-bootstrap (ST_ADMIN_USER/ST_ADMIN_PASSWORD) — опция для headless. - Эндпоинты:
POST /api/login,POST /api/logout,GET /api/me,POST /api/setup,GET /api/auth/status. - Middleware-замок в gateway: всё, кроме
/login, статики и health — требует валидную сессию, иначе401. - Защита от перебора: счётчик неудач по IP + экспоненциальная задержка/бан.
- Фронт: модалки входа/установки в стиле проекта (StGroup/StInput, валидация рамкой, плашка-уведомление под окном). Визуал принят; эффект «матового стекла» — решено НЕ делать (текущий вид финальный).
- uvicorn:
--proxy-headers --forwarded-allow-ips <nginx_ip>. - Куки: Secure + HttpOnly + SameSite=Strict.
TrustedHostMiddleware(список разрешённых доменов).- Заголовок HSTS (или на nginx).
- Документация: готовый пример конфига nginx (§7).
GET /api/authи аналоги: НЕ возвращать значения секретов; только{"configured": true/false}. Фронт показывает●●●●, меняет вводом нового.- Шифрование в БД: Fernet, ключ
ST_SECRET_KEYиз.env. Прозрачный слой шифровать/расшифровать в репозиториях-владельцах (trackerauth,settings). Миграция существующих значений в шифртекст.
/docs,/openapi.json: выключить (или под вход).clear_table,parse_url: под замок (уже будут за gate) + parse_url — allowlist доменов трекеров (анти-SSRF).- Защитные заголовки: CSP, X-Content-Type-Options, Referrer-Policy, frame-ancestors.
- Ошибки наружу — без стек-трейсов/деталей стека.
- Статический скан кода (
bandit), чек-лист §5, ручная проверка входа и ограничения доступа. Зафиксировать «было красное → стало зелёное».
- Все эндпоинты, кроме входа/статики, требуют аутентификацию — замок
включён на проде (
ST_AUTH_REQUIRED=true). (Этап 1+2) - Пароль админа хранится хэшем (argon2), не в открытом виде. (Этап 1)
- Защита от перебора пароля (лимит + бан по IP). (Этап 1)
- TLS включён (nginx + Let's Encrypt, setr.mywolfram.ru); HTTP→HTTPS. (Этап 2)
- Куки: Secure, HttpOnly, SameSite (
ST_COOKIE_SECURE=trueза TLS). (Этап 2) - Проверка хоста (TrustedHost) + доверие forwarded только от nginx. (Этап 2)
- Защитные заголовки: X-Content-Type-Options/X-Frame-Options/Referrer- Policy (Этап 4) + HSTS на nginx (Этап 2). CSP отложен (риск для SPA).
- Секреты не уходят на фронт; в БД зашифрованы; ключ вне git. (Этап 3)
- Нет секретов в git и его истории. (Этап 0)
-
/docsзакрыт; parse_url ходит только на известные трекеры (SSRF митигирован); опасные ручки — под замком при включённом входе. (Этап 4) - Наружу не уходят стек-трейсы (generic-обработчик ошибок). (Этап 4)
Сменить/перевыпустить (значения не приводятся намеренно):
- qBittorrent — пароль WebUI (+ обновить
.envST_QBIT_*). - Трекеры — пароли kinozal, rutracker (учётки в БД, вводятся через UI).
- VK — пароль/токен учётки.
- TMDB — перевыпустить API-токен (
settings.tmdb_token).
server {
listen 443 ssl http2;
server_name series.example.com;
ssl_certificate /etc/letsencrypt/live/series.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/series.example.com/privkey.pem;
# HSTS — браузер ходит только по https
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE: не буферизовать поток событий
proxy_buffering off;
proxy_read_timeout 3600s;
}
}
server { # http -> https
listen 80;
server_name series.example.com;
return 301 https://$host$request_uri;
}uvicorn за этим nginx запускать с:
uvicorn run:app --host 127.0.0.1 --port 5000 --proxy-headers --forwarded-allow-ips 127.0.0.1