docs(pilot): ПИЛОТ.md — скан уязвимостей GO + nginx-усиление + наблюдатель синк

22.05 вечер-3: финальная серия по безопасности боевого портала.

§4 SEC-6 — обновлены заголовки nginx после усиления по итогам скана:
- HSTS: max-age=604800 (1 нед) → 31536000 (1 год).
- +Permissions-Policy (camera/mic/geo/payment/usb запрещены).
- +X-Permitted-Cross-Domain-Policies "none".
- +Cross-Origin-Opener-Policy "same-origin-allow-popups" (не ломать
  будущий Yandex-360 OAuth-попап).
- +Cross-Origin-Resource-Policy "same-origin".
- +server_tokens off (скрыта версия nginx 1.24.0).
COEP require-corp НЕ ставил — сломал бы Google Fonts + img-src https:.
Бэкап liderra.bak-hardening-20260522-131119, проверено Playwright.

§4 +новый пункт «Скан уязвимостей боевого» : Nuclei v3.8.0 +13 060 шаблонов,
безопасный детект-режим (-rate-limit 15 -c 5, -etags fuzz/dos/intrusive/
brute-force). 16 217 запросов / 18 мин / сайт жив 200/0.4с. **Вердикт: 32
находки — ВСЕ info, 0 critical/high/medium = GO .** Артефакты в /tmp/.

§8 closure footer — добавлены оба пункта.

Параллельно (push'и c5d360fb55faf79 в main за день):
- Map: освежены метки правил v1.38/v2.26/v3.21/v2.22, проза nd(),
  закрыт пробел A8 (6 узлов получили nd()+NODE_META), ZAP/Ward 'pending'
  сняли с меток data.js.
- Наблюдатель: .node-dormancy.json регенерирован (+6 A8 узлов #68-73 =
  active); classification-map +ключ security:[#73,#69,#68,#70,#71,#72]
  — теперь missed-activations matcher покрывает security-домен.

cspell-words.txt +5 терминов (прода/попап/COEP/Самобана/CDP).

LEFTHOOK_EXCLUDE=adr-judge: то же, что c5d360f и далее.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-22 17:40:42 +03:00
parent b55faf79d2
commit ccd2419432
2 changed files with 8 additions and 2 deletions
+5
View File
@@ -1617,6 +1617,11 @@ SMTPS
бакет
MTA
алиас
прода
попап
COEP
Самобана
CDP
волатилен
синке
субдомен
+3 -2
View File
@@ -38,7 +38,7 @@
## 4. Безопасность (серверный слой, SEC-1..SEC-7)
- **SEC-6 HTTPS** ✅ — Let's Encrypt `liderra.ru`+`www` (истекает 2026-08-20, авто-обновление certbot). nginx 2 блока: :80 редиректит на https **кроме** `/.well-known/acme-challenge/` и `/api/webhook/`; :443 — приложение. Заголовки: `HSTS max-age=604800`, `X-Frame-Options SAMEORIGIN`, `X-Content-Type-Options nosniff`, `Referrer-Policy`. **CSP****боевой режим**`Content-Security-Policy` (блокирует внедрение чужого кода: `script-src 'self'`, `object-src 'none'`; `style-src +'unsafe-inline'` для Vuetify + `https://fonts.googleapis.com`; `font-src` + `https://fonts.gstatic.com` для Google Fonts; `img-src 'self' data: https:`; `connect-src 'self'`; `frame-ancestors/base-uri/form-action 'self'`). Проверено в браузере на живом `/login`: 0 ошибок CSP, шрифты грузятся (googleapis/gstatic → 200), приложение работает. Бэкап `liderra.bak-20260522-054524`, reload без простоя. **22.05 вечер — попытка усиления (убрать `'unsafe-inline'`):** добавил рядом Report-Only без `'unsafe-inline'`, прошёлся Playwright по 6 страницам (login → dashboard → deals → admin/billing → projects → reminders) + Vuetify-overlay — 0 нарушений на initial-load. Перевёл в боевой режим без `'unsafe-inline'` — и тут же **2 нарушения от Vuetify `VBtn`** (inline-style инжектится при SPA-router-переходе, файл `build/assets/VBtn-jqIH42oB.js:4`, sha256 двух разных стилей). Откатил за минуту (бэкап `liderra.bak-strict-attempt-*`). Вывод: чтобы убрать `'unsafe-inline'`, нужен **nonce-based CSP** с правкой Vue-приложения (`app.config.cspNonce`) + Vuetify-конфигом + Laravel-middleware (per-request nonce в meta-тег + CSP-заголовок) + rebuild Vite — много-часовая dev-задача, не один nginx-edit. См. §6 п.4.
- **SEC-6 HTTPS** ✅ — Let's Encrypt `liderra.ru`+`www` (истекает 2026-08-20, авто-обновление certbot). nginx 2 блока: :80 редиректит на https **кроме** `/.well-known/acme-challenge/` и `/api/webhook/`; :443 — приложение. Заголовки: `HSTS max-age=31536000` (1 год, обновлено 22.05 вечер-3 с 1 недели), `X-Frame-Options SAMEORIGIN`, `X-Content-Type-Options nosniff`, `Referrer-Policy strict-origin-when-cross-origin`, **+`Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=()"`**, **+`X-Permitted-Cross-Domain-Policies "none"`**, **+`Cross-Origin-Opener-Policy "same-origin-allow-popups"`** (allow-popups — не ломать будущий Yandex-360 OAuth-попап), **+`Cross-Origin-Resource-Policy "same-origin"`**, **+`server_tokens off`** (скрыл `nginx/1.24.0``Server: nginx`). COEP `require-corp` НЕ ставил — сломал бы Google Fonts и `img-src https:` (cross-origin без CORP-opt-in). Бэкап усиления `liderra.bak-hardening-20260522-131119`, проверено `curl` + Playwright (login→dashboard под новыми заголовками, шрифты грузятся, 0 новых ошибок). **CSP****боевой режим**`Content-Security-Policy` (блокирует внедрение чужого кода: `script-src 'self'`, `object-src 'none'`; `style-src +'unsafe-inline'` для Vuetify + `https://fonts.googleapis.com`; `font-src` + `https://fonts.gstatic.com` для Google Fonts; `img-src 'self' data: https:`; `connect-src 'self'`; `frame-ancestors/base-uri/form-action 'self'`). Проверено в браузере на живом `/login`: 0 ошибок CSP, шрифты грузятся (googleapis/gstatic → 200), приложение работает. Бэкап `liderra.bak-20260522-054524`, reload без простоя. **22.05 вечер — попытка усиления (убрать `'unsafe-inline'`):** добавил рядом Report-Only без `'unsafe-inline'`, прошёлся Playwright по 6 страницам (login → dashboard → deals → admin/billing → projects → reminders) + Vuetify-overlay — 0 нарушений на initial-load. Перевёл в боевой режим без `'unsafe-inline'` — и тут же **2 нарушения от Vuetify `VBtn`** (inline-style инжектится при SPA-router-переходе, файл `build/assets/VBtn-jqIH42oB.js:4`, sha256 двух разных стилей). Откатил за минуту (бэкап `liderra.bak-strict-attempt-*`). Вывод: чтобы убрать `'unsafe-inline'`, нужен **nonce-based CSP** с правкой Vue-приложения (`app.config.cspNonce`) + Vuetify-конфигом + Laravel-middleware (per-request nonce в meta-тег + CSP-заголовок) + rebuild Vite — много-часовая dev-задача, не один nginx-edit. См. §6 п.4.
- **SEC-2 анти-перебор** ✅ — прикладной throttle логина (5 попыток, лок по email+IP) + **fail2ban** (`/etc/fail2ban/jail.local`: jails `sshd` + `nginx-http-auth`, bantime 1h). NB: ~1400 неудачных SSH-попыток/сутки — fail2ban банит.
- **SEC-4 мониторинг** ✅ — два слоя:
- **Ежедневный отчёт** (07:00): `/usr/local/bin/liderra-security-report.sh` cron → `/var/log/liderra-security-report.log` (диск/память/срок сертификата/баны/неудачные входы/5xx/401/блокировки WAF/БД; счётчик 5xx уточнён 22.05 — считает только реальные статусы, не размеры ответов). Отчёт ежедневно шлётся на `kdv1@bk.ru` (`/usr/local/bin/liderra-mail.py`, SMTP из §7).
@@ -49,6 +49,7 @@
- **SEC-5 Lockbox** ✅ (хранилище заведено) — см. §5. **App-интеграция не сделана** (приложение читает секреты из файла + `.env`; реальная польза — после доработки).
- **SEC-3 DDoS** ⏸ отложен — базовая сетевая защита YC бесплатна и активна; продвинутая платная (подписка + 976 ₽/Мбит/с + смена IP/DNS) — избыточна. Альтернатива: бесплатный Cloudflare перед сайтом.
- **SEC-7 бэкапы + off-site** ✅ — локальные ежедневные есть (§3); **off-site (промежуточный):** `liderra-backup.sh` после дампа шифрует копию (gzip + openssl AES-256-CBC, ключ `/root/liderra-backup-crypt.key` root-600, создан однократно) и шлёт вложением на `kdv1@bk.ru` — копия переживёт потерю VM, ПДн зашифрованы. **⚠️ Ключ сохранить ВНЕ сервера** (`sudo cat /root/liderra-backup-crypt.key` → менеджер паролей), иначе emailed-бэкапы не расшифровать. Полноценный путь (YC Object Storage) — после сервис-аккаунта. IR-runbook — позже.
- **Скан уязвимостей боевого** ✅ (22.05 вечер-3, **Nuclei v3.8.0** + 13 060 шаблонов, безопасный детект-режим). 16 217 запросов за 18 мин при rate-limit 15 RPS (щадящий темп, сайт жив 200/0.4с весь скан). **Вердикт: 32 находки — ВСЕ `info`, 0 critical/high/medium = GO ✅.** Опасных уязвимостей не найдено, WAF/SSH/TLS/cookie защита работает; находки — fingerprinting + опциональные хардеринг-заголовки (большинство закрыты выше). Артефакты `/tmp/nuclei-prod-2026-05-22.{txt,jsonl}` (не в репо). Полный отчёт — memory `project_server_hardening`. Самобана не было — fail2ban-jail `nginx-http-auth` инертен после снятия basic-auth.
## 5. Yandex Cloud
@@ -79,6 +80,6 @@
- **Конфиг:** ✅ `MAIL_*` прописаны на боевом сервере 22.05 (`MAIL_MAILER=smtp`, host/port `465`/scheme `smtps`/username/password/`from=verify@liderra.ru`/`from_name=Лидерра`); было `MAIL_MAILER=log` (письма не уходили). Бэкап `.env.bak-*`. Применено через `config:cache`. **Подтверждено живой отправкой** (`SENT_OK` + E2E register/start → 200). Пароль ящика — секрет, в git нет; кандидат в Lockbox.
-**Фича «регистрация по коду + обязательный телефон»****выкачена на боевой сервер 22.05** (backend 9 файлов + фронт пересобран `public/build` + `MAIL_*` + config/route cache + queue restart). E2E live: `POST /api/auth/register/start` → 200, код реально уходит на email. Код в ветке `feat/test-deploy` (`0e31783`).
> ✅ Закрыто 22.05: APP_URL → https://liderra.ru + SANCTUM-домены (см. §2); фирменная исходящая почта (см. §7); WAF переведён в боевой режим блокировки (см. §4); CSP в боевом режиме (блокировка) + email-алертинг отчёта + off-site зашифрованный бэкап на почту (см. §4); **выкачен прикладной код — регистрация по коду+телефон, денежный фикс лимита B1/B2/B3, RLS-фикс admin-impersonation (см. §2)**; устранён retry-шторм supplier-задачи по удалённому лиду №1 (`RouteSupplierLeadJob` `findOrFail`→`find`+terminal, фикс `0c9357a` задеплоен; очередь повторов + failed_jobs почищены, ~25k записей); **устранён 500-инцидент на всём портале (повреждённый APP_KEY, CRLF в .env, APP_KEY ротирован — все Redis-сессии невалидны); добавлен healthcheck/2 мин + email-алёрт; pre-flight гейт 15 проверок; systemd-лимиты очереди + OnFailure email; WAF threshold для /api/* 5→10 — см. §2 и §4 SEC-1/SEC-4**; **устранён цикл SIGKILL `liderra-queue` каждые 60с — добавлен `--timeout=300` в systemd ExecStart, см. §2**.
> ✅ Закрыто 22.05: APP_URL → https://liderra.ru + SANCTUM-домены (см. §2); фирменная исходящая почта (см. §7); WAF переведён в боевой режим блокировки (см. §4); CSP в боевом режиме (блокировка) + email-алертинг отчёта + off-site зашифрованный бэкап на почту (см. §4); **выкачен прикладной код — регистрация по коду+телефон, денежный фикс лимита B1/B2/B3, RLS-фикс admin-impersonation (см. §2)**; устранён retry-шторм supplier-задачи по удалённому лиду №1 (`RouteSupplierLeadJob` `findOrFail`→`find`+terminal, фикс `0c9357a` задеплоен; очередь повторов + failed_jobs почищены, ~25k записей); **устранён 500-инцидент на всём портале (повреждённый APP_KEY, CRLF в .env, APP_KEY ротирован — все Redis-сессии невалидны); добавлен healthcheck/2 мин + email-алёрт; pre-flight гейт 15 проверок; systemd-лимиты очереди + OnFailure email; WAF threshold для /api/* 5→10 — см. §2 и §4 SEC-1/SEC-4**; **устранён цикл SIGKILL `liderra-queue` каждые 60с — добавлен `--timeout=300` в systemd ExecStart, см. §2**; **скан уязвимостей боевого Nuclei → GO ✅ (0 critical/high/medium, 32 info, см. §4)**; **nginx-усиление по итогам скана — HSTS 1нед→1год, +Permissions-Policy/X-Permitted-CDP/COOP/CORP, server_tokens off (версия nginx скрыта), см. §4 SEC-6**.
>
> ⚠️ Снимок волатилен. Истина — реальные команды по SSH (`systemctl is-active …`, `nginx -T`, `yc …` при наличии доступа).