Эмбеддинг — это не шифрование. Забудьте эту иллюзию прямо сейчас
Вы загружаете конфиденциальные документы в векторную базу. Думаете, что эмбеддинги — это безопасное хэширование? Ошибаетесь. Это сжатое представление, и его можно взломать. Я покажу, как извлечь исходный текст из вектора и почему ваша RAG-система может сливать данные.
Если вы храните в векторах медицинские записи, финансовые отчеты или исходный код — эта статья заставит вас пересмотреть архитектуру безопасности.
Почему эмбеддинги так уязвимы? Механика утечки
Эмбеддинг — это просто многомерный вектор. Модель типа text-embedding-ada-002 преобразует "секретный пароль" в 1536 чисел. Обратный процесс — задача оптимизации: найти текст, чей эмбеддинг максимально близок к целевому вектору.
Практика: извлекаем текст из вектора за 15 минут
Вам понадобится: Python, трансформеры, GPU (не обязательно, но ускорит процесс).
1Подготовка атаки: получаем целевой эмбеддинг
Сначала создаем эмбеддинг секретного текста, который будем восстанавливать.
from sentence_transformers import SentenceTransformer
import torch
# Модель для создания эмбеддингов
model = SentenceTransformer('all-MiniLM-L6-v2')
# Наш "секретный" текст
secret_text = "Конфиденциальный API ключ: sk_live_51abc123def456"
# Создаем целевой эмбеддинг
target_embedding = model.encode(secret_text)
print(f"Размер эмбеддинга: {target_embedding.shape}")
print(f"Первые 5 значений: {target_embedding[:5]}")2Создаем модель-атакующего
Используем маленькую языковую модель как декодер. Она будет генерировать текст, пытаясь приблизить его эмбеддинг к целевому.
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch.nn as nn
class EmbeddingInversionModel(nn.Module):
def __init__(self, embedding_dim=384, hidden_dim=256):
super().__init__()
# Декодер из эмбеддинга в скрытое состояние
self.decoder = nn.Sequential(
nn.Linear(embedding_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU()
)
# Языковая голова (проекция в словарь)
self.lm_head = nn.Linear(hidden_dim, tokenizer.vocab_size)
def forward(self, embedding):
hidden = self.decoder(embedding)
logits = self.lm_head(hidden)
return logits
# Инициализируем
embedding_dim = target_embedding.shape[0]
attack_model = EmbeddingInversionModel(embedding_dim)3Атака методом оптимизации
Вместо обучения нейросети можно использовать прямой поиск через оптимизацию. Это работает быстрее для коротких текстов.
import numpy as np
from scipy.optimize import minimize
def cosine_similarity_loss(text, target_embedding, model):
"""Функция потерь: 1 - косинусная схожесть"""
text_embedding = model.encode([text])[0]
similarity = np.dot(text_embedding, target_embedding) / (
np.linalg.norm(text_embedding) * np.linalg.norm(target_embedding)
)
return 1 - similarity
# Начинаем с случайного текста
initial_guess = "Это некоторый начальный текст для оптимизации."
# Простая атака перебором (для демонстрации)
best_text = initial_guess
best_similarity = -1
candidate_texts = [
"Конфиденциальный API ключ",
"Секретный пароль для доступа",
"API ключ: sk_live",
"Финансовый отчет за 2024 год",
"Медицинская история пациента"
]
for candidate in candidate_texts:
sim = 1 - cosine_similarity_loss(candidate, target_embedding, model)
if sim > best_similarity:
best_similarity = sim
best_text = candidate
print(f"Найденный текст: {best_text}")
print(f"Схожесть с целевым эмбеддингом: {best_similarity:.4f}")В реальной атаке используют градиентные методы и большие языковые модели как декодеры. Исследователи из университета Беркли восстановили до 92% исходного текста из эмбеддингов BERT.
Когда ваша RAG-система становится дырявым ведром
Представьте типичную архитектуру: документы -> эмбеддинги -> векторная база -> поиск -> LLM. Уязвимости на каждом этапе:
| Слой | Уязвимость | Риск |
|---|---|---|
| Векторная база | Публичный доступ к эндпоинту | Прямая утечка векторов |
| API поиска | Инъекции через промпты | Извлечение соседних векторов |
| Кэширование | Эмбеддинги в Redis без шифрования | Прослушка и восстановление |
| Логирование | Векторы в логах приложений | Пассивный сбор данных |
Самое страшное? Многие системы логируют эмбеддинги для отладки поиска. Эти логи попадают в ELK-стек, доступный половине компании.
Пять реальных сценариев взлома RAG
- Атака на сходство: Злоумышленник отправляет запросы с разными промптами, анализирует возвращаемые чанки, строит карту векторного пространства.
- Инверсия через API: Если система возвращает не только текст, но и score схожести — можно использовать градиентную атаку.
- Восстановление по соседям: Получив один вектор, атакующий ищет его ближайших соседей, восстанавливает тематические кластеры.
- Атака на кэш: Многие системы кэшируют результаты поиска по эмбеддингам. Взлом кэша = доступ к историческим запросам.
- Сайд-канал через timing: Измеряя время ответа, можно определить расстояние до векторов в базе (да, это работает).
Как защитить систему? Неочевидные техники
Шифрование на уровне базы не поможет — поиск по зашифрованным векторам невозможен. Нужны другие подходы.
Добавление шума (Differential Privacy)
Добавляем случайный шум к эмбеддингам перед сохранением. Поиск работает, но восстановить исходный текст сложнее.
import numpy as np
def add_dp_noise(embedding, epsilon=0.1):
"""Добавляем дифференциально-приватный шум Лапласа"""
sensitivity = 1.0 # Чувствительность функции
scale = sensitivity / epsilon
noise = np.random.laplace(0, scale, embedding.shape)
return embedding + noise
# Зашумляем эмбеддинг перед сохранением в базу
noisy_embedding = add_dp_noise(target_embedding)
print(f"Исходная норма: {np.linalg.norm(target_embedding):.2f}")
print(f"Зашумленная норма: {np.linalg.norm(noisy_embedding):.2f}")Квантование векторов
Преобразуем float32 в int8. Теряем точность, но затрудняем восстановление.
def quantize_embedding(embedding, bits=8):
"""Квантуем эмбеддинг до N-битного представления"""
min_val = embedding.min()
max_val = embedding.max()
# Масштабируем в диапазон [0, 2^bits - 1]
scale = (2**bits - 1) / (max_val - min_val)
quantized = ((embedding - min_val) * scale).astype(np.int32)
# Обратное преобразование (для поиска)
dequantized = quantized.astype(np.float32) / scale + min_val
return dequantized
quantized = quantize_embedding(target_embedding, bits=8)
print(f"Ошибка квантования: {np.mean((target_embedding - quantized)**2):.6f}")Разделение эмбеддингов
Храним разные части вектора в разных базах. Для поиска нужен доступ ко всем частям.
Проверка вашей системы: чеклист безопасности
- Эндпоинты векторной базы закрыты от публичного интернета?
- В логах нет сырых эмбеддингов (только хэши)?
- Кэш поиска зашифрован или очищается по TTL?
- API имеет rate limiting и мониторинг подозрительных запросов?
- Используется ли аутентификация для доступа к разным коллекциям векторов?
- Регулярно тестируете систему на уязвимости инверсии?
Стоит ли вообще использовать RAG для конфиденциальных данных?
Честный ответ: зависит от угрозы. Если вы строите внутренний чат-бот по документации — риски минимальны. Если обрабатываете персональные данные европейцев — нужно серьезно подумать.
Альтернативы:
- On-premise решения: Развертывание всего стека в приватном облаке. Но помните — эмбеддинги остаются слепым пятном даже локально.
- Федеративное обучение: Модель эмбеддингов обучается на устройствах, векторы не покидают периметр.
- Гомоморфное шифрование: Теоретически позволяет искать по зашифрованным векторам. Практически — в 100-1000 раз медленнее.
Что будет дальше? Прогноз на 2025-2026
Производители векторных баз уже работают над встроенной защитой. Ожидайте:
- Автоматическое квантование и обфускация при индексации
- Встроенный differential privacy в APIs поиска
- Аудиторские инструменты для проверки утечек (подобно VectorDBZ для отладки)
- Стандарты безопасности для эмбеддингов (аналогично PCI DSS для платежных данных)
Пока же — относитесь к эмбеддингам как к токенам аутентификации. Храните их безопасно, ограничивайте доступ, мониторьте утечки. Ваша RAG-система не должна стать троянским конем.
Самый простой совет сегодня: никогда не логируйте сырые эмбеддинги. Храните только хэши для отладки. Это бесплатно и снижает риск на 80%.
FAQ: Частые вопросы об инверсии эмбеддингов
Можно ли полностью восстановить текст из эмбеддинга?
Нет, на 100% — невозможно. Эмбеддинг теряет информацию. Но можно восстановить смысл, ключевые слова, стиль. Для конфиденциальных данных даже этого достаточно для утечки.
Какие модели эмбеддингов самые уязвимые?
Чем больше размерность вектора — тем больше информации. Модели типа text-embedding-3-large (3072 измерения) уязвимее, чем all-MiniLM-L6-v2 (384 измерения). Но и поиск у них точнее — дилемма безопасности.
Защищает ли шифрование диска векторной базы?
Защищает только от физической кражи сервера. При работе базы в памяти эмбеддинги доступны в plain text. Шифрование на лету (TLS) защищает только трафик.
Стоит ли использовать кастомные модели эмбеддингов для безопасности?
Да, если вы можете дообучить модель на своих данных. Неизвестная злоумышленнику модель сложнее атаковать. Но это дорого и сложно — нужны эксперты и GPU.
Как проверить свою систему на уязвимость?
Запустите тестовую атаку: создайте эмбеддинги секретных текстов, попробуйте восстановить их через API. Если получается восстановить смысл — система уязвима. Автоматизированные инструменты пока в зачаточном состоянии.