# Безопасность AGIL Calls

Документ описывает модель угроз, реализованные меры, честные ограничения MVP и
рекомендации по эксплуатации. Технические сигнатуры и примеры — в
`packages/security/README.md`; границы ответственности с приложением компании —
в `docs/integration-boundaries.md`.

## 1. Модель угроз

**Что защищаем**

| Актив | Риск |
|---|---|
| Медиа/аудио звонка | перехват, несанкционированная запись, дипфейк/подмена участника |
| Чат, файлы, записи, транскрипты | утечка, вредоносный контент, доступ по угаданной ссылке |
| Персональные данные (IP, имя, email, страна) | деанонимизация, нарушение GDPR/152-ФЗ |
| Пароли комнат | brute-force, утечка из хранилища/логов |
| API-ключи, join-токены, JWT-секрет | подделка токена, захват комнаты |
| Корпоративные интеграции (AI/knowledge-агент, вебхуки) | SSRF, подделка событий, утечка ключей |
| Конфиги тенантов и права | повышение привилегий, доступ к чужим функциям |
| Доступность | DoS на WebSocket/upload/join, переполнение памяти |

**Кто угрожает**

- внешний гость по ссылке: brute-force пароля комнаты, спам/флуд, обход
  гео-политики через VPN/заголовки, попытки SSRF через настраиваемые endpoint'ы;
- злоумышленник с украденной ссылкой/токеном: вход в активную комнату,
  повторное использование ссылки;
- недоверенный браузер участника: подмена клиентских эвристик, инъекции в чат/файлы;
- инсайдер/модератор: доступ к записям и аудиту, экспорт данных;
- вредоносный контент: HTML/SVG/JS в файлах, path traversal, огромные файлы;
- дипфейк/voice-clone: подмена личности участника;
- сеть/инфраструктура: MITM, перехват TURN-трафика, утечка секретов из репозитория;
- supply chain: скомпрометированный npm-пакет.

**Границы доверия**

- Браузер участника — недоверенный источник. Всё, что он присылает (имя, SDP,
  чат, файлы, `frameTimingRegularity`, `externalScore`), валидируется и
  ограничивается по размеру/схеме.
- HTTP-заголовки (`x-forwarded-for`, `x-agil-country`, `cf-ipcountry`)
  доверяются **только** при `AGIL_TRUST_PROXY=1` и только если сервер стоит за
  контролируемым reverse-proxy; иначе они игнорируются.
- Ответы внешних сервисов (ipapi, STT, AI-агент, knowledge-агент, детектор
  дипфейков) — недоверенный вход: таймауты, ограничение размера, валидация
  (zod/структурный парсинг), никакого исполнения.
- Секреты компании (AI-ключи и т.п.) хранятся только как **имя env-переменной**
  в конфиге тенанта; значения — в окружении/secret manager.

## 2. Реализованные меры (`@agil/security`)

### 2.1 Пароли комнат

- Хэш: scrypt `N=16384, r=8, p=1`, соль 16 байт, ключ 64 байта, формат
  `scrypt$N$r$p$<salt b64url>$<hash b64url>`.
- Проверка — `verifyPassword` через `crypto.timingSafeEqual`; битый/чужой/
  усечённый хэш → `false` без исключений. Параметры из строки валидируются с
  ограничениями (защита от DoS подделанным хэшем).
- Пароль нигде не хранится в открытом виде, не логируется и не возвращается API
  (только `passwordProtected: boolean`); в аудит попадает лишь `password.failed`.
- Lockout: `config.security.maxFailedJoinAttempts` за `lockoutSec`, событие
  `lockout` в аудите. Второй слой — rate-limit join (10/мин на IP+комнату).

### 2.2 Гео-политика и VPN

- `resolveGeo` **никогда не бросает**: таймаут (`AbortController`), сеть, 4xx/5xx,
  битый JSON → `{ source: 'none', vpn: false }`.
- `provider: 'header'` — страны из доверенных заголовков
  (`countryFromHeaders`), `provider: 'ipapi'` — внешний lookup с кэшем 10 мин,
  `provider: 'none'` — выключено.
- Код страны нормализуется и валидируется (ISO alpha-2, заглушки `XX/ZZ`
  отбрасываются). Пустой `allowedCountries` = разрешены все; неизвестная страна
  при непустом allow-list = отказ (`geo_blocked`, событие `geo.blocked`).
- `blockVpn`: флаг `vpn` из внешнего lookup (поля `security.vpn/proxy/tor`);
  при провайдере `header` VPN определить нельзя — не блокируем по VPN.
- GPS-локация (если включена в конфиге тенанта) — только по явному согласию
  участника (`gpsEnabled/requireConsent`); геолокация — чувствительные данные,
  хранить/логировать координаты в открытом виде нельзя.

### 2.3 Сессии и токены

- Join-токен — HS256 JWT с коротким TTL; секрет `AGIL_TOKEN_SECRET` только на
  сервере, в клиентский бандл не попадает; токен передаётся, но не хранится в
  логике.
- `sessionBinding` (`bindToIp`, `bindToDevice`) — привязка сессии к IP-хэшу
  (`hashIp`, не сырой IP) и/или устройству; нарушение — ре-аутентификация,
  событие `session.bound`.
- `security.requireToken`, `waitingRoom`, `lockRoom`, `moderatorJoinFirst` —
  серверный enforcement; права на функции — `resolveAccess` (§18 контракта),
  клиент лишь скрывает недоступный UI.

### 2.4 Rate-limit

`createRateLimiter` — фиксированное окно с bounded memory (LRU-eviction):
join 10/мин на IP+комнату, files/STT 30/мин на тенант, admin 120/мин, пароль —
`maxFailedJoinAttempts` за `lockoutSec`. WS-лимит 40 сообщений/с на сокет,
3 нарушения → закрытие 1008. Это защита от случайного/простого флуда, а не от
распределённого L3/L4-DoS (см. §4).

### 2.5 Аудит и приватность

- JSONL per tenant, append-only, in-memory ring 2000 для admin API.
- Сырой IP в аудит **не** пишется: только `hashIp(ip, AGIL_IP_SALT)` (sha256,
  128 бит). Соль делает хэши несопоставимыми между инсталляциями.
- События `AuditEventType`: создание/закрытие комнаты, join allowed/denied,
  password.failed, geo.blocked, lockout, config.updated, recording.*, file.shared,
  chat.sent, integrity.flagged, session.bound, admin.action.
- Ошибки аудита **не проглатываются**: если запись не удалась, операция не
  должна выглядеть успешной.

### 2.6 Файлы и записи

- Хранение: `<AGIL_DATA_DIR>/files|recordings/<id>` + `<id>.json` метаданные.
- Скачивание — только по подписанной ссылке
  `?sig=<HMAC-SHA256 base64url>&exp=<epoch ms>`; подпись покрывает
  `file:<id>:<exp>` / `recording:<id>:<exp>`, проверка constant-time.
- TTL ссылки (`AGIL_FILE_TTL_SEC`, default 86400), `id` — UUID и строгая
  валидация `[A-Za-z0-9_-]` (path traversal невозможен), размеры лимитируются
  (`AGIL_MAX_UPLOAD_MB`, лимит записей), MIME-allow-list и запрет активного
  контента (HTML/SVG/JS), имя файла санитизируется (`sanitizeFilename`).
- Отдача бинаря — с `Content-Disposition: attachment` (на стороне сервера) и
  `X-Content-Type-Options: nosniff`; не позволять браузеру рендерить загрузки на
  origin API.
- Рекомендуется антивирусная проверка на периметре (в MVP её нет).

### 2.7 Медиа-интеграция (anti-deepfake): честно

- `MediaIntegrityService` — эвристический риск-скор 0..1 (тайминги кадров,
  liveness, наличие лица, внешний скор, гео-несовпадение) с порогом
  `mediaIntegrity.flagThreshold`; результат — `unknown|verified|suspicious` +
  флаги. Это не доказательство подделки.
- Браузерные эвристики вероятностные и обходятся современными дипфейками;
  `externalScore` приходит только от внешнего детектора
  (`AGIL_INTEGRITY_URL/AGIL_INTEGRITY_KEY`), который вызывает сервер.
- Юридически значимая детекция требует внешней модели + liveness-challenge +
  провенанс-меток (C2PA) и организационных процедур. В MVP этого нет — roadmap.
- `autoMuteOnHighRisk` — реакция на риск, а не наказание; решение принимает
  модератор, событие `integrity.flagged` идёт в аудит.

### 2.8 STT и корпоративный AI

- `SttService` — OpenAI-совместимый endpoint, `Authorization: Bearer` только если
  ключ задан; ключ не логируется и не попадает в URL/ошибки; таймаут
  `AbortController`.
- Не настроено → `isConfigured() === false`, эндпоинт 501 с подсказкой.
- AI/knowledge-агент: ключ только по имени env-переменной; запросы подписаны HMAC
  (`x-agil-signature`), ответы валидируются zod (недоверенный вход), таймауты и
  один ретрай на 5xx/сеть.
- Транскрипты/записи — персональные данные: срок хранения `retentionMin`,
  согласие участников (`recording.consentRequired`) и уведомление
  (`notifyParticipants`) — обязанность сервера/тенанта.

### 2.9 Сетевые вызовы компании (SSRF/подпись)

Сервер вызывает **настраиваемые тенантом** endpoint'ы: AI-агент, knowledge-агент,
webhooks, внешний детектор медиа (`AGIL_INTEGRITY_URL`) и STT. Это классический
вектор SSRF, если URL не проверять.

- `validateOutboundUrl(raw, { allowHttp?, allowPrivate? })` — синхронная проверка
  **перед каждым вызовом** и на старте для `AGIL_INTEGRITY_URL`:
  - только `https` (в проде; `allowHttp` — только для локальной разработки);
  - URL ≤ 500 символов, без `user:password@`;
  - блокируются `localhost`, `127.0.0.0/8`, `0.0.0.0/8`, `169.254.0.0/16`
    (cloud metadata), `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `::1`,
    `fc00::/7`, `fe80::/10`, IPv4-mapped формы (`::ffff:127.0.0.1`),
    числовые/hex-формы IPv4;
  - `allowPrivate: true` — только dev, никогда в проде; DNS-имена не резолвятся
    синхронно, поэтому дополнительно нужен egress-firewall/DNS-pinning.
- HMAC-заголовки для вебхуков: `x-agil-signature` (hex HMAC-SHA256 по
  `${timestamp}.${rawBody}`), `x-agil-delivery`, `x-agil-event`,
  `x-agil-timestamp`; `signWebhookPayload`/`verifyWebhookSignature`
  (constant-time + окно timestamp, default 300 с) — защита от подделки и replay.
- Таймауты обязательны на всех сетевых вызовах (`AbortController`), размеры
  ответов ограничиваются, ошибки наружу отдаются без деталей чужих сервисов.
- Секреты компании хранятся только в env/secret manager; в `TenantConfig` —
  имя переменной (`secretEnv`, `apiKeyEnv`).

### 2.10 Вебхуки: подпись и стык с приложением компании

Подробный контракт — `docs/integration-boundaries.md`.

- Подписка: `integrations.webhooks[]` (`url`, `events`, `secretEnv`, `retries`,
  `timeoutMs`).
- Доставка: `POST` JSON `WebhookPayload` с заголовками `x-agil-signature`,
  `x-agil-delivery`, `x-agil-event`, `x-agil-timestamp`; подпись —
  `hex HMAC-SHA256(secret, `${timestamp}.${rawBody}`)`.
- Секрет живёт в env по имени `secretEnv`; в конфиге и логах его нет. Ключ не
  меняется автоматически — ротация через env/secret manager.
- Получатель обязан: проверить подпись constant-time, проверить окно timestamp
  (replay), ответить 2xx быстро (тяжёлую обработку — асинхронно), дедуплицировать
  по `x-agil-delivery`.
- Ретраи с backoff, таймаут, лог последних 200 доставок (in-memory) для
  диагностики; события: `room.created/closed`, `participant.joined/left`,
  `chat.message`, `file.shared`, `recording.ready`, `transcript.ready`,
  `analysis.ready`, `knowledge.answer`, `compliance.finding`, `security.event`.
- Наш сервер **не** содержит бизнес-логику компании (каталог сотрудников, wiki,
  корпоративные правила), а компания не реализует медиа-часть — это граница
  ответственности, см. `docs/integration-boundaries.md`.

## 3. Что НЕ решает MVP

- **E2EE медиа**: нет; транспорт защищён DTLS-SRTP (WebRTC) + TLS signalling/TURN,
  но сервер (SFU) видит медиа. E2EE — roadmap.
- **Надёжный deepfake-детект**: только эвристики + внешний детектор, вероятностно.
- **SSO/SCIM/OIDC**: `identity.mode:'oidc'` принимается конфигом, но проверка
  JWKS не реализована (501); MVP-путь — HS256-токены от компании.
- **2FA/MFA, RBAC-движок**: только роли host/moderator/guest + `resolveAccess`.
- **Антивирус/DLP** для файлов и записей: нет (только MIME/size/HMAC).
- **Горизонтальное масштабирование**: rate-limit и аудит in-memory/локальный
  диск; multi-node требует Redis/S3.
- **C2PA/провенанс записей**, автоматический retention-экспорт, SIEM-коннектор.
- **Защита от L3/L4-DoS**: вне приложения (CDN/WAF/прокси).

## 4. Рекомендации по эксплуатации

**Транспорт и периметр**

- Только TLS (HTTPS/WSS) на периметре: reverse-proxy (nginx/Caddy/ALB) + HSTS,
  редирект HTTP→HTTPS. `AGIL_PUBLIC_ORIGIN` — https-origin.
- TURN: `turns:` с TLS-сертификатом, ephemeral credentials (TURN REST
  `time-based`), а не статические логин/пароль в клиенте; STUN — публичный.
- `AGIL_TRUST_PROXY=1` включать **только** за своим прокси, который сам
  перезаписывает `x-forwarded-for`/гео-заголовки; иначе клиент подделает страну.
- WAF/CDN: rate-limit на join/files, лимит размера тела, блокировка подозрительных
  гео/ASN — до приложения.

**Секреты**

- `AGIL_TOKEN_SECRET`, `AGIL_IP_SALT`, файловые секреты, ключи STT/AI —
  только через secret manager/env, не в git, с ротацией и разными значениями
  на окружения. Соль IP — стабильная в рамках инсталляции, иначе корреляция
  аудита ломается; при её смене старые хэши не сопоставимы (это ок).
- Не логировать заголовки `authorization`, `x-api-key`, тела с паролями/
  транскриптами; `console.log` секретов запрещён на ревью.

**Данные и соответствие**

- GDPR/152-ФЗ: согласие на запись (`recording.consentRequired`,
  `notifyParticipants`), минимизация (IP — хэшем, страна — кодом), сроки
  хранения (`retentionMin`, `AGIL_FILE_TTL_SEC`), право на удаление
  (`DELETE /v1/admin/recordings/:id`, удаление файлов), DPA с тенантом,
  оценка трансграничной передачи (ipapi/STT/AI — за пределами РФ/ЕС?);
  для 152-ФЗ — локализация БД с персональными данными.
- Каталоги `audit/`, `files/`, `recordings/` — вне web-root, права только сервису,
  бэкапы шифровать; диск с записями — шифрование at rest.
- Аудит хранить не менее срока, оговорённого политикой тенанта; выгрузка — через
  admin API/log shipping, не через публичные пути.

**Мониторинг**

- Алерты: всплеск `password.failed`/`join.denied`/`geo.blocked`/`lockout`,
  `integrity.flagged`, 5xx на webhooks/STT/AI, рост `rate_limited`, ошибки аудита
  (запись не удалась), размер каталогов files/recordings.
- Проверять доставки вебхуков (`GET /v1/admin/webhooks`, recentDeliveries) и
  валидность внешних endpoint'ов при изменении конфига.

## 5. Ссылки

- Контракт и API: `docs/contract.md` (§12 — security, §14 — REST, §18–§21).
- Интеграционные границы и вебхуки: `docs/integration-boundaries.md`.
- Аудит безопасности и roadmap-меры: `docs/security-audit.md`.
- Развёртывание: `docs/deployment.md`.
- API библиотеки и примеры: `packages/security/README.md`.
