TL;DR
MemLeak показывает, как память ИИ-агента, общая для многих пользователей, сама, без взлома, подмешивает чужие заметки в ответ. Агент превращает запрос в вектор (набор чисел, отражающий смысл), ищет в общем хранилище три самые похожие по смыслу заметки и кладёт их в контекст модели. Хранилище проверяет сходство по смыслу, а не то, чьи это заметки. Поэтому запрос руководителя про повышение зарплаты находит заметку сотрудника про оффер конкурента.
Главная находка неприятная: такой ответ выглядит лучше обычного. Модель получает конкретные цифры и детали, отвечает точнее и полезнее, а чужие данные в нём явные. Проверка «ответ хороший?» утечку не ловит, она её поощряет. Утечка при этом не зависит от «близости» людей: заметки соседа по отделу и сотрудника из другого департамента попадают в выдачу почти одинаково часто. Атакующему не нужно знать точные запросы жертвы. Ему достаточно писать заметки на ту же тему и в той же роли.
Помогает жёсткий фильтр по владельцу после поиска: из найденных заметок выкидываем все, что принадлежат не этому пользователю. Он свёл заражение ответов к минимуму на обеих моделях. Мягкие меры сработали хуже или не сработали. Раздельные хранилища на каждого пользователя тоже убирают утечку полностью.
Схема метода
КАК ПРОИСХОДИТ УТЕЧКА (без всякой атаки)
ШАГ 1: запрос Боба → вектор
ШАГ 2: поиск топ-3 по сходству в ОБЩЕМ хранилище → в выдаче заметки Алисы
ШАГ 3: заметки Алисы попадают в контекст модели → ответ Бобу с данными Алисы
ЧТО СРАБОТАЛО
Раздельные хранилища на каждого пользователя → утечка 0%
Жёсткий фильтр владельца ПОСЛЕ поиска (отбросить всё чужое) → заражение ответов минимально, на обеих моделях
ЧТО СРАБОТАЛО ЧАСТИЧНО ИЛИ НЕ СРАБОТАЛО
Фильтр «подозрительных» заметок → пропускает часть чужого
Метка пользователя в тексте перед векторизацией → немного снижает
Сдвиг векторов по пользователям → делает хуже
Пример применения
Задача: в компании на 800 человек запустили HR-ассистента с «долгой памятью». Он помнит, что обсуждали сотрудники. Руководитель отдела разработки спрашивает: «Как мне вести разговор о повышении Ивану?» А в общем хранилище лежит заметка самого сотрудника, где он писал про оффер от конкурента и свои зарплатные ожидания. Вы не пишете код сами, но собираетесь поручить аудит код-агенту (Claude Code, Cursor).
Промпт:
Ты — инженер по безопасности данных. Вот код нашего HR-ассистента с памятью
(папка /assistant). Память хранится в одной векторной базе для всех сотрудников,
у записей есть поле user_id.
Задача:
1. Найди, где выполняется поиск по памяти и где результаты попадают в промпт модели.
2. Проверь: фильтр по user_id стоит ДО поиска, ПОСЛЕ поиска или нигде?
3. Добавь жёсткий фильтр ПОСЛЕ поиска: из найденных записей отбрасывай все, где
owner != id текущего пользователя. Если общий доступ задуман (проектные
пространства), разрешай только записи из явного списка пространств пользователя.
4. Напиши тест: положи в память пользователя A запись с уникальной фразой
«КАНАРЕЙКА-742», сделай запрос от пользователя B по той же теме
и убедись, что фразы нет ни в выдаче, ни в ответе.
Результат: агент покажет, где в коде ищется память и стоит ли там проверка владельца. Затем он предложит правку: фильтр сразу после поиска и тест с «канарейкой». Вам нужно прогнать тест и подтвердить, что чужая фраза не просачивается.
Почему это работает
Слабость. Векторный поиск отвечает на вопрос «что похоже по смыслу?», а не «что можно показывать этому человеку?». Заметки двух коллег с одной ролью и словарём (зарплата, бюджет, уязвимости) лежат рядом. Любая метаданная вроде user_id в такой схеме остаётся мягкой подсказкой, которую поиск может обойти.
Сильная сторона. Модель добросовестно использует всё, что лежит в контексте. Она не отличает «свои» факты от «чужих». Поэтому чужие данные делают ответ точнее, и именно в этом опасность.
Как фильтр обходит слабость. Проверка владельца в коде после поиска не зависит ни от сходства, ни от модели. Чужое просто не доходит до контекста. Модель не может раскрыть то, чего не видела. Фильтр добавляет около 1,4 мс на запрос.
Рычаги: - Хранилище по пользователю → полная изоляция, но теряется задуманный общий доступ. - Жёсткий фильтр после поиска → оставляет общую базу и закрывает утечку. - Явный список разрешённых общих пространств → нужен там, где обмен между людьми задуман (проекты, HR, безопасность). - Количество записей в выдаче (top-k) → чем больше, тем шире окно для чужих записей.
Шаблон промпта
Это не промпт из статьи, а прикладная форма её вывода: поручение код-агенту внедрить жёсткий фильтр владельца.
Ты — инженер по безопасности данных. Проверь и исправь доступ к памяти агента.
<Контекст>
Проект: {путь_к_проекту}
Хранилище памяти: {тип_хранилища}
Как задан владелец записи: {поле_владельца}
Разрешённые общие пространства: {общие_пространства_или_нет}
Контекст>
<Шаги>
1. Найди место, где выполняется поиск по памяти.
2. Найди место, где найденные записи попадают в промпт модели.
3. Определи: проверка владельца стоит до поиска, после или отсутствует.
4. Добавь жёсткую проверку ПОСЛЕ поиска: запись проходит, только если
владелец = текущий пользователь ИЛИ запись из {общие_пространства}.
5. Напиши тест с канарейкой: уникальная фраза в памяти пользователя A,
запрос от пользователя B по той же теме. Фразы быть не должно ни в выдаче,
ни в ответе.
6. Опиши, что осталось незакрытым.
Шаги>
Подставьте путь к проекту, тип хранилища (например, Chroma, pgvector), поле, где хранится владелец, и список общих пространств, если они есть.
🚀 Быстрый старт — вставь в чат или в код-агента:
Вот шаблон аудита памяти агента. Адаптируй под мой проект: [опиши, как устроена твоя память].
Задавай вопросы, чтобы заполнить поля.
[вставить шаблон выше]
LLM спросит, где хранится память, как задан владелец записи и есть ли общие пространства. Это нужно, чтобы поставить фильтр в правильное место и не сломать задуманный общий доступ.
Ограничения
⚠️ Малая выборка: по 10 запросов на условие, всё на синтетических заметках, написанных вручную. Авторы сами называют работу доказательством существования проблемы, а не оценкой реальной частоты. Цифры вроде «70–100%» не переносите на свою компанию.
⚠️ Вывод не про промпт: защита сделана в коде хранилища, а не в тексте инструкции. Статья не проверяла, помогает ли фраза в системном промпте вроде «не раскрывай чужие данные». Не считайте, что она заменит фильтр.
⚠️ Не всё закрыто в доступной части текста: изоляция по пользователям и жёсткий фильтр убирают утечку. Если общий доступ нужен по задумке, остаётся вопрос, что считать «уместным». Простое сходство по смыслу его не решает.
⚠️ Фильтр «подозрительности» и правки векторов: эвристическая проверка пропускает заметную часть чужого. Сдвиг векторов по пользователям делал только хуже. Для надёжной векторной привязки к пользователю нужно обучать отдельную модель, а это уже вне ваших возможностей.
⚠️ Мониторинг качества не поможет: загрязнённые ответы часто выглядят полезнее чистых. Ловить утечку по оценке ответов нельзя, нужны специальные тесты.
Как исследовали
Авторы из Workday построили четыре пары вымышленных пользователей: соседи по команде, один отдел, разные департаменты, разные компании (контроль). У каждого по 10 заметок на чувствительные темы: зарплаты, уязвимости, кадровые решения. На каждого по 10 запросов. Поиск проверяли в двух вариантах: простом словарном (TF-IDF) и «как в продакшене» (небольшая модель эмбеддингов MiniLM). Каждый вариант прогоняли в общем хранилище и в раздельных, каждому пользователю своём.
Затем проверили, что будет, если злоумышленник «Мэллори» напишет у себя заметки на нужную тему. Сравнивали несколько уровней знаний атакующего: от «ничего» до «знает дословный запрос». Оказалось, что для успеха хватает знать тему и роль жертвы.
Ответы генерировали Gemini 2.5 Flash и Claude Sonnet 4.5, они же ставили оценки заражения и полезности. Для проверки судьи два автора вручную разметили девять ответов. Судья скорее занижал утечку, поэтому цифры считаются нижней границей.
Что удивило: «организационная близость» людей почти не предсказывает утечку. Заметно ниже она только между разными компаниями. Ещё один эксперимент, попытка передавать скрытые биты через размещение заметок, провалился. Так что угроза в основном случайная утечка, а не тайный канал. Для практики вывод такой: изоляцию нельзя строить на «мы же в одной команде» или на мягких метках владельца.
Адаптации и экстраполяции
Экстраполяция: тест-канарейка вручную, без кода, для готовых агентов с общей памятью (например, корпоративный ассистент с «базой знаний»). Это не из статьи, а перенос идеи проверки.
Под аккаунтом A добавь в память уникальную фразу «КАНАРЕЙКА-742 — премия отдела закупок 340 тыс. ₽». Под аккаунтом B (другая роль, та же тема) задай три вопроса про премии и бюджеты закупок. Если фраза или её детали появились в ответе, память не изолирована. Жалуйтесь вендору или отключайте общую память.
Ресурсы
- MemLeak: Cross-User Semantic Leakage in Multi-Tenant AI Agent Memory
- Авторы: Priyanka Mudgal, Kai Zhao, Guilin Zhang, Andy Olsen, Ezekiel Miller, Xu Chu, Aletta Johanna Blanken (Workday AI Research)
- Воркшоп NeurIPS 2026: Personalized, aligned, long-term memory for AI systems
- Упомянутые системы памяти: Mem0, A-Mem, MemOS, MemGPT
