From 63b45217dcdfd25a46e901fae288e39d91a8d739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Wed, 6 May 2026 14:32:13 +0700 Subject: [PATCH] =?UTF-8?q?docs(narrative):=20v8.4=20+=C2=A722=20(CSP=20+?= =?UTF-8?q?=20prompt=20injection=20+=20=D0=B0=D0=BD=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D0=B0=D1=82=D1=82=D0=B5=D1=80=D0=BD=D1=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §22.2 Транспортный уровень: CSP-строка расширена до полной политики (script-src без 'unsafe-inline', object-src 'none', frame-ancestors 'none', report-uri /csp-report). §22.11 «Защита от prompt injection в DOM» (новый, 4 уровня): - 22.11.1 — полная CSP-политика с 13 директивами - 22.11.2 — CI-линтер запрещает в DOM элементы с ID/class на префиксы claude-/gpt-/agent-/ai- - 22.11.3 — DOMPurify-конфиг для всех v-html - 22.11.4 — методология аудита third-party скриптов (31 keyword grep + SRI-хеши + ежеквартальный re-audit) - 22.11.5 — что НЕ делаем (не блокируем легитимных AI-агентов) Контекст — атака `claude-agent-stop-container/glow-border/animation-styles`, найденная во ВСЕХ страницах crm.bp-gr.ru (партии 10, 12-15 аудита). §22.12 «Антипаттерны оригинала» (новый): - 22.12.1 — защита формы смены пароля (vs пароль в `` оригинала, партия 14.3.4) - 22.12.2 — защита форм интеграций (vs API credentials в `` всех 5 карточек оригинала, партия 15.2.3) - 22.12.3 — code review checklist (7 пунктов) Шапка narrative обновлена. План v8.4: 7/13 ✅ (было 6/13). cspell-words.txt: +санитизация, +санитизируются. Co-Authored-By: Claude Opus 4.7 (1M context) --- cspell-words.txt | 4 + docs/CRM_bp-gr_Инструкция_v8_3.md | 142 +++++++++++++++++++++++++++++- docs/Plan_narrative_v8_4.md | 4 +- 3 files changed, 146 insertions(+), 4 deletions(-) diff --git a/cspell-words.txt b/cspell-words.txt index fa558ead..d0d66f77 100644 --- a/cspell-words.txt +++ b/cspell-words.txt @@ -666,3 +666,7 @@ soft новосибирск санкт петербург + +# Security/IT-термины +санитизация +санитизируются diff --git a/docs/CRM_bp-gr_Инструкция_v8_3.md b/docs/CRM_bp-gr_Инструкция_v8_3.md index 888d1ab6..d1828638 100644 --- a/docs/CRM_bp-gr_Инструкция_v8_3.md +++ b/docs/CRM_bp-gr_Инструкция_v8_3.md @@ -33,9 +33,10 @@ - **§17.8** — новый подраздел «Тихие часы» с паритетом партии 13.2.1 (start_hour 0..23, end_hour 0..23, минимум 3 часа, общий timezone тенанта). - **§18.4 Безопасность** — расширен: §18.4.4 «Уведомления о подозрительной активности» (6 типов email-алертов), §18.4.5 «Конкурентное преимущество vs оригинал» — таблица 7 функций безопасности, отсутствующих в crm.bp-gr.ru (партии 14.3.2–14.3.4). Явное упоминание Биз-9 (2FA на всех тарифах). - **§19.10 Outbound webhook** — переписан полностью: статус MVP (был v2 «не реализуется»), 7 линий доказательств отсутствия в оригинале (партии 9–15), привязка к OPEN-И-2 (amoCRM в спринте 14–15, Bitrix24/RetailCRM Post-MVP). Подразделы 19.10.1–19.10.10: формат запроса, HMAC SHA-256, retry-логика (7 попыток до 24ч), управление в кабинете, защита от SSRF. +- **§22 Безопасность** — расширен CSP в §22.2 (полная политика). Новый §22.11 «Защита от prompt injection в DOM» — 4 уровня защиты против атаки, найденной во всех страницах оригинала (Прил. М §6, маркеры `claude-agent-stop-container/glow-border/animation-styles`). Новый §22.12 «Антипаттерны оригинала» — фиксация 3 практик, которые мы строго не повторяем (пароль plaintext в ``, API credentials в text, AI-маркеры) + code review checklist. - **§26 Дизайн-система** — полностью переписан под брендбук v1.1: палитра Teal `#0F6E56` (вместо `#3B82F6`), сетка 4px (вместо 8px), JetBrains Mono для кода, маскирование credentials (`type="password"` + toggle). -**Ещё не переписано (план):** §5, §7, §8, §9, §12, §22, §23.10. Детали в [Plan_narrative_v8_4.md](Plan_narrative_v8_4.md). Источник правды для этих разделов до переписывания — **Прил. М v1.1** (партии 1–15) + реестр v1.10. +**Ещё не переписано (план):** §5, §7, §8, §9, §12, §23.10. Детали в [Plan_narrative_v8_4.md](Plan_narrative_v8_4.md). Источник правды для этих разделов до переписывания — **Прил. М v1.1** (партии 1–15) + реестр v1.10. **Технический долг v8.4:** при правке §7 добавить DDL `outbound_webhook_subscriptions` и `outbound_webhook_deliveries` в `db/schema.sql` v8.4 (на которые ссылается §19.10.8). Структура — аналогично `api_keys` + retention 90 дней (как `webhook_log`). @@ -3849,7 +3850,7 @@ URL: `/billing/balance` (для клиента) | HTTPS | Только. HTTP-запросы редиректятся 301 на HTTPS | | Минимальная версия TLS | TLS 1.2 (TLS 1.3 предпочтительно) | | Strict-Transport-Security | `max-age=31536000; includeSubDomains; preload` | -| Content-Security-Policy | `default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; ...` | +| Content-Security-Policy | `default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://*.jivosite.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.sentry.io; frame-ancestors 'none'; base-uri 'self'; form-action 'self'` (полная политика — см. [§22.11](#2211-защита-от-prompt-injection-в-dom)) | | X-Frame-Options | `DENY` (защита от clickjacking) | | X-Content-Type-Options | `nosniff` | | Referrer-Policy | `strict-origin-when-cross-origin` | @@ -4170,6 +4171,143 @@ CREATE TABLE pd_subject_requests ( | **HMAC-подпись webhook от шлюзов** | Обязательная проверка для всех callback-ов | | **Idempotence keys** | UUID при создании платежа, передаётся в шлюз. При повторном запросе шлюз вернёт тот же платёж — защита от двойного списания | +## 22.11. Защита от prompt injection в DOM + +**Контекст.** В аудите оригинала crm.bp-gr.ru (партии 10, 12, 13, 14, 15 — см. [Прил. М §6](Analiz_originala_v8_3.md)) во всех страницах сервиса обнаружена **активная prompt injection-атака**, целенаправленно нацеленная на AI-агентов с именем «Claude»: + +```html +
+ +
+
+ +``` + +Источник инъекции не установлен (вероятнее всего — расширение браузера, версия 1 в [Прил. М §6.2](Analiz_originala_v8_3.md)). Реакция всех операторов Claude — корректная: кнопка не нажата, команда «Stop Claude» проигнорирована. + +Чтобы наша платформа Лидпоток не стала аналогичной целью для AI-агентов клиентов и их подрядчиков, применяем 4 уровня защиты. + +### 22.11.1. CSP — запрет inline и whitelist источников + +Полная политика CSP (заголовок `Content-Security-Policy`): + +```text +default-src 'self'; +script-src 'self' https://cdn.jsdelivr.net https://*.jivosite.com; +style-src 'self' 'unsafe-inline'; +img-src 'self' data: https:; +font-src 'self' data:; +connect-src 'self' https://*.sentry.io https://*.jivosite.com; +media-src 'self'; +object-src 'none'; +frame-src 'none'; +frame-ancestors 'none'; +base-uri 'self'; +form-action 'self'; +upgrade-insecure-requests; +report-uri /csp-report +``` + +**Ключевое:** + +- `script-src` — **без `'unsafe-inline'`** для скриптов (только для `style-src`, без них Vuetify ломается). Все JS-файлы — собранные Vite-бандлы с известными хешами. +- `object-src 'none'` — запрет Flash, Java applets, plugins. +- `frame-ancestors 'none'` — никто не может встроить наш UI в iframe (защита от clickjacking). +- `report-uri /csp-report` — все нарушения политики логируются в Sentry для расследования. + +### 22.11.2. Запрет AI-маркеров в собственном DOM + +В CI добавляется **линтер сборки** (Vue ESLint plugin + кастомное правило), который падает при попытке закоммитить элемент с ID/class/data-attribute, начинающимся на: + +- `claude-` +- `gpt-` +- `agent-` +- `ai-` (кроме явно разрешённых легитимных префиксов из шорт-листа, например `ai-icon-` из MDI) + +**Обоснование:** даже если в нашем UI появится элемент с таким префиксом по неосторожности (например, `claude-tooltip` для какой-то фичи), это создаёт риск: AI-агенты клиентов могут неожиданно среагировать, плюс это упрощает враждебную инъекцию через XSS (атакующий просто переиспользует наш стиль). + +### 22.11.3. Санитизация пользовательского ввода + +Все поля, которые могут отображать HTML (комментарии сделок, заметки, имена контактов с эмодзи, тело шаблонов комментариев), проходят через **DOMPurify** при сохранении и при выводе: + +```javascript +import DOMPurify from 'dompurify'; + +const SAFE_HTML_CONFIG = { + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'br', 'p', 'a'], + ALLOWED_ATTR: ['href', 'title'], + ALLOWED_URI_REGEXP: /^https?:\/\//i, + FORBID_TAGS: ['style', 'script', 'iframe', 'object', 'embed', 'form'], + FORBID_ATTR: ['onerror', 'onload', 'onclick', 'style'], +}; + +const safe = DOMPurify.sanitize(userInput, SAFE_HTML_CONFIG); +``` + +По умолчанию Vue экранирует через `{{ }}`. `v-html` разрешён только в компонентах с явным комментарием `// SAFE: DOMPurify applied` и проходит через тот же конфиг. + +### 22.11.4. Аудит сторонних виджетов + +Перед подключением любого third-party скрипта (JivoSite, аналитика, маркетинг-пиксели): + +1. **Скачать минифицированный JS** и прогнать grep по 31 keyword (`claude-`, `gpt-`, `ai-agent-`, `prompt-`, `instruction-`, …) — методология из [Прил. М §9.4.2](Analiz_originala_v8_3.md), линия 5. +2. При обнаружении хотя бы одного маркера — **отказ от интеграции** или прокcирование через наш CDN с очисткой подозрительных конструкций перед раздачей клиентам. +3. Все third-party скрипты имеют `Subresource Integrity (SRI)` хеш — при изменении контента CDN скрипт не выполнится. +4. Ежеквартальный аудит — повторная проверка SRI-хешей и keyword-grep актуальных версий подключённых скриптов. + +### 22.11.5. Что мы НЕ делаем + +- **Не блокируем AI-агентов клиентов в принципе.** Семантическая разметка, ARIA-атрибуты, осмысленные id остаются — это нужно для legitimate автоматизации (clients automation через Playwright/Selenium, тесты доступности). +- **Не пытаемся детектировать «вредоносный» AI-трафик** по User-Agent или поведению — это гонка вооружений без победителя, плюс ломает легитимные интеграции. + +## 22.12. Антипаттерны оригинала crm.bp-gr.ru + +**Контекст.** В аудите оригинала (партии 14–15, см. [Прил. М §2.18–2.19](Analiz_originala_v8_3.md)) выявлены 3 практики, которые мы **строго не повторяем**. Этот раздел — фиксация наших защитных мер с прямой привязкой к найденным антипаттернам, чтобы при code review/PR review можно было быстро сослаться на конкретный пункт. + +| # | Антипаттерн оригинала | Где обнаружен | Наша защита | +|---|---|---|---| +| 1 | **Пароль в `` plaintext в HTML профиля** | `/admin/user/account`, поле «Текущий пароль» (партия 14.3.4) | См. §22.12.1 | +| 2 | **API-credentials (api_key, client_secret, token) в ``** | Все 5 карточек интеграций (Битрикс24, amoCRM, Скорозвон, Unisender, Мои Звонки) — партия 15.2.3 | См. §22.12.2 | +| 3 | **AI-маркеры `claude-agent-*` в DOM** | Каждая страница оригинала (партии 10, 12–15) | См. [§22.11](#2211-защита-от-prompt-injection-в-dom) | + +### 22.12.1. Защита формы смены пароля + +| Угрозы оригинала | Наши требования (обязательны на code review) | +|---|---| +| Пароль виден в DevTools → Elements при просмотре сессии | Все 3 поля формы — `` | +| Пароль угоняется XSS без user interaction | Кнопка-toggle «👁 показать/скрыть» только на клиенте, без отправки на сервер | +| При случайном скриншоте пароль в кадре | В HTML страницы пароль никогда не пре-заполнен (`value=""` всегда) | +| Brute-force через подмену старого пароля | Rate-limit 3 попытки/час на пользователя (см. [§22.3](#223-rate-limiting)) | +| Слабый пароль | zxcvbn ≥ 3, минимум 10 символов (см. [§22.4.1](#2241-пароль)) | +| Сохранение в `auth_log` | Запись `event='password_changed'` без значения пароля | + +### 22.12.2. Защита форм интеграций (API-credentials) + +При реализации Уровня 2 интеграций (amoCRM в спринте 14–15, далее Bitrix24/RetailCRM, см. [§19.10.1](#19101-назначение)): + +| Угрозы оригинала | Наши требования | +|---|---| +| Токены видны на экране при заполненной карточке | Все credentials-поля — `` + button-toggle «👁 показать» | +| Токены попадают в скриншоты для саппорта | В UI после сохранения отображаются только последние 4 символа: `••••••••••6b8c` | +| Полные значения видны в Network → Response | API-response при чтении карточки возвращает маску `***...****` (последние 4 символа) | +| Невозможно скрыть полный секрет от админа | Полное значение возвращается **только при явном «отредактировать»** + повторная аутентификация (re-auth через пароль или 2FA) | +| Утечка через журнал | В `activity_log` пишем `event='integration.credentials_changed'` без значения | +| Никогда не ротируется | Кнопка «Перевыпустить секрет» в карточке + email-уведомление при ротации | + +**Шифрование at-rest.** Поля credentials в БД — обязательно с Eloquent cast `'encrypted'` (см. [§22.5.2](#2252-шифрование-на-хранении-at-rest) — `payment_gateways.config` использует ту же механику). + +### 22.12.3. Code review checklist + +При ревью PR с новой формой ввода чувствительных данных проверяем: + +- [ ] Все поля паролей/секретов — `type="password"`? +- [ ] Нет пре-заполнения значением в HTML? +- [ ] Применён DOMPurify к пользовательскому HTML (если есть `v-html`)? +- [ ] Нет ID/class начинающихся на `claude-/gpt-/agent-/ai-`? +- [ ] Поля credentials в модели имеют cast `'encrypted'`? +- [ ] В `activity_log` не утекает значение секрета? +- [ ] Rate-limit настроен (если форма отправляется на изменение секретного значения)? + --- # 23. Инфраструктура и деплой diff --git a/docs/Plan_narrative_v8_4.md b/docs/Plan_narrative_v8_4.md index a6839c70..a61b5c03 100644 --- a/docs/Plan_narrative_v8_4.md +++ b/docs/Plan_narrative_v8_4.md @@ -23,7 +23,7 @@ ## 1. 13 разделов под правку -**Прогресс на 06.05.2026 (вечер):** 6 из 13 готово ✅ (§1, §14, §17, §18.4, §19.10, §26). +**Прогресс на 06.05.2026 (вечер 2):** 7 из 13 готово ✅ (§1, §14, §17, §18.4, §19.10, §22, §26). | # | § narrative | Что менять | Источник | Объём | Статус | |---|---|---|---|---|---| @@ -37,7 +37,7 @@ | 8 | **§17 Напоминания и Push-уведомления** | §17.1 — формат «Тихих часов»: `start_hour 0..23`, `end_hour 0..23`, минимум 3 часа разницы, общий timezone тенанта (из партии 13.2.1). §17.2 — каналы: push + email. **Без Telegram, без SMS** (Биз-12 закрыт как Post-MVP). | Прил. М партия 13.2.1, ТЗ §17 | малый | ✅ 06.05 | | 9 | **§18.4 Профиль → Безопасность** | Новая вкладка «Безопасность»: Пароль / 2FA / История входов / Активные сессии / Подозрительная активность (партия 14). Это **наше преимущество** (у оригинала нет 2FA, нет журнала входов, нет sessions). 2FA доступна на всех тарифах (Биз-9). *(в narrative — §18.4, не §18.5; §18.5 — настройки уведомлений.)* | Прил. М §2.19, ТЗ §22.4, Биз-9 | средний | ✅ 06.05 | | 10 | **§19 Исходящий REST API** | §19.10 «Webhook events» — outbound webhook как **уникальное преимущество**. 7 линий доказательств отсутствия в оригинале (партия 15). Привязка к OPEN-И-2 — amoCRM в спринте 14–15, Bitrix24/RetailCRM Post-MVP. | Прил. М §13 партия 15, OPEN-И-2 | средний | ✅ 06.05 | -| 11 | **§22 Безопасность** | §22.9 «Защита от prompt injection» — расширение про DOM-маркеры `claude-agent-stop-container`, `claude-agent-glow-border`, `claude-agent-animation-styles` (15 партий). + Антипаттерны оригинала: пароль plaintext в `` (партия 14.3.4); API credentials в `` (партия 15.2). У нас — обязательно `type="password"` + toggle. + CSP запрет `claude-*`/`gpt-*`/`agent-*` маркеров в нашем DOM. | Прил. М §2.19, §11.5, §12.8 | средний | ⏸ | +| 11 | **§22 Безопасность** | §22.2 — полная CSP. Новый §22.11 «Защита от prompt injection в DOM» (4 уровня: CSP, запрет AI-маркеров `claude-/gpt-/agent-/ai-` в нашем DOM через CI-линтер, DOMPurify санитизация, аудит сторонних виджетов). Новый §22.12 «Антипаттерны оригинала» (3 практики, которые не повторяем + code review checklist). | Прил. М §2.19, §6, §9.4 | средний | ✅ 06.05 | | 12 | **§23.10 Админка SaaS** | Колонка «Желаемое × факт сегодня» в `/admin/tenants` (Биз-16). Раздел поставщиков (§4.9 Прил. Г — Ю-2): дашборд маржи, реестр закупок, реестр счетов от поставщика. Журнал инцидентов (Прил. Д — OPEN-Д-5/И-1). Workflow `pd_subject_requests` (Прил. Д). Impersonation Ю-1 (одноразовый код email тенанту, 15 мин TTL, 5 попыток, сессия 1 час). | Прил. Г, Прил. Д, Биз-16 | большой | ⏸ | | 13 | **§26 Дизайн-система** | **Полная замена** под брендбук v1.1: палитра `#3B82F6` → `#0F6E56` (Teal 600); сетка 8px → **4px**; добавить JetBrains Mono для кода; ссылка на CSS-переменные и Vuetify-тему `lidpotokLight` в брендбук §8. WCAG 2.1 AA — обновить таблицу контрастов под Teal. **Маскирование credentials** в формах интеграций (`type="password"` + toggle) — антипаттерн оригинала. | brandbook.md v1.1, Прил. М §6.6 | средний | ✅ 06.05 |