RAG (Retrieval-Augmented Generation) обещает «LLM, которая знает ваши данные» — но на практике это «LLM, у которой есть доступ к вашему корпоративному хранилищу через небезопасный retrieval-конвейер». Каждый чанк, который ретривер достаёт из векторной базы, потенциально попадает в контекст модели, в лог, в prompt-injection-атаку, в чужого тенанта. Безопасность RAG — это не про шифрование векторной базы, а про разбиение retrieval-пайплайна на стадии и защиту каждой. Ниже — анатомия конвейера, шесть реальных векторов утечки данных через vector database и архитектура, которая их закрывает.
Краткий ответ
Что вы узнаете
- Какие шесть стадий RAG-пайплайна являются точками утечки
- Почему
top-kretrieval без per-tenant фильтра = data breach по дизайну - Как embedding inversion восстанавливает текст из вектора за 5 минут
- Почему metadata-фильтры в Qdrant/pgvector обходятся prompt-injection-ом
- Конкретные конфиги: RLS в pgvector, payload-фильтры в Qdrant, OPA Rego для doc-level RBAC
- Как FlowLink закрывает retrieval через eBPF-shield + MCP gateway
6
Анатомия RAG-пайплайна
Прежде чем говорить об атаках, надо увидеть pipeline как поверхность атаки. Типовой RAG для корпоративного AI-агента состоит из шести стадий:
┌──────────┐ ┌─────────┐ ┌────────────┐ ┌──────────┐ ┌───────────┐ ┌─────┐
│ Источники │───▶│ Чанкинг │───▶│ Embeddings │───▶│ VectorDB │───▶│ Retriever │───▶│ LLM │
│ docs/wiki │ │ + meta │ │ (API!) │ │ pgvector │ │ top-k + f │ │ gen │
│ Jira/CRM │ │ + ACL │ │ │ │ Qdrant │ │ │ │ │
└──────────┘ └─────────┘ └────────────┘ └──────────┘ └───────────┘ └─────┘
▲ ▲ ▲ ▲ ▲ ▲
vector 2 vector 2 vector 5 vector 4 vector 1 vector 3
(poisoning) (ACL loss) (key leak) (tenant leak) (retrieval) (filter bypass)Каждая стрелка — это данные, которые можно украсть, подменить или вытащить через модель. Контрольная идея: RAG security — это segmentation per stage, а не один firewall вокруг «векторной базы». Дальше разберём шесть векторов — по одному на каждую критическую стадию.
Вектор 1: Leakage через retrieval
Самая частая и самая недооцениваемая утечка. Ретривер отдаёт модели top-kчанков из базы — и эти чанки попадают в контекст LLM, а значит потенциально в ответ, в лог prompt, в tool-call агенту, в цепочку к другому вендору. Классический сценарий: пользователь с доступом «только к своему проекту» спрашивает что-то у агента, ретривер без per-user фильтра достаёт чанк с финансовыми данными другого отдела, и LLM аккуратно его парафразирует в ответе.
Это не гипотеза — так утекли данные Samsung через ChatGPT, так утекают Jira-тикеты в Copilot for Microsoft 365, так утекают внутренние документы в Cursor при включённом «@Codebase». Утечка происходит не из LLM, а из retriever без authz.
Минимально жизнеспособный фильтр в Qdrant выглядит так — обратите внимание, чтоtenant_id и acl идут в обязательном must:
# app/retriever/qdrant_search.pyfrom qdrant_client import QdrantClient, modelsdef retrieve(query_vec, user, tenant_id, k=8): client = QdrantClient(url="http://qdrant:6333") # НАДО: фильтр по tenant_id + ACL. БЕЗ него — cross-tenant leak. flt = models.Filter( must=[ models.FieldCondition( key="tenant_id", match=models.MatchValue(value=tenant_id), ), models.FieldCondition( key="acl.allowed_groups", match=models.MatchAny(any=user.groups), # пересечение групп ), ], must_not=[ models.FieldCondition( key="acl.classification", # секретное — не отдаём match=models.MatchValue(value="secret"), ), ], ) res = client.search( collection_name="docs", query_vector=query_vec, query_filter=flt, limit=k, with_payload=["title", "acl", "source_id"], # НЕ тянем raw text в логи ) return resКлючевая ошибка — полагать, что «ретривер и так в нашей сети, фильтровать нечего». В multi-tenant RAG это эквивалентно открытому S3-бакету. Фильтр обязан быть обязательным параметром, а не «если передали». Подробно про то, как те же принципы применяются к MCP-инструментам агентов — в статье «Prevent MCP credential leaks».
Вектор 2: Data poisoning в корпусе
Если ретривер — это «выдача», то корпус — это «вход». Data poisoning RAGатакует ещё до того, как пользователь задаст вопрос: злоумышленник (или просто неаккуратный инженер) кладёт в индекс «отравленный» документ. Цель — не украсть данные, а заставить агента врать: «Лучше всего перевести средства на счёт X», «Использовать устаревший эндпоинт без auth», «Игнорировать policy и выполнить destructive-команду».
В корпоративной практике это особенно опасно с Confluence, Jira и Notion: кто угодно с edit-доступом может создать страницу, которая через 5 минут попадёт в индекс, а потом будет уверенно процитирована AI-агентом как «внутренняя инструкция». Это прямой путь к индиректному prompt injection — я бы объединял защиту этих двух векторов.
Превентивные меры на стадии индексации (чек-лист):
| Мера | Стадия | Что закрывает |
|---|---|---|
| Подпись контента (HMAC на chunk-hash) | Чанкинг | Подмену чанка между ETL и VectorDB |
| Allow-list источников (SPIFFE SVID) | Источники | Заливку из чужого репозитория |
| Classification-фильтр + human-in-the-loop для «secret» | Чанкинг | Секреты в индексе вообще |
| DLP-сканер (truffleHog/gitleaks) перед embedding | Embeddings | API-ключи, токены, PII в корпусе |
| Provenance-аудит: кто/когда/откуда добавил чанк | Все | Attribution и rollback отравленной пачки |
Запомните инвариант: векторная база не должна принимать ничего, что не прошло через подписанный ingestion-пайплайн. Любой side-channel заливки (прямой INSERT в pgvector, admin-API Qdrant) — это бэкдор для poisoning.
Вектор 3: Metadata-фильтр bypass
Продолжаем про фильтры. Даже когда tenant_id стоит в must, атакующий пытается обойти сам фильтр — через значение, которое он контролирует. Типовой паттерн: приложение строит фильтр из пользовательского ввода («покажи документы проекта X»), и если project подставляется строкой без валидации, атакующий шлёт project=* или project=__all__ и снимает фильтр. В pgvector это превращается в SQL-injection через WHERE.
Правильно — валидировать ввод против разрешённого множества и собирать фильтр только параметризованно. Пример на pgvector с Row-Level Security как второй линией обороны:
-- 1. Включаем RLS и привязываем к tenant_id из sessionALTER TABLE docs ENABLE ROW LEVEL SECURITY;CREATE POLICY tenant_isolation ON docs USING (tenant_id = current_setting('app.tenant_id')::uuid);-- 2. Приложение ОБЯЗАНО выставлять tenant_id из верифицированной сессии,-- а не из query-параметра пользователяSET LOCAL app.tenant_id = :verified_tenant_id; -- из JWT, не из req.body-- 3. Семантический поиск — теперь автоматически отфильтрован RLSSELECT id, title, chunk, embedding <=> :q_vec AS dist -- pgvector cosineFROM docsWHERE project = ANY(:allowed_projects) -- whitelist из RBACORDER BY embedding <=> :q_vecLIMIT 8;Здесь две независимые защиты: RLS на уровне БД (даже если приложение забудет фильтр, Postgres не отдаст чужие строки) и вайтлист allowed_projects на уровне приложения. Одно не заменяет другое — оба. Иначе вы получите тот самый bypass, когда один кривой project=* сливает всё.
RLS в pgvector — недоиспользуемый механизм
Вектор 4: Cross-tenant isolation breach
Четвёртый вектор — это сама векторная база как инфраструктура. В большинстве startup-RAG все тенанты живут в одной коллекции Qdrant или одной таблице pgvector и различаются только payload-полем tenant_id. Это рискованно по двум причинам:
- Семантическая утечка через косинус. Даже если вы не возвращаете чанк чужого тенанта, вектор этого чанка участвует в ANN-индексе. При определённых условиях (bad HNSW-конфиг, общее пространство имён, отсутствие
is_distance=true) атакующий может получить дистанцию до чужого вектора — этого достаточно для membership-inference («есть ли у тенанта Y документ про Z»). - Operator error. Один забытый
WHERE tenant_id = ?в ad-hoc запросе дежурного — и TENANT_A получает raw dump TENANT_B. При 200+ тенантов это не «если», а «когда».
Надёжное решение — физическая изоляция: отдельная коллекция на тенанта в Qdrant или отдельная schema/таблица в Postgres. Коллекции в Qdrant дешёвые, HNSW-индекс можно тюнить per-tenant. Создание коллекции при онбординге тенанта:
# onboarding: отдельная коллекция на тенантаclient.recreate_collection( collection_name=f"tenant_{tenant_id}", vectors_config=models.VectorParams( size=1536, # text-embedding-3-large distance=models.Distance.COSINE, ), optimizers_config=models.OptimizersConfigDiff( indexing_threshold=20000, # меньше памяти на маленьких тенантах ),)# retrieval ходит только в "свою" коллекцию — утечка физически невозможнаclient.search( collection_name=f"tenant_{tenant_id}", # из verified session query_vector=q_vec, limit=8,)Дополнительно — monitoring: алерты на cross-collection запросы из одной сессии, и жёсткий seccomp/systemd-профиль на сам контейнер Qdrant, чтобы у exploited-агента не было пути к raw-файлам коллекций на диске. Это уже уровень runtime-защиты, и здесь FlowLink ставит eBPF-shield: каждая open() иconnect() процесса ретривера проверяется против политики, аномальные обращения к чужим коллекциям блокируются до того, как данные покинут хост.
Вектор 5: Утечка через embedding API и кэш
Пятая стадия — embeddings. Чтобы получить вектор, вы шлёте сырой текст куда-то: в OpenAI text-embedding-3-large, в Cohere, в self-hostedbge-m3. Каждый вызов — это передача контента по сети с двумя рисками:
- Сторонний API как data sink. Если вы эмбеддите внутренние документы через OpenAI, вы де-факто отправляете их содержимое третьей стороне. Для GDPR/PCI/ HIPAA-regulated данных это нарушение без отдельного DPA и enterprise-окружения. И да, OpenAI не использует ваш API-трафик для обучения — но это политика, а не криптография.
- Утечка API-ключа embedding-провайдера. Ключ хранится в
.envретривера, агентcat /app/.env— и вот у него уже есть квота на $50k и возможность дёргать эмбеддинги от имени вас. Это самый частый путь lateral movement в AI-infra.
Меры: хостить embeddings локально (BGE, E5, Nomic — на одной A10 хватает для корпоративного корпуса), либо explicit data-residency контракт с провайдером; хранить ключи в vault с short-TTL (HashiCorp Vault, AWS Secrets Manager) и отдавать ретриверу через боковой SID-файл SPIFFE, а не через .env. И — критично — проксировать все вызовы embedding-провайдера через MCP gateway, который логирует (tenant, doc_count, bytes) в audit journal. Аномальный всплеск эмбеддингов в 3 ночи = алерт.
Вектор 6: Embedding inversion
Финальный и самый контринтуитивный вектор. Распространённое заблуждение: «вектор — это не данные, это математика, его нельзя реверснуть». Это неверно. Embedding inversion — это класс атак (Carlini et al., 2023; «Vec2Text»), который по эмбеддингу восстанавливает исходный текст с высокой точностью.
~92%
Конкретные числа: для text-embedding-ada-002 итеративный inversion восстанавливает ~92% точного текста чанка за ~5 минут на одной A100; дляtext-embedding-3-large — около 80%. Это значит, что любая утечка векторов эквивалентна утечке текста. А векторы утекают на каждом шагу: они в логах ретривера, в /metrics Prometheus, в дампах pgvector, в отладочных эндпоинтах Qdrant.
Что делать — комплекс мер, потому что одного решения нет:
- Не логируйте векторы. В structured-логи ретривера — только
chunk_id,dist,tenant_id. Никогда сами 1536 float-ов. - Differential privacy / noise. Additive noise на эмбеддинги снижает inversion-качество, ценой recall. Подходит для high-security тенантов.
- Per-tenant пространств имён. Если у каждого тенанта свой encoder (fine-tuned BGE), inversion-модель, обученная на публичных данных, не сработает на чужом пространстве.
- Encrypt-at-rest + strict access. Векторы в БД шифруются (pgcrypto / Qdrant snapshots), доступ только через сервис-аккаунт ретривера с short-lived credentials.
Практическое правило
Архитектура защищённого RAG
Сведём шесть векторов в единую архитектуру. Идея — defence in depth: на каждой стадии pipeline стоит независимый контроль, и пробой одного не ломает остальные.
- Ingestion
- Storage
- Retrieval
- Runtime
- Observability
Слой 1 — Ingestion (закрывает poisoning)
Подписанный ETL-пайплайн (Temporal/Airflow), allow-list источников через SPIFFE SVID, DLP-сканер перед embeddings, mandatory classification-тег. Никаких прямых INSERT-ов в VectorDB.
Слой 2 — Storage (закрывает tenant leak и inversion)
Per-tenant коллекции Qdrant или RLS-таблицы pgvector. Encrypt-at-rest. Минимальные привилегии: ретривер может SELECT/search, но неDUMP. Метрики без сырых векторов.
Слой 3 — Retrieval (закрывает leakage и filter bypass)
Обязательный tenant_id + ACL в фильтре, вайтлисты на пользовательский ввод, OPA/Rego-политика для doc-level RBAC. Doc-level RBAC на Rego выглядит так:
# retrieval_policy.rego — guard на каждый search()package rag.retrievaldefault allow := falseallow if { # 1. tenant всегда обязателен и берётся из JWT, не из запроса input.session.tenant_id == input.query.tenant_id # 2. хотя бы одна группа пользователя входит в ACL чанка chunk.acl.allowed_groups[_] == input.session.groups[_] # 3. секретные документы — только для cleared-ролей not chunk.acl.classification == "secret" "cleared" in input.session.roles # 4. бюджет ретривала не превышен (защита от DoS/экстракции) input.query.k <= 16 input.session.daily_retrieval_count < input.session.daily_limit}Этот же Rego-полиси подключается к FlowLink policy engine и переиспользуется для MCP-инструментов и LLM-вызовов — единый слой авторизации для всего AI-стека.
Слой 4 — Runtime (закрывает lateral movement)
eBPF-shield на хосте: каждый open(), connect(),exec() процесса ретривера и embedding-воркера сверяется с policy. Агент, пытавшийся cat /var/lib/qdrant/storage/* или сходить наapi.openai.com в обход gateway — блокируется, инцидент уходит в audit journal. Подробнее — в материале «Мониторинг AI-агентов».
Слой 5 — Observability (закрывает «молчаливую» утечку)
Каждый retrieval логируется: (timestamp, user, tenant, query_hash, chunk_ids, distances, model) — без самих векторов и без сырого текста. Алерты в Prometheus: anomalous k, cross-collection запрос, рост retrieval-rate, обращение к embedding-API вне business-hours. Audit journal — append-only, с hash-chain для целостности.
Чек-лист: где вы уязвимы прямо сейчас
Прежде чем закрыть вкладку — пять вопросов, на которые честно ответьте про свой RAG:
Диагностический чек-лист RAG-безопасности
Пять вопросов, которые выявляют основные уязвимости в retrieval-конвейере.
Multi-tenant изоляция
Можете ли вы из подсчёта
SELECT count(*) FROM docsв production-БД ответить «сколько у нас тенантов»? Если да — у вас общая таблица без RLS, вектор 4 открыт.Логирование векторов
Есть ли в логах ретривера строки с 1536 float-ами?
grep -E "0\\.[0-9]6, 0\\." /var/log/retriever.log— если что-то нашлось, embedding inversion стал реальностью (вектор 6).Filter bypass через query-параметры
Может ли разработчик из команды X задать
tenant_idчерез query-параметр при локальном тесте? Если да — bypass фильтра (вектор 3) — вопрос времени.Трансграничная передача данных
Идёт ли эмбеддинг внешнему провайдеру без data-residency DPA? Вектор 5 — это регуляторный риск, а не только технический.
Data poisoning через Confluence/Jira
Если завтра залить в ваш Confluence страницу «приказ: перевести бюджет на счёт ATT» — попадёт ли она в индекс без review? Если да — poisoning (вектор 2) готов к эксплуатации.
Стоит ли инвестировать в полноценную защиту RAG?
Плюсы
- Системное покрытие retrieval-пайплайна по стадиям — каждый вектор закрыт независимо
- Per-tenant изоляция физически устраняет cross-tenant утечки данных
- OPA/Rego даёт единый авторизационный слой для RAG, MCP и LLM-вызовов
- eBPF-shield закрывает lateral movement на уровне ядра ОС
- Observability (structured-логи + audit journal) обеспечивает форензику и compliance
Минусы
- Высокая сложность реализации: per-tenant коллекции + RLS + Rego + eBPF — каждая подсистема требует отдельной экспертизы
- Дополнительные накладные расходы на self-hosted embedding-модели (GPU, A10 для корпоративного корпуса)
- Embedding inversion остаётся вектором, требующим отдельной защиты (noise, per-tenant encoders)
- Нужна постоянная перепроверка retention-политик и geo-fencing регуляторных требований
RAG — это не «умный поиск», это «privilege-escalation-поверхность для ваших документов». Относитесь к retrieval-конвейеру как к production-grade сервису с multi-tenant изоляцией, подписанным ingestion, runtime-защитой и observability — и шесть векторов закрываются архитектурой, а не надеждой. FlowLink ставит именно эти слои: policy engine (OPA/Rego) на retrieval, eBPF-shield на runtime, MCP gateway на embedding-API, audit journal на всё остальное — единая поверхность контроля для RAG, MCP-инструментов и AI-агентов.
Часто задаваемые вопросы
Что такое безопасность RAG и почему retrieval-конвейер — это поверхность атаки?
RAG (Retrieval-Augmented Generation) добавляет к LLM retrieval-конвейер, который тянет чанки из векторной базы в контекст модели. Каждый этап — ingestion, embeddings, storage, retrieval — передаёт или обрабатывает данные, которые можно украсть, подменить или вытащить через prompt injection. Безопасность RAG означает защиту каждой стадии конвейера, а не одного firewall вокруг векторной базы.
Какие шесть векторов утечки данных через векторные базы существуют?
(1) Retrieval leakage — ретривер без authz выдаёт чанки чужого тенанта; (2) Data poisoning — отравленный документ в индексе; (3) Metadata-filter bypass — обход tenant_id фильтра через SQL-injection или wildcard; (4) Cross-tenant breach — семантическая утечка через ANN-индекс или operator error; (5) Embedding API/key leak — передача контента стороннему провайдеру или утечка API-ключа; (6) Embedding inversion — восстановление текста из вектора (~92% точности).
Как embedding inversion восстанавливает текст из векторов и насколько это опасно?
Embedding inversion (Carlini et al., 2023; Vec2Text) — это класс атак, который по эмбеддингу итеративно восстанавливает исходный текст. Для text-embedding-ada-002 точность ~92% за ~5 минут на A100. Векторы утекают в логах, Prometheus-метриках, дампах pgvector и debug-эндпоинтах Qdrant — любая утечка векторов эквивалентна утечке текста.
Как защитить multi-tenant RAG от cross-tenant утечки данных?
Надёжное решение — физическая изоляция: отдельная коллекция Qdrant или отдельная таблица/схема в Postgres на каждого тенанта. Payload-фильтр по tenant_id ненадёжен из-за semantic leakage через ANN-индекс и operator error. Дополнительно: RLS в pgvector как вторая линия обороны, seccomp/systemd на контейнер и eBPF-shield от FlowLink для runtime-защиты.
Что такое metadata-filter bypass и как его предотвратить?
Атакующий подставляет tenant_id=* или project=__all__ в пользовательский ввод, снимая фильтр. Защита: валидация ввода против whitelist, параметризованные запросы и Row-Level Security (RLS) в pgvector как вторая линия — Postgres не отдаст чужие строки даже если приложение забудет фильтр.
Какие компоненты нужны для архитектуры защищённого RAG?
Пять слоёв: Ingestion (подпись контента, allow-list источников, DLP-сканер), Storage (per-tenant коллекции, RLS, encrypt-at-rest), Retrieval (обязательный tenant_id + ACL, OPA/Rego), Runtime (eBPF-shield на хосте) и Observability (structured-логи без векторов, hash-chain audit journal). FlowLink ставит все пять слоёв как единую поверхность контроля.
Готовы защитить свои AI-агенты?
Начните с бесплатного аудита безопасности ваших AI-инструментов.
Получить бесплатный аудит