Тема
Безопасность и права
Статус: решения зафиксированы (Q3, Q10, Q11 брейншторма). Детали реализации уточняются в фазе 0. Урок, который держим в голове: 91% CVE в экосистеме WordPress — в плагинах, а не в ядре. Значит, контракт модуля и модель прав важнее, чем ядро.
Модель прав (RBAC)
Filament Shield поверх spatie/laravel-permission. Уже в стеке студии, стандарт де-факто. Shield генерирует permission'ы по Filament-ресурсам автоматически.
Базовые роли парка:
| Роль | Доступ |
|---|---|
| Разработчик студии | Полный: код, модули, обновления, все сайты |
| Контент-менеджер клиента | Контент, страницы, заявки, настройки своего сайта |
| Редактор | Ограниченный: только назначенные разделы контента |
Многоуровневая ролевая модель (Twill Role/RoleGroup/RoleGroupItem) — не нужна для 3–4 ролей. Если у конкретного клиента вырастут сложные роли — это модуль «расширенные роли», не переделка ядра.
Принцип «модуль ≠ ядро по правам»
Ключевое архитектурное правило, заложенное с фазы 0: модуль не может молча расширить свои привилегии до уровня ядра.
- Модуль декларирует свои permission'ы в манифесте — Shield их подхватывает, но они видны и управляемы администратором.
- Модуль не пишет в таблицы ядра напрямую — только через сервисы/события ядра.
- Модуль не переопределяет политики ядра — только добавляет свои.
- Capability-слой: что модулю можно (свои роуты, свои таблицы, свои события), что нельзя (трогать пользователей ядра, менять чужие права) — в контракте модуля.
Это заготовка под будущий открытый маркетплейс: когда/если пустим сторонние модули, модель «песочницы прав» уже будет на месте. Ретрофитить её в открытый маркетплейс невозможно — поэтому закладываем сразу, хотя маркетплейс пока закрыт.
Маркетплейс: закрыт сейчас, архитектура под открытие
Сейчас маркетплейс закрытый — только модули студии, внутренний каталог Satis. Сторонние разработчики не публикуют.
Почему не открываем сразу: ревью безопасности чужого кода силами одного разработчика неподъёмно. Открытый маркетплейс без обязательного ревью = один уязвимый модуль ставит под удар весь парк managed-сайтов (урок WordPress).
Что закладываем под будущее открытие (без переделки потом):
- контракт модуля с capability-слоем (см. выше);
- модель лицензирования по клиенту (license key на сайт — Statamic-модель);
- политику «плагин ≠ ядро по правам»;
- изоляцию сбоев (баг в модуле не роняет ядро).
Открытие маркетплейса сторонним — фаза 5, не блокирует ядро. Модель ревью безопасности (кто отвечает за чужой код) решается тогда же.
Лицензирование клиенту
При расторжении подписки на поддержку:
| Что | Передаётся клиенту |
|---|---|
| Код сайта (skeleton + тема + контент + БД) | ✅ Да — сайт продолжает работать |
| Доступ к Satis (обновления ядра/модулей) | ❌ Нет — отключается |
Модель: клиент владеет своим сайтом (не заложник SaaS), но поток обновлений и модулей студии — платная услуга. Проговорить в договоре: пункт о передаче кода и прекращении доступа к обновлениям.
Техническое следствие: skeleton должен быть самодостаточным — composer-пакеты ядра и модулей вендорятся в поставку клиента, чтобы сайт работал без Satis. На стороне Satis — управление доступом по клиенту (токен/ключ), отзываемый при расторжении.
Фильтрация входа на всех границах (ядро)
Принцип OWASP: валидация на входе, экранирование на выходе, и ни одна граница не доверяет предыдущей. Границ у CMS пять, у каждой — свой обязательный фильтр ядра:
| Граница | Фильтр (не на совести разработчика модуля) |
|---|---|
| Формы (публичные + конструктор Q8) | FormRequest из движка полей: типы, rules, maxLength; CSRF, honeypot, rate-limit, согласие ПДн |
API (/api/vN) | тот же движок полей → 422; whitelisted-фильтрация spatie/laravel-query-builder (произвольный SQL/поле в запросе невозможны); конверт ошибок без stack trace |
| Админка (Filament) | те же rules из декларации поля; RBAC/Policy на каждое действие; никакого «доверенного» ввода — контент-менеджер клиента не админ студии |
| Файлы | whitelist MIME+расширений по типу поля, лимит размера, пересборка изображений (Intervention re-encode срезает полезную нагрузку), приватный диск для документов, раздача через подписанные URL |
| Внешние ответы (webhooks, интеграции) | верификация подписи (HMAC + timestamp против replay), схема-валидация payload, outbound-http только через integration-core (SSRF-защита: запрет внутренних адресов) |
Правила сквозные:
- rich-text — санитизация HTML по whitelist-тегам при сохранении и при выводе (двойной барьер);
{!! !!}— только для санитизированных полей из whitelist, линтер контракта это проверяет; - регулярные выражения: пользовательские regex (правила редиректов
is_regex, кастомные rules полей) — источник ReDoS. Ядро валидирует паттерн при сохранении (компилируемость + эвристика катастрофического бэктрекинга) и исполняет с лимитом (preg_*+ backtrack_limit, таймаут-обёртка). Произвольный regex от контент-менеджера клиента — запрещён capability-моделью, только роль студии; - выход — только
; JSON-ответы через API Resources (нет утечки скрытых полей); заголовки безопасности (CSP черезspatie/laravel-csp, X-Frame-Options, nosniff) — middleware ядра с конфигом. Механика CSP — ревизия ядра №2: nonce для инлайн-вставок (pixels/analytics уже рассчитывают на него), источники собираются каноническим фильтромsecurity.csp(тема — изtheme.json, модули — фильтром), режимыoff/report-only/enforceв группе настроекsecurity(внедрение всегда через report-only), SRI на ассеты темы.
Модуль «Статистика атак» (attack-monitor)
Обязательный инфра-модуль managed-парка (в одном ряду с бэкапом и health-check): CMS видит, что её атакуют, сама замедляет атакующего и отчитывается студии.
Детект (источники сигналов):
- паттерны в запросах: SQLi/XSS/path-traversal сигнатуры, зонды по чужим CMS (
/wp-login.php,/bitrix/admin), всплеск 404 (сканер), невалидные подписи вебхуков; - брутфорс: серии провалов логина по IP/аккаунту (admin, кабинеты, API-токены);
- флуд форм: срабатывания honeypot/капчи, превышения rate-limit, спам-паттерны в лидах;
- аномалии: резкий рост RPS с одного IP/подсети, перебор ID/slug (enumeration).
Реакция (эскалация по ступеням):
- rate-limit — штатный троттлинг (уже в ядре) — первый барьер;
- временная блокировка IP/аккаунта на уровне приложения (minutes → hours при повторе), капча-челлендж вместо жёсткого бана для серых случаев;
- эскалация на периметр: экспорт блок-листа для Caddy (reverse proxy студии уже терминирует весь трафик — баним до PHP, дёшево) — файл/API, который Caddy перечитывает; аналог fail2ban-петли;
- алерт: Telegram/email студии при превышении порогов; событие в телеметрию парка (флит-дашборд видит волну атак по всем сайтам — признак таргетированной кампании).
Статистика (Filament-дашборд):
- журнал
attack_events: время, IP/ASN/гео, тип сигнала, URL, вердикт (logged/challenged/ blocked), TTL блокировки; - агрегаты: топ-IP, топ-паттернов, динамика по дням, «до/после» блокировок;
- ретеншн и выгрузка (для абуз-жалоб хостеру/RKN-отчётности);
- ложные срабатывания: whitelist IP (офис клиента, мониторинг), разбан в один клик.
Границы модуля: это не полноценный WAF (не подменяет Caddy/CDN-защиту от L7-DDoS) — это прикладной слой: то, что видно только приложению (логины, формы, бизнес-аномалии), плюс статистика и эскалация. Донор паттернов: антифрод catalog/er (детект накрутки), freelance/project/src/app/Domains/Reputation/ (репутационные сигналы).
Безопасность как набор подсистем
Безопасность — не одна фича, а слой подсистем (см. каталог подсистем). В ядре — базовое (аутентификация, RBAC, политики, rate-limit, защита форм, фильтрация границ — выше). Остальное — модули под потребность сайта:
- Attack-monitor — статистика атак и автоблокировка (см. выше), обязателен в парке;
- 2FA (er, freelance) — двухфакторка для админки/кабинета;
- OAuth/соцвход — вход через Google/VK/Яндекс;
- Антиспам/антифрод (catalog, er) — защита форм и отзывов;
- Согласия/152-ФЗ — cookie-баннер, согласия на обработку ПДн, экспорт/удаление;
- Аудит действий (freelance, referendum) — журнал «кто что изменил»;
- Шифрование полей — encrypted casts в ядре;
- API-токены (Sanctum) — для внешнего доступа.
Чеклист безопасности ядра (фаза 0)
- [ ] Capability-слой модуля в контракте — что модулю можно/нельзя.
- [ ] Политика «модуль не пишет в таблицы ядра напрямую».
- [ ] Shield-декларация permission'ов обязательна для модуля.
- [ ] Rate-limit + CSRF + honeypot на всех формах ядра (не на совести клиента).
- [ ] Фильтрация пяти границ (формы/API/админка/файлы/вебхуки) — движок полей + query-builder + подписи.
- [ ] Санитизация rich-text на входе и выходе;
{!! !!}только по whitelist (линтер). - [ ] Валидация пользовательских regex (ReDoS): проверка паттерна + лимиты исполнения; произвольный regex — только роль студии.
- [ ] Заголовки безопасности (CSP, X-Frame-Options, nosniff) — middleware ядра; CSP с nonce, источники через фильтр
security.csp, внедрение через report-only. - [ ] Загрузки сканируются при включённой реализации
upload-scanner(карантинpendingдо вердикта); 0 реализаций — штатная деградация. - [ ] Attack-monitor: журнал атак, автоблокировка со ступенями, экспорт блок-листа в Caddy, алерты, дашборд.
- [ ] Базовое согласие ПДн (чекбокс +
consent_atна лиде) — в ядре; расширенное — модуль 152-ФЗ. - [ ] 2FA для ролей студии — обязательная политика managed-парка (модуль 2FA ставится всем сайтам по умолчанию: учётка разработчика открывает весь парк).
- [ ] Секреты — только
.env→config(), никогдаenv()в коде модуля. - [ ] Satis — управление доступом по клиенту (отзыв при расторжении).
- [ ] Обновления через worker/CLI, не HTTP (см. центр обновлений).