docs(narrative): v8.4 +§22 (CSP + prompt injection + антипаттерны)

§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 пароль в `<input type="text">`
  оригинала, партия 14.3.4)
- 22.12.2 — защита форм интеграций (vs API credentials в `<input type="text">`
  всех 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) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-06 14:32:13 +07:00
parent 6648fdd52f
commit 63b45217dc
3 changed files with 146 additions and 4 deletions
+4
View File
@@ -666,3 +666,7 @@ soft
новосибирск
санкт
петербург
# Security/IT-термины
санитизация
санитизируются
+140 -2
View File
@@ -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.214.3.4). Явное упоминание Биз-9 (2FA на всех тарифах).
- **§19.10 Outbound webhook** — переписан полностью: статус MVP (был v2 «не реализуется»), 7 линий доказательств отсутствия в оригинале (партии 9–15), привязка к OPEN-И-2 (amoCRM в спринте 1415, 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 в `<input type="text">`, 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
<div id="claude-agent-stop-container">
<button id="claude-agent-stop-button"><span>Stop Claude</span></button>
</div>
<div id="claude-agent-glow-border"></div>
<style id="claude-agent-animation-styles">@keyframes claude-pulse { ... }</style>
```
Источник инъекции не установлен (вероятнее всего — расширение браузера, версия 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.182.19](Analiz_originala_v8_3.md)) выявлены 3 практики, которые мы **строго не повторяем**. Этот раздел — фиксация наших защитных мер с прямой привязкой к найденным антипаттернам, чтобы при code review/PR review можно было быстро сослаться на конкретный пункт.
| # | Антипаттерн оригинала | Где обнаружен | Наша защита |
|---|---|---|---|
| 1 | **Пароль в `<input type="text">` plaintext в HTML профиля** | `/admin/user/account`, поле «Текущий пароль» (партия 14.3.4) | См. §22.12.1 |
| 2 | **API-credentials (api_key, client_secret, token) в `<input type="text">`** | Все 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 поля формы — `<input type="password">` |
| Пароль угоняется 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 в спринте 1415, далее Bitrix24/RetailCRM, см. [§19.10.1](#19101-назначение)):
| Угрозы оригинала | Наши требования |
|---|---|
| Токены видны на экране при заполненной карточке | Все credentials-поля — `<input type="password">` + 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. Инфраструктура и деплой
+2 -2
View File
@@ -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 в спринте 1415, 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 в `<input type="text">` (партия 14.3.4); API credentials в `<input type="text">` (партия 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 |