Блог
Технические обзоры

Встраиваемые чат-виджеты: архитектура и безопасность

Двухшаговый loader и sandboxed iframe в основе виджета Simmetric Chat: изолированные сессии, rate limiting и многоуровневый доступ к базе знаний.

Simmetric Team7 мин чтения30/04/2026

Ваша база знаний - это продуктовая поверхность

Документы, которые пишет ваша команда - продуктовая документация, статьи поддержки, внутренние вики - отвечают на те же вопросы, которые посетители вашего сайта уже задают. Встроенный чат-виджет превращает эту базу знаний в разговорную входную дверь: посетители спрашивают своими словами, а ответы приходят с цитатами, опираясь на те workspaces, которые вы решили открыть.

Сложная часть - не чат, а встраивание почти недоверенного ПО на чужую страницу без превращения в поверхность атаки. Виджет-система Simmetric Chat - уровня Enterprise - построена вокруг этого ограничения. В этой статье разбирается архитектура: двухшаговый embed, sandboxed-рантайм, изоляция сессий, rate limiting и многоуровневая модель доступа к базе знаний, которая решает, чего вопрос посетителя может достичь, а чего нет.

Двухшаговый embed: loader и sandboxed iframe

Виджет поставляется в два шага, и это разделение осознанное:

  1. Скрипт-loader. Добавьте на свой сайт тег script, указывающий на widget-сервис с вашим widget ID. Loader - небольшой JavaScript-файл, создающий iframe на хост-странице.
  2. Панель iframe. Iframe загружает автономное приложение Preact - собранное как IIFE-бандл с инлайн-CSS - которое рендерит плавающую кнопку, панель чата и всё взаимодействие внутри собственного документа.
html
<script src="https://your-simmetric-chat.com/widget/loader.js?widgetId=WIDGET_ID"></script>

Почему бы не внедрять интерфейс чата прямо в хост-страницу? Потому что интерполяция строк недоверенного контента в хост-страницу - это классический путь XSS, а весь контентный поток виджета почти недоверенный: вывод модели, фрагменты документов и ввод посетителей. Запуск интерфейса в sandboxed iframe с opaque origin устраняет этот класс риска полностью: содержимое чата никогда не выполняется в контексте вашей страницы, и стили виджета не могут конфликтовать с вашими (и наоборот).

Старые embed-пути через интерполяцию строк были удалены из продукта именно по этой причине - двухшаговый паттерн не опция среди прочих, а сам embed. IIFE-сборка виджета существует по той же причине: панель - единственный автономный файл без модульной системы и без загрузки внешних ресурсов в рантайме, что держит сетевую поверхность iframe предсказуемой.

Что где работает

Виджет затрагивает три процесса, и знание карты границ объясняет каждое следующее свойство безопасности:

  • Основной сервер владеет CRUD и брендингом виджетов, привязкой к workspaces, внутренним API, который вызывает widget-сервис, и lead- и analytics-эндпоинтами на стороне админа. Весь внутренний роутер виджетов привязан к фиче - когда фича виджетов выключена, он отвечает 402 на каждом маршруте.
  • Widget-сервис - публичная embed-поверхность: JS-loader, HTML iframe, создание сессий, выдача кэшированной конфигурации, SSE-прокси чата к основному серверу и lead-эндпоинт. Он аутентифицируется на основном сервере выделенным API-ключом и несёт собственные rate limiter'ы.
  • Iframe - клиент Preact: bootstrap сессии, SSE-стриминг, отображение цитат, предложенные вопросы, барьер согласия и lead-карточка.

Прокси чата пересылает тот же формат событий, что и внутренний streaming API - статусные сообщения, анонсы плана, потоковые токены, цитаты и терминальные события done/error, - так что посетитель получает тот же конвейер, которым пользуется ваша команда внутри, с правами виджета.

Изолированные сессии для анонимных посетителей

Посетители никогда не аутентифицируются; виджет чеканит идентичность за них. Когда панель открывается, она запрашивает сессию у widget-сервиса, который просит основной сервер создать её: 256-битный hex-токен на стороне сервера со сроком 24 часа. Каждый последующий запрос виджета валидируется против этой сессии - никакого JWT из приложений ваших пользователей, никаких cookie через источники, никаких средовых учётных данных любого рода.

Изоляция сессий - то, что держит состояние одного посетителя вне досягаемости другого: разговоры анонимны, счётчики и состояние rate limit'ов привязаны к сессионному токену, а не к IP или фингерпринту, и хост-страница никогда не видит токен - он живёт в storage самого iframe и передаётся хосту через небольшой версионированный протокол postMessage, где обе стороны валидируют против точного ожидаемого contentWindow, так что поддельные сообщения от других скриптов игнорируются.

Выдача конфигурации следует той же дисциплине. Конфигурация виджета - брендинг, приветствие, предложенные вопросы - передаётся в iframe через JSON-блок в документе самого iframe, инжектируемый на сервере, а не через URL-параметры, которые посетитель мог бы подменить. Посетитель не может повлиять ни на что, что сервер не разрешает сам: недавнее добавление закрепляет это ещё сильнее, позволяя конфигурации каждого виджета выбирать LLM-провайдера и модель, обслуживающую его ответы в чате, - они разрешаются на сервере при каждом запросе чата и никогда не передаются клиентом.

Rate limiting как контракт публичной поверхности

Виджет - публичный эндпоинт на вашей инфраструктуре, поэтому контроль злоупотреблений - часть архитектуры, а не запоздалая мысль. Лимиты расслоены по поверхностям:

  • Лимиты чата на сессию: 20 сообщений в час и 5 разговоров в день на сессию; превышенные лимиты отвечают 429 с подсказкой retryAfter, и виджет показывает предупреждение о лимите вместо молчаливого отказа.
  • API-лимитер по ключу: вызовы widget-сервиса к основному серверу ограничены по хэшу API-ключа, так что сбойное развёртывание не может исчерпать общую поверхность.
  • Lead-лимитер: эндпоинт lead capture - самый узкий барьер: 3 запроса в час, потому что публичная форма - именно то место, где концентрируется автоматизированный абьюз.
  • CORS fail-closed: заголовок Origin каждого запроса проверяется против списка разрешённых источников самого виджета, редактируемого из админ-панели. Нера́зрешённые preflight-запросы отклоняются, а если проверка источника не может обратиться к базе, доступ запрещается, а не разрешается.

Результат - публичная чат-поверхность, которая изящно деградирует под нагрузкой или абьюзом, а не рушится или отключает всех.

Многоуровневый доступ к базе знаний

Виджет должен отвечать из знаний, которые вы намерены опубликовать, - и не более. Расслоение доступа обеспечивается структурно, а не промптом:

  • Whitelist workspaces. Каждый виджет привязан к одному или нескольким workspaces, и whitelist атомарно заменяется при каждом обновлении. Знание, из которого виджет может черпать, - ровно объединение векторных коллекций и документов привязанных workspaces: support-виджет может видеть ваш публичный документационный workspace, никогда не касаясь данных внутренних проектов.
  • Привязка к фиче. Создание виджетов требует флага фичи виджетов и соблюдает числовой лимит виджетов тарифа, так что поверхность не может расти молча.
  • Цитаты как границы. Ответы приходят с цитатами, указывающими только в разрешённые workspaces, - посетитель может проверить, откуда взялся ответ, и след цитаты не может указывать вне whitelist.

Брендинг - основной цвет, имя бота, логотипы, позиция - это конфигурация на виджет, рендеримая через CSS custom properties внутри iframe, так что консалтинговая фирма может представить ассистента как собственного у клиента. Развёртывания уровня Community получают разумные значения по умолчанию при пустых полях; полная кастомизация и white-labeling - enterprise-флаги.

Чек-лист безопасности, кратко

Каждый слой выше существует потому, что виджет - это ПО, которое вы отправляете внутрь чужой границы доверия:

  • Двухшаговый loader + sandboxed iframe с opaque origin - никакого XSS через интерполяцию строк
  • Конфигурация, инжектируемая сервером - ничего значимого для безопасности не приходит от клиента
  • Model pin, разрешаемый сервером - модель, отвечающая на запросы, не может быть подменена браузером посетителя
  • Анонимные 256-битные сессии со сроком 24 часа - никаких средовых учётных данных
  • Расслоённые rate limit'ы с 429 + retryAfter - изящная деградация под абьюзом
  • Динамический fail-closed CORS - доверие к источнику решается ровно в одной точке
  • Атомарный whitelist workspaces - доступ к знаниям структурен, а не возложен на промпт

От админ-панели до работающего сниппета - путь в несколько минут: создайте виджет, добавьте workspaces в whitelist, добавьте источники, вставьте тег script. Полный гид по интеграции покрывает брендинг, протокол simmetric:* postMessage и lead capture от начала до конца.

Читайте полное руководство в документации →

Похожие статьи

Синтез-конвейер: противоречия, бюджеты, одобрение
Технические обзоры

Синтез-конвейер: противоречия, бюджеты, одобрение

Как конвейер синтеза Simmetric Chat собирает документы архива в вики-страницы - ходы LLM с бюджетом на прогон, обнаружение противоречий с фильтром сходства и LLM-судьёй, и заслон выборочного одобрения прежде, чем что-то тронет файловую систему.

Simmetric Team · 16/10/20267 мин чтения
Квоты и учёт затрат: движок hold-and-settle
Технические обзоры

Квоты и учёт затрат: движок hold-and-settle

Как quota engine Simmetric Chat охватывает токены, хранилище и настоящие деньги - дизайн без второго счётчика на ledger'е, семантика fail-open когда ничего не настроено, и двухфазная hold-and-settle-бронь, делающая потолки расхода заслоном, а не отчётом.

Simmetric Team · 14/10/20268 мин чтения
Wiki-движок знаний: правила, директивы и синтез
Технические обзоры

Wiki-движок знаний: правила, директивы и синтез

Как wiki-движок Simmetric Chat управляет записями в архивы трёхслойной моделью инструкций - жёсткие правила, принуждаемые кодом, консультативный schema prompt каждого архива, структурированные поля - и что цепочка приоритетов значит на практике.

Simmetric Team · 11/10/20267 мин чтения