Этот документ описывает, что защищается, от каких угроз, и как именно. Если нашёл уязвимость — пиши в GitHub Security Advisories, не открывай публичный issue.
| Актив | Где живёт | Чувствительность |
|---|---|---|
| Bearer-токен сессии (Pro) | chrome.storage.local пользователя |
как cookie сессии |
| Прокси-логин/пароль (basic_auth) | chrome.storage.local пользователя |
как пароль от подписки |
| Bearer-токен в БД | backend/data/plgames.db (на нашем VPS) |
приватно |
DONATEPAY_WEBHOOK_SECRET, INTERNAL_API_TOKEN, ключи DonatePay |
backend/.env |
приватно (не в git) |
| Прокси-серверы и их IP | backend/data/proxy-pool.json |
приватно (не в git) |
| Реальные домены инфраструктуры | конфиги nginx/Caddy на VPS | приватно (не в git) |
Ключевой факт: в коде расширения нет никаких НАШИХ секретов. URL API публичен, нет API-ключей, нет master-паролей, нет shared-secret. Реверс-инжиниринг расширения злоумышленнику ничего не даёт.
- Содержимое
chrome.storage.local. Это обычный JSON-файл под OS-аккаунтом пользователя. Тот же уровень защиты что у cookies и сохранённых паролей Chrome. Шифровать его внутри расширения бесполезно: ключ всё равно пришлось бы хранить рядом с шифротекстом, в открытом коде расширения. Это security through obscurity, а не настоящая защита. - Перехват трафика на машине пользователя. Если на машине вирус с правами пользователя — он считает что угодно из любого браузера. Это вне нашего периметра.
Пользователь сам копирует свои username:password (basic_auth) и раздаёт
друзьям/знакомым. Это не взлом, а недобросовестное использование.
- Частая ротация кредов.
- Бэкенд при каждом запросе
GET /api/profile, если креды старшеCREDENTIALS_TTL_MS(2 ч) — выдаёт новые. - Расширение в фоне через
chrome.alarmsзовётPOST /api/profile/rotateраз в 1.5 часа — независимо от того, открыт ли попап. - Агент-синхронизатор на прокси-сервере подхватывает новые
basic_authкаждые 30 секунд. Старые перестают работать. - Итог: украденные креды живут максимум ~2 часа.
- Бэкенд при каждом запросе
- Connection limit (опционально).
- В Caddyfile прокси-сервера можно ограничить количество одновременных соединений на пользователя — отсекает массовую раздачу.
- Revocation API.
- Если юзер пожаловался — у нас есть
POST /api/internal/admin/revokeсINTERNAL_API_TOKEN. После вызова статус меняется наrevoked,Authorization: Bearer …начинает возвращать 401.
- Если юзер пожаловался — у нас есть
- Привязка к устройству (installation_id).
Managed-подписка привязывается к
installation_idрасширения при создании заказа. Токен и креды выдаются только этому устройству; запрос с другим (или без)installation_idполучает403 installation mismatch. Привязку к IP НЕ делаем — слишком много false positives на мобильных/динамических IP. - Shareguard по одновременным IP (опционально).
Watcher на прокси-сервере шлёт
POST /api/internal/connections; если у одногоproxy_userза окно набирается большеSHAREGUARD_MAX_IPSуникальных IP — подписка помечаетсяrevoked.
- TLS обязателен для всего публичного API. nginx/Caddy перед
127.0.0.1:8080. - Rate limiting:
@fastify/rate-limit60 req/min на IP по умолчанию. - Internal API изолирован заголовком
X-Internal-Tokenи должен быть ограничен на nginx-уровне по IP-whitelist прокси-серверов:location /api/internal/ { allow 1.2.3.4; # IP прокси-сервера eu-1 allow 1.2.3.5; # IP прокси-сервера eu-2 deny all; proxy_pass http://127.0.0.1:8080; }
- Webhook DonatePay: HMAC-SHA256 подпись от raw-body (header
X-Signature), идемпотентная обработка (повторный webhook не дублирует подписку), валидация суммы (если оплачено меньше тарифа ИЛИ сумма не указана — 402). Secure by default: безDONATEPAY_WEBHOOK_SECRETвебхук отклоняется (для dev можно явно разрешитьALLOW_UNSIGNED_WEBHOOK=1). - Токен заказа привязан к устройству.
GET /api/orders/:idотдаёт bearer-токен только томуinstallation_id, что создал заказ — orderId светится в комментарии платежа (публичная лента DonatePay) и сам по себе доступ не даёт. - БД (SQLite + WAL): файл доступен только UID процесса бэкенда. Бэкап — копированием файла, при этом WAL обеспечивает консистентность снимка.
- Секреты в
.env:.envв.gitignoreрепозитория. На сервере права0640 root:root.
INTERNAL_API_TOKENлежит в/etc/plgames-agent/agent.envс правами0640 root:root.- Запросы идут только по HTTPS.
- Файрвол: исходящий трафик к
api.plgames-connect.example:443разрешён, всё остальное — на усмотрение оператора. - При утечке
INTERNAL_API_TOKENзлоумышленник получит только список текущих активных подписчиков на ОДНОМ сервере. Не сможет: создавать заказы, отзывать токены, ходить вchrome.proxyпользователей, менять подписки. Ротация токена (один env + рестарт бэкенда + правкаagent.envна прокси-серверах) занимает 5 минут.
- Manifest V3 — ужесточённый CSP, нет
eval, нет inline-скриптов. - Permissions минимальны:
storage,proxy,webRequest,webRequestAuthProvider,tabs,alarms. Никакогоcookies,history,bookmarks,tabs(открытие страницы оплаты использует ограниченныйtabs.create). - Никаких сторонних UI/CDN. Все стили inline через design-tokens, иконки
inline-SVG, шрифты системные. Никакого
<script src=…>извне. - API URL зашит в build-time через
webpack.DefinePlugin— пересборка под другой URL требует прав на репо.
git statusперед каждым коммитом: не утекают.env,*.pem,*.key, реальные домены/IP, GitHub-токены. Sweep-паттерны:ghp_…,github_pat_…,BEGIN [A-Z]+ PRIVATE,<твой реальный домен>,<твой IP>.- Перед публикацией релиза: содержимое
dist/декомпилировать обратно на secret pattern (grep -aE "ghp_|github_pat_|BEGIN.*PRIVATE" dist/*.js).
-
INTERNAL_API_TOKENсгенерирован и одинаковый в backend и всех агентах -
DONATEPAY_WEBHOOK_SECRETполучен в кабинете DonatePay и прописан в.env(без него вебхук отклоняется;ALLOW_UNSIGNED_WEBHOOKНЕ должен быть выставлен) - nginx с TLS перед бэкендом и landing'ом
- nginx-allowlist для
/api/internal/ - Расширение собрано с реальным
PLGAMES_API_URL -
proxy-pool.jsonсодержит хотя бы один реальный сервер - Агент установлен и активен на каждом прокси-сервере
- Тестовая покупка прошла end-to-end и профиль активировался автоматически