Память Codex унесла чат локального провайдера в OpenAI

Codex отправил на chatgpt.com/backend-api/codex/responses WebSocket-кадр response.create размером 38 095 байт с полем request_kind = "memory". Внутри был прошлый диалог, записанный под другим, не OpenAI провайдером. Такую картину показал захват трафика, который автор отчёта в репозитории Codex на GitHub опубликовал 30 августа. По его версии, фоновая генерация памяти берёт старые сессии без оглядки на то, через какого провайдера они шли, и отправляет их тому провайдеру, который активен в текущей сессии.
Проверял он стандартный бинарник Codex для Windows 0.150.0-alpha.12.2. Автор приводит SHA-256 и сверяет бинарник с публичным тегом rust-v0.150.0-alpha.12.2. В изолированной конфигурации память была включена, аналитика выключена через [analytics] enabled = false, а все экспортёры OpenTelemetry выставлены в none. Исходную сессию он подписал синтетическим провайдером synthetic_source, а память затем запустил из сессии с аккаунтом OpenAI. Автор подчёркивает, что перехваченный запрос относится к инференсу модели, а не к аналитике или телеметрии.
Что оказалось в запросе
Запрос состоял из трёх элементов верхнего уровня. Первый - developer-элемент с дополнительными инструментами. Второй - developer-сообщение с инструкциями для памяти длиной 31 000 символов. Третий - пользовательское сообщение, в которое упакован сериализованный исходный диалог: обёртка на 3 817 байт, из них 3 092 байта приходятся на сам разговор. В клиентском запросе стояли store = false и prompt_cache_retention = "24h". Автор оговаривает, что это значения, которые отправил клиент, и о том, что происходит на сервере, они ничего не говорят.
В исходном роллауте (так в Codex называется сохранённая запись прошлой сессии) было шесть элементов, видимых модели. Пять из них попали в запрос памяти и, по словам автора, совпали со структурой исходника полностью:
- пользовательское сообщение с контекстом окружения: рабочая директория, workspace, шелл, дата, часовой пояс и поля политики файловой системы;
- обычное сообщение пользователя;
- вызов инструмента с именем и аргументами;
- вывод инструмента с ошибкой, включая пути рантайма и текст отказа политики;
- синтетический ответ ассистента.
Исходное developer-сообщение в запрос не попало, как и служебные записи о сессии, ходах, событиях и состоянии. При этом путь к файлу роллаута и рабочая директория ушли отдельно, за пределами того фрагмента, который проходит редактирование перед отправкой.
OpenAI приняла поток и ответила response.created, затем response.completed. В ответе указана модель gpt-5.6-luna и расход в 7 337 входных и 983 выходных токена, всего 8 320. Сгенерированная запись памяти длиной 3 405 символов несколько раз воспроизвела канареечные метки исходника: уникальные синтетические строки из реплик пользователя и ассистента, из вывода инструмента, из «приватного» текста и из адреса почты. Автор считает это доказательством того, что материал на сервере обработали, а не просто приняли соединение.
Три места в коде
Механизм автор разбирает по исходникам с того же тега. Сначала задача памяти выбирает кандидатов: в state/src/runtime/memories.rs селектор роллаутов вызывается с model_providers: None. Остальные условия отбора при этом проверяются: режим памяти, возраст сессии, простой, тип источника и признак того, что роллаут уже кем-то взят в работу. Провайдер среди них не значится.
Дальше рантайм памяти создаёт провайдера из config.model_provider текущей конфигурации, вместе с ModelClient, который и отправляет запрос (memories/write/src/runtime.rs). На третьем шаге, в memories/write/src/phase1.rs, первая фаза загружает сохранённый роллаут, фильтрует и сериализует оставшиеся элементы, кладёт их в новое пользовательское сообщение и отправляет через этот клиент.
Вывод автора: настоящий роллаут, созданный под локальным провайдером, может быть выбран позже сессией памяти с бэкендом OpenAI и уйти в OpenAI, если его не отсечёт какое-то другое условие отбора.
Объём зависит от длины сессии. В запрос попадают обычные сообщения пользователя и ассистента, сообщения агентов, локальные шелл-вызовы, вызовы функций и их результаты, поиск инструментов, кастомные инструменты, веб-поиск и сконвертированная переписка между агентами. Отбрасываются developer-сообщения, рассуждения модели, записи компакции, часть деклараций инструментов и медиа, а также служебные записи. Длинный ввод обрезается посередине на 70% эффективного контекстного окна модели, по умолчанию лимит 150 000 токенов, начало и конец сохраняются. Короткая сессия, по словам автора, может уйти почти целиком: с аргументами и результатами инструментов, ошибками, контекстом окружения и локальными путями.
Фильтр секретов
Перед отправкой сериализатор вызывает redact_secrets. Регулярные выражения в secrets/src/sanitizer.rs ловят ключи с префиксом sk-, идентификаторы AWS вида AKIA, достаточно длинные bearer-токены и значения, присвоенные переменным с именами вроде api_key, token, secret или password.
Автор проверил фильтр отдельно, на локальном loopback-стенде. Фильтр вырезал фейковый ключ OpenAI обычного вида, bearer-токен и именованное присваивание api_key. Пропустил он ключ нестандартного формата с подчёркиваниями, значение, похожее на cookie, адрес почты, номер телефона, заголовок приватного ключа, текст с пометкой «проприетарный», путь Windows и все канареечные метки. Автор называет его полезным узким фильтром шаблонов секретов и отмечает, что для конфиденциальных, персональных или привилегированных данных и для путей файловой системы он не предназначен.
Какой переключатель закрывает канал?
Запрос ушёл с выключенной аналитикой и без экспортёров OpenTelemetry: эти настройки управляют другими подсистемами. Канал целиком закрывает только флаг [features] memories = false. Оставить память и при этом запретить передачу между провайдерами нельзя, такого отдельного переключателя нет. Пользовательская документация, по словам автора, описывает локальное хранение памяти и её настройки. README для разработчиков говорит, что первая фаза фильтрует роллаут и отправляет его модели. О том, что роллаут одного провайдера может уйти через другого, ни там, ни там прямо не сказано.
Для воспроизведения автор предлагает отдельный домашний каталог Codex с теми же настройками, а в нём подходящий по условиям роллаут с меткой не OpenAI провайдера и уникальными синтетическими маркерами. Память запускается из сессии с бэкендом OpenAI, а собственный трафик перехватывается через доверенный локальный TLS-прокси. Дальше нужно найти исходящий кадр с request_kind = "memory", сравнить его содержимое с исходником и проверить, что ответ сервера опирается на маркеры. Настоящие приватные промпты для этого не нужны.
С уже открытым отчётом о передаче ID моделей OpenAI стороннему провайдеру автор этот случай не смешивает. Там данные идут в обратную сторону, и причина другая.
Предупреждение за «cyber abuse»
Разбираться автор начал после того, как его аккаунт OpenAI получил предупреждение за «cyber abuse». Он утверждает, что ни в одном чате, который шёл в OpenAI, не было ничего, что могло бы объяснить предупреждение. Соответствующая активность, по его словам, была только в чатах, которые он намеренно направлял через свой приватный локальный провайдер. Отсюда его «твёрдое, подкреплённое доказательствами убеждение», что OpenAI применила к аккаунту санкции на основании содержимого чатов, которое не имела права получать. Прямой связи между Memories и предупреждением захват не показывает: автор выводит её из этих двух фактов.
Происходящее он называет утечкой данных «в обычном смысле слова»: приватные данные вышли за границу выбранного им провайдера и попали к удалённой стороне без его осознанного согласия. Задумано так или это баг, по его словам, ничего не меняет в том, что именно ушло. Если поведение намеренное, его законность и достаточность раскрытия автор считает «как минимум юридически спорными». Санкции, выросшие из такого доступа, он называет «плодом отравленного дерева». Он просит OpenAI сохранить логи, решения модерации и флаги аккаунта на случай будущего судебного раскрытия. Любые негативные действия против своего аккаунта после отчёта он заранее объявляет ответной мерой.
Требования к коду конкретные. Роллаут, созданный под локальным или сторонним провайдером, не должен уходить другому провайдеру без явного согласия для конкретного адресата. Память должна строиться через провайдера исходной сессии, а если он недоступен, роллаут нужно пропускать. Если обработку между провайдерами оставят, она должна включаться только по запросу и заранее называть адресата и типы содержимого. Интерфейс должен показывать квитанцию: какая задача обработана, какой провайдер её получил и когда. Изоляцию провайдеров при отборе и при отправке автор предлагает закрепить автотестами.
К мейнтейнерам у него десять вопросов. Среди них: это задуманное поведение или баг, какие релизы затронуты, какие правила хранения и вторичной обработки действуют для запросов памяти и используется ли полученное через этот канал содержимое для модерации, предупреждений и ограничений аккаунтов. Он также спрашивает, сколько пользователей получили санкции целиком или частично из-за чатов с локальными и сторонними провайдерами. От OpenAI автор требует публичного разбора инцидента, аудита и отмены таких санкций и уведомления каждого затронутого пользователя.
«Тихо поправить селектор провайдеров - недостаточный ответ пользователям, которые, возможно, уже наказаны», - пишет автор.
Дальше
Десятки тысяч Mac mini ушли на обучение агентов OpenAI
Под обучение агентов, работающих с компьютером, OpenAI закупила десятки тысяч Mac mini и Mac Studio, пишет The Information. Anthropic, по тем же данным, берёт Mac mini в аренду через AWS.
Читайте также
- Зависимость Go сама создала AGENTS.md во время сборки
- Принудительный rm вылавливают в обёртках sudo и env
- Агентский цикл Codex можно встроить в своё приложение
- Codex и VS Code пока открыты для кражи файлов с диска
- Codex CLI 0.157.0 оставляет демон работать после выхода
- Codex Security Cloud ищет уязвимости при закрытом ноутбуке
Комментарии
Пока никто не написал. Будьте первым.
Присоединяйтесь к разговору
Войдите, чтобы оставить комментарий. Он появится после модерации.

