
# Подробный анализ CVE-2026-22038 Анализ уязвимости CVE-2026-22038 — уязвимости высокой степени серьезности в блоках AutoGPT Stagehand, которая приводит к записи API-ключей в открытом виде, включая первопричину, влияние и меры по устранению.
CVE ID: CVE-2026-22038
Продукт: AutoGPT Platform (интеграция Stagehand)
Затронутые версии: Все версии вплоть до autogpt-platform-beta-v0.6.45 включительно
Исправлено в: autogpt-platform-beta-v0.6.46
Тип уязвимости: CWE-532 — Вставка конфиденциальной информации в файл журнала
Серьёзность: Высокая
Сообщил: Panuganti Siva Aditya (@sivaadityacoder)
Дата отчёта: 19 декабря 2025 г.
Консультация GitHub: GHSA-rc89-6g7g-v5v7
AutoGPT — это платформа с открытым исходным кодом, которая позволяет пользователям создавать и запускать автономных AI-агентов. Она включает интеграцию Stagehand, которая подключается к браузерной автоматизации и LLM-провайдерам, таким как OpenAI, Anthropic и Groq.
Во время проверки кода блоков Stagehand я заметил, что объекты учётных данных передаются напрямую в вызовы logger.info(). Я проследил каждое лог-сообщение и обнаружил, что .get_secret_value() вызывается инлайн — что явно обходит защиту SecretStr, предоставляемую Pydantic.
Уязвимый файл:
autogpt_platform/backend/backend/blocks/stagehand/blocks.py
Затронуты три блока, каждый с одинаковым паттерном:
StagehandObserveBlock (строки 185–188)
logger.info(f"OBSERVE: Stagehand credentials: {stagehand_credentials}")
logger.info(
f"OBSERVE: Model credentials: {model_credentials} for provider "
f"{model_credentials.provider} secret: {model_credentials.api_key.get_secret_value()}"
)
StagehandActBlock (строки 285–288)
logger.info(f"ACT: Stagehand credentials: {stagehand_credentials}")
logger.info(
f"ACT: Model credentials: {model_credentials} for provider "
f"{model_credentials.provider} secret: {model_credentials.api_key.get_secret_value()}"
)
StagehandExtractBlock (строки 373–376)
logger.info(f"EXTRACT: Stagehand credentials: {stagehand_credentials}")
logger.info(
f"EXTRACT: Model credentials: {model_credentials} for provider "
f"{model_credentials.provider} secret: {model_credentials.api_key.get_secret_value()}"
)
Чтобы подтвердить эксплуатируемость, я запустил каждый блок Stagehand с действительными учётными данными и выполнил поиск по журналам приложения:
grep "secret:" /var/log/autogpt/application.log
Вывод подтвердил, что реальные API-ключи появлялись в открытом виде:
[INFO] OBSERVE: Model credentials: ... secret: sk-proj-abc123xyz789...
[INFO] ACT: Model credentials: ... secret: sk-ant-api03-def456uvw...
[INFO] EXTRACT: Model credentials: ... secret: sk-1234567890abcdef...
Кодовая база AutoGPT корректно использует тип SecretStr из Pydantic для API-ключей. Когда объект SecretStr включается в f-строку или выводится обычным способом, он отображается как ********** — эта защита является намеренной.
Корневая причина в том, что разработчики вызывали .get_secret_value() напрямую внутри операторов logger.info(). Это явно распаковывает секрет и передаёт сырое строковое значение в логгер, полностью обходя встроенное маскирование SecretStr.
Уровень логирования — INFO, что означает, что эти операторы выполняются в обычных производственных средах, а не только в локальных сессиях отладки. Каждый раз, когда один из этих блоков запускается с учётными данными, секрет записывается в файл журнала.
Сценарий атаки:
secret:, sk-proj- или sk-ant-.curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer sk-proj-abc123xyz789..." \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4", "messages": [{"role": "user", "content": "test"}]}'
Раскрытые учётные данные: API-ключи Stagehand / Browserbase, API-ключи OpenAI, API-ключи Anthropic, API-ключи Groq и любые учётные данные LLM-провайдеров, настроенные в блоках Stagehand.
Патч в autogpt-platform-beta-v0.6.46 удаляет вызовы .get_secret_value() из операторов logger.info().
Рекомендуемый подход — полностью удалить секрет из лога:
# До (уязвимо):
logger.info(
f"OBSERVE: Model credentials: {model_credentials} for provider "
f"{model_credentials.provider} secret: {model_credentials.api_key.get_secret_value()}"
)
# После (исправлено):
logger.info(
f"OBSERVE: Model credentials for provider {model_credentials.provider} (API key redacted)"
)
Альтернатива — редактирование с сохранением структуры лога:
def redact_secret(value: str) -> str:
if len(value) <= 8:
return "***"
return f"{value[:4]}...{value[-4:]}"
logger.info(
f"OBSERVE: Model credentials for provider {model_credentials.provider} "
f"secret: {redact_secret(model_credentials.api_key.get_secret_value())}"
)
Подход с редактированием раскрывает только первые и последние 4 символа — достаточно, чтобы определить, какой ключ использовался, не раскрывая полный секрет.
Никогда не вызывайте .get_secret_value() в лог-операторах. Если ваш фреймворк предоставляет тип с маскированием секретов (SecretStr, SecretBytes и т. д.), позвольте ему выполнять свою работу. Вызов метода распаковки внутри логгера сводит на нет всю цель.
INFO — это логирование производственного уровня. Дампы учётных данных в стиле отладки никогда не должны попадать на уровень INFO. Если вам действительно нужно подтвердить, какие учётные данные активны, логируйте только нечувствительные метаданные (имя провайдера, префикс ключа, последние 4 символа).
Файлы журналов — это поверхность атаки. Относитесь к ним как к любому другому хранилищу чувствительных данных — ограничивайте доступ, ротируйте их и аудитируйте содержимое. Инструменты агрегации (Splunk, ELK, Datadog) часто имеют широкий доступ на чтение, поэтому секрет в любой строке лога фактически является секретом и в этих системах.
Исправление всегда проще, чем ошибка. Удаление двух строк лога (или замена их на редактированные эквиваленты) полностью закрывает эту экспозицию. Технический долг из-за «временного отладочного логирования», оставленного в продакшене, распространён и предотвращается проверкой кода.
Защита SecretStr — это opt-in, а не автоматическая. Разработчики должны понимать, что защита действует только до тех пор, пока кто-то явно не распакует значение. Проверка кода должна помечать любое использование .get_secret_value() вне пути аутентификации.