OpenClaw: Code Mode bypass, Teams lookalike и memory provenance — серия security-фиксов

OpenClaw закрыла критические уязвимости: Code Mode терял ограничения после Gateway restart, Teams-плагин отправлял credentials на look-alike домены, память инъектировала контекст без проверки provenance.

Источник: OpenClaw Academy


A technician sprints down a server corridor with a giant key to close a leaking door.

Одна ошибка — это случайность. Три подряд — это система.

За четыре часа 22–23 августа OpenClaw смержила три исправления Code Mode. Каждое по отдельности выглядит как обычный баг-фикс. Вместе они рисуют картину: state machine Code Mode имела несколько независимых путей, где обработка ошибок мутировала security-релевантные ограничения инструментов или оставляла сессии без возможности восстановления.

Первый: PR #128054 закрывает ситуацию, когда preflight-реъект Code Mode оставлял bridge dispatch state в несогласованном состоянии. После этого любые попытки восстановления отклонялись, и сессия застревала. Фикс вводит per-run dispatch state с классификацией repairProvenance — clean, eligible или invalid — и система теперь знает, какие отказы можно восстановить автоматически.

Второй: PR #128071 добавляет read-only reconciliation turn для прерванных мутаций. Если мутация была прервана на полпути, слепая повторная попытка могла применить изменение дважды, а отказ оставлял агента и workspace в разных реальностях. Новый путь консервативен: recovery turn получает только read-only инструменты (find, glob, grep, ls, read, search), требует read entitlement и сообщает, что применилось, что нет, и что осталось неизвестным.

Третий — и вот он интересный — PR #128098. После Gateway restart recovery-логика, обрабатывающая failed wait, не привязывалась к конкретному tool call. Другой, unrelated failed wait мог переключить Code Mode из ограниченного состояния, хотя ничего легитимно не изменилось. PR описывает это прямо: «users recovering after a restart could have Code Mode tools re-enabled without authorization». Это privilege-restore bug в чистом виде.

Когда Teams уводил credentials не туда

Параллельно — PR #123046, и это уже не plumbing.

Функция isGraphSharedLinkUrl() определяла, указывает ли URL вложения на Microsoft shared link, проверяя hostname через JavaScript endsWith() — с проверкой суффикса без требования границ. Хост evil1drv.ms заканчивается на 1drv.ms. notonedrive.com заканчивается на onedrive.com. fakeonedrive.live.com заканчивается на onedrive.live.com. Все три удовлетворяли старому предикату.

Когда URL классифицировался как Graph shared link, загрузчик переписывал запрос к Graph shares endpoint и переключался на Graph-аутентификацию. То есть ссылка, которая просто выглядела как shared link, могла направить credentials-машину бота на инфраструктуру, которую оператор не одобрял.

Фикс требует dot-boundary matching: суффикс сравнивается с .<suffix> или точным хостом. Дополнительно введено требование HTTPS. Регрессионные тесты покрывают реальные download flows — не только функцию-классификатор.

Это credential-routing defect. Бот не передавал токены атакующему напрямую, но принимал решения о том, какие эндпоинты заслуживают Microsoft credentials, на основании входного контента сообщений. В среде, где это решение управляемо через входящие сообщения, дистанция между фишингом и tenant-scoped Graph token сокращается.

Память без provenance

Ещё один фикс — PR #127469 — закрывает проблему memory provenance в automatic context injection. Memory provenance — это учёт OpenClaw о том, откуда пришла память и кто может её потреблять: какой runtime записал, был ли захвачен из пользовательской команды, какие recall-пути разрешены.

Проблема: automatic context path — тот, что тихо собирает background context для turnов без явной recall-команды — не единообразно соблюдал правила provenance. Explicit recall уважал ограничения, но implicit injection мог обойти их.

До фикса: записи с ограниченной provenance могли попадать в контекст модели через automatic path. Операторы, использующие memory scoping как compartmentalization control (клиентская работа против личного, production diagnostics против daily chat), не получали configured separation.

После фикса: automatic context фильтруется через те же provenance rules, что и explicit recall. Legacy stores ремонтируются в валидные формы. Runtimes, не поддерживающие provenance constraints, получают отказ вместо best-effort semantics. Command exclusions записываются для аудитирования.

Это не performance tweak. Это scope-integrity fix. Два failure mode существовали одновременно: over-exposure (restricted memories reaching model context) и under-detection (ничего не ломалось громко, так что граница не работала, и её отсутствие было незаметно).

Девять фиксов сессий, cron и health

Отдельный дайджест из девяти мерджей (#128265, #128109, #128138, #128119, #128129, #128104, #128184, #128220, #128222) закрывает ещё один набор проблем:

— Live-сессии замирали при нескольких агентах из-за transcript broadcast lanes, привязанных к raw session string вместо agent-qualified identity.

— Удаление одного чата в Control UI теряло IndexedDB transcript сохранение другого чата. Snapshot owner теперь fence’ит scoped deletes per-session revision.

— Hot reloads теряли уже принятые ответы или восстанавливали stale credentials.

— Subагенты после restart забывали последние инструкции.

— Fleet onboarding менял не ту модель.

— openclaw mcp add молча перезаписывал существующие серверы.

— Вложения с opaque download URLs игнорировались.

— Cron results пропадали при transient network failures.

— Откат часов делал мёртвые каналы здоровыми в health probes.

Последнее особенно изящно: после отката часов readiness probes возвращали HTTP 200 для остановленных каналов, и cached health оставался stale. Фикс: future-dated observations больше не доказывают readiness.

Telegram дублировал сообщения

PR #124310 и #128202 закрывают баг, при котором получатели Telegram получали один и тот же ответ дважды с интервалом в 5–15 секунд. Для этого требовалось три ингредиента: send через message tool терпел proven-not-sent failure (ECONNREFUSED, ETIMEDOUT или dispatch rejection), durable write-ahead delivery row был помечен failDeliveryBeforePlatformSend, что очищало platformSendAttemptId и делало row replay-eligible, и тот же error возвращался caller’у — агенту, который немедленно ретраил.

Результат: модельный retry плюс reconnect/poll drain, проигрывающий ту же самую row. Характерный интервал 5–15 секунд — это время до следующего успешного poll в Telegram.

Фикс назначает retry ownership: после proven-not-sent failure durable queue перестаёт хранить вторую live copy. Для remote Gateway топологии (#128202) queue становится единственным retry owner через маркировку recoveryOwnedRetry.

Что операторам делать

Обновиться. Особенно если используется Code Mode с exec approval policies или post-restart автоматизацией — PR #128098 закрывает restriction-bypass window через Gateway restart. Протестировать Teams extension на look-alike host checks. Проверить memory provenance на restricted memories. Проверить audit logs за 22–23 августа на неожиданный Code Mode availability.

Каждое исправление по отдельности — plumbing. Вместе — напоминание: recovery actions должны быть привязаны к конкретному failure, не нести write authority и классифицироваться как trusted перед выполнением. Это принципы, а не патчи. Но они пришли через патчи.

Краткий ответ

OpenClaw закрыла серию security-уязвимостей: Code Mode терял ограничения после Gateway restart (PR #128098 — privilege-restore bug), Teams-плагин направлял credentials на look-alike домены через bare-suffix endsWith() баг (PR #123046), automatic context injection обходил memory provenance (PR #127469). Дополнительно закрыты 9 фиксов сессий, cron, health probes и Gateway health. Каждое исправление — plumbing. Вместе — системная hardening-кампания.

← Все новости